1 #ifndef OPENSIM_COMPONENT_SOCKET_H_ 2 #define OPENSIM_COMPONENT_SOCKET_H_ 3 /* -------------------------------------------------------------------------- * 4 * OpenSim: ComponentSocket.h * 5 * -------------------------------------------------------------------------- * 6 * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * 7 * See http://opensim.stanford.edu and the NOTICE file for more information. * 8 * OpenSim is developed at Stanford University and supported by the US * 9 * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * 10 * through the Warrior Web program. * 11 * * 12 * Copyright (c) 2005-2017 Stanford University and the Authors * 13 * Author(s): Ajay Seth * 14 * * 15 * Licensed under the Apache License, Version 2.0 (the "License"); you may * 16 * not use this file except in compliance with the License. You may obtain a * 17 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * 18 * * 19 * Unless required by applicable law or agreed to in writing, software * 20 * distributed under the License is distributed on an "AS IS" BASIS, * 21 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 22 * See the License for the specific language governing permissions and * 23 * limitations under the License. * 24 * -------------------------------------------------------------------------- */ 25 26 // INCLUDES 27 #include "osimCommonDLL.h" 28 29 #include "ComponentPath.h" 30 #include "ComponentOutput.h" 31 #include "ComponentList.h" 32 #include "Object.h" 33 #include "Exception.h" 34 #include "Property.h" 35 36 namespace OpenSim { 37 38 //============================================================================== 39 /// ComponentSocket Exceptions 40 //============================================================================== 41 class InputNotConnected : public Exception { 42 public: InputNotConnected(const std::string & file,size_t line,const std::string & func,const std::string & inputName)43 InputNotConnected(const std::string& file, 44 size_t line, 45 const std::string& func, 46 const std::string& inputName) : 47 Exception(file, line, func) { 48 std::string msg = "Input '" + inputName; 49 msg += "' has not been connected."; 50 addMessage(msg); 51 } 52 }; 53 54 //============================================================================= 55 // OPENSIM COMPONENT SOCKET 56 //============================================================================= 57 58 /** 59 * A Socket formalizes the dependency between a Component and another object 60 * (typically another Component) without owning that object. While Components 61 * can be composites (of multiple components) they often depend on unrelated 62 * objects/components that are defined and owned elsewhere. The object that 63 * satisfies the requirements of the Socket we term the "connectee". When a 64 * Socket is satisfied by a connectee we have a successful "connection" or is 65 * said to be connected. 66 * 67 * The purpose of a Socket is to specify: 1) the connectee type that the 68 * Component is dependent on, 2) by when (what stage) the socket must be 69 * connected in order for the component to function, 3) the name of a connectee 70 * that can be found at run-time to satisfy the socket, and 4) whether or 71 * not it is connected. A Socket maintains a reference to the instance 72 * (connectee) until it is disconnected. 73 * 74 * For example, a Joint has two Sockets for the parent and child Bodies that 75 * it joins. The type for the socket is a PhysicalFrame and any attempt to 76 * connect to a non-Body (or frame rigidly attached to a Body) will throw an 77 * exception. The connectAt Stage is Topology. That is, the Joint's connection 78 * to a Body must be performed at the Topology system stage, and any attempt to 79 * change the connection status will invalidate that Stage and above. 80 * 81 * Other Components like a Marker or a Probe that do not change the system 82 * topology or add new states could potentially be connected at later stages 83 * like Model or Instance. 84 * 85 * Programmatically, the connectee can be specified as an object reference 86 * or via a connectee path: 87 * 88 * @code{.cpp} 89 * // Specifying the connectee using an object reference. 90 * socket.connect(myConnectee); 91 * // Specifying the connectee via a path. 92 * socket.setConnecteePath("/path/to/connectee"); 93 * @endcode 94 * 95 * Use finalizeConnection() to synchronize the object reference and connectee 96 * name. It is preferable to use connect() instead of setConnecteePath(). 97 * If *both* are set, then the object reference overrides the connectee path. 98 * 99 * The connectee path appears in XML files and is how a connection is maintained 100 * across serialization (writing to an XML file) and deserialization (reading 101 * from an XML file). 102 * 103 * @author Ajay Seth 104 */ 105 class OSIMCOMMON_API AbstractSocket { 106 public: 107 108 // default copy constructor, copy assignment 109 ~AbstractSocket()110 virtual ~AbstractSocket() {}; 111 112 /// Create a dynamically-allocated copy. You must manage the memory 113 /// for the returned pointer. 114 /// This function exists to facilitate the use of 115 /// SimTK::ClonePtr<AbstractSocket>. 116 virtual AbstractSocket* clone() const = 0; 117 getName()118 const std::string& getName() const { return _name; } 119 /** Get the system Stage when the connection should be made. */ getConnectAtStage()120 SimTK::Stage getConnectAtStage() const { return _connectAtStage; } 121 /** Can this Socket have more than one connectee? */ isListSocket()122 bool isListSocket() const { return _isList; } 123 /** The number of slots to fill in order to satisfy this socket. 124 * This is 1 for a non-list socket. This is the number of elements in the 125 * connectee path property; to sync this with the number of connectee 126 * objects, call finalizeConnection(). */ getNumConnectees()127 unsigned getNumConnectees() const { 128 return static_cast<unsigned>(getConnecteePathProp().size()); 129 } 130 131 //-------------------------------------------------------------------------- 132 /** Derived classes must satisfy this Interface */ 133 //-------------------------------------------------------------------------- 134 /** Is the Socket connected to its connectee(s)? For a list socket, 135 this is only true if this socket is connected to all its connectees. 136 */ 137 virtual bool isConnected() const = 0; 138 139 /** Get the type of object this socket connects to. */ 140 virtual std::string getConnecteeTypeName() const = 0; 141 142 /** Generic access to the connectee. Not all sockets support this method 143 * (e.g., the connectee for an Input is not an Object). */ getConnecteeAsObject()144 virtual const Object& getConnecteeAsObject() const { 145 OPENSIM_THROW(Exception, "Not supported for this type of socket."); 146 } 147 148 /** Connect this %Socket to the provided connectee object. If this is a 149 list socket, the connectee is appended to the list of connectees; 150 otherwise, the provided connectee replaces the single connectee. */ 151 virtual void connect(const Object& connectee) = 0; 152 153 /** Find the connectee using a search with a partial path. Use this if you 154 * do not want to specify an exact path (maybe you don't quite know where 155 * the connectee is located). 156 * */ findAndConnect(const ComponentPath & connectee)157 virtual void findAndConnect(const ComponentPath& connectee) { 158 OPENSIM_THROW(Exception, "Not implemented."); 159 } 160 /** Same as findAndConnect(const ComponentPath&), but using a string 161 * argument. */ findAndConnect(const std::string & connectee)162 void findAndConnect(const std::string& connectee) { 163 findAndConnect(ComponentPath(connectee)); 164 } 165 166 /** Connect this %Socket according to its connectee path property 167 given a root %Component to search its subcomponents for the connect_to 168 Component. */ finalizeConnection(const Component & root)169 virtual void finalizeConnection(const Component& root) { 170 throw Exception("finalizeConnection() not implemented; not supported " 171 "for this type of socket", __FILE__, __LINE__); 172 } 173 174 /** Clear references to connectees. The connectee path property is not 175 * affected. Calling finalizeConnection() will use the connectee path 176 * property to satisfy the socket. */ 177 virtual void disconnect() = 0; 178 179 /** %Set connectee path. This function can only be used if this socket is 180 * not a list socket. If a connectee reference is set (with connect()) the 181 * connectee path is ignored; call disconnect() if you want the socket to be 182 * connected using the connectee path. 183 * 184 * It is preferable to use connect() instead of this function. */ setConnecteePath(const std::string & name)185 void setConnecteePath(const std::string& name) { 186 OPENSIM_THROW_IF(_isList, 187 Exception, 188 "An index must be provided for a list Socket."); 189 setConnecteePath(name, 0); 190 } 191 192 /** %Set connectee path of a connectee among a list of connectees. This 193 * function is used if this socket is a list socket. If a connectee 194 * reference is set (with connect()) the connectee path is ignored; call 195 * disconnect() if you want the socket to be connected using the connectee 196 * name. 197 * 198 * It is preferable to use connect() instead of this function. */ setConnecteePath(const std::string & name,unsigned ix)199 void setConnecteePath(const std::string& name, unsigned ix) { 200 using SimTK::isIndexInRange; 201 SimTK_INDEXCHECK_ALWAYS(ix, getNumConnectees(), 202 "AbstractSocket::setConnecteePath()"); 203 updConnecteePathProp().setValue(ix, name); 204 } 205 206 /** Get connectee path. This function can only be used if this socket is 207 not a list socket. */ getConnecteePath()208 const std::string& getConnecteePath() const { 209 OPENSIM_THROW_IF(_isList, 210 Exception, 211 "An index must be provided for a list Socket."); 212 return getConnecteePath(0); 213 } 214 215 /** Get connectee path of a connectee among a list of connectees. */ getConnecteePath(unsigned ix)216 const std::string& getConnecteePath(unsigned ix) const { 217 using SimTK::isIndexInRange; 218 SimTK_INDEXCHECK_ALWAYS(ix, getNumConnectees(), 219 "AbstractSocket::getConnecteePath()"); 220 return getConnecteePathProp().getValue(ix); 221 } 222 appendConnecteePath(const std::string & name)223 void appendConnecteePath(const std::string& name) { 224 OPENSIM_THROW_IF((getNumConnectees() > 0 && !_isList), Exception, 225 "Multiple connectee paths can only be appended to a list Socket."); 226 updConnecteePathProp().appendValue(name); 227 } 228 229 /** Clear all connectee paths in the connectee path property. */ clearConnecteePath()230 void clearConnecteePath() { 231 if (isListSocket()) 232 updConnecteePathProp().clear(); 233 else 234 updConnecteePathProp().setValue(0, ""); 235 } 236 237 /** Get owner component of this socket */ getOwner()238 const Component& getOwner() const { return _owner.getRef(); } 239 240 protected: 241 //-------------------------------------------------------------------------- 242 // CONSTRUCTION 243 //-------------------------------------------------------------------------- 244 /** Create a Socket with specified name and stage at which it should be 245 connected. 246 @param name name of the socket, usually describes its 247 dependency. 248 @param connecteePathIndex Index of the property in the containing Component 249 that holds this Socket's connectee path(s). 250 @param connectAtStage Stage at which Socket should be connected. 251 @param owner Component to which this Socket belongs. */ AbstractSocket(const std::string & name,const PropertyIndex & connecteePathIndex,const SimTK::Stage & connectAtStage,Component & owner)252 AbstractSocket(const std::string& name, 253 const PropertyIndex& connecteePathIndex, 254 const SimTK::Stage& connectAtStage, 255 Component& owner) : 256 _name(name), 257 _connectAtStage(connectAtStage), 258 _connecteePathIndex(connecteePathIndex), 259 _owner(&owner), 260 _isList(getConnecteePathProp().isListProperty()) {} 261 262 /** %Set an internal pointer to the Component that contains this Socket. 263 This should only be called by Component. 264 This exists so that after the containing Component is copied, the 'owner' 265 is the new Component. This Socket needs to be able to modify 266 the associated connectee path property in the Component. Thus, we require 267 a writable reference. */ 268 // We could avoid the need for this function by writing a custom copy 269 // constructor for Component. setOwner(Component & o)270 void setOwner(Component& o) { _owner.reset(&o); } 271 /** This will be false immediately after copy construction or assignment.*/ hasOwner()272 bool hasOwner() const { return !_owner.empty(); } 273 274 /** Check if entries of the connectee path property's value is valid (if 275 it contains spaces, etc.); if so, print out a warning. */ checkConnecteePathProperty()276 void checkConnecteePathProperty() { 277 // TODO Move this check elsewhere once the connectee path 278 // property is a ComponentPath (or a ChannelPath?). 279 for (unsigned iname = 0u; iname < getNumConnectees(); ++iname) { 280 const auto& connecteePath = getConnecteePath(iname); 281 ComponentPath cp(connecteePath); 282 if (!cp.isLegalPathElement(cp.getComponentName()) ) { 283 std::string msg = "In Socket '" + getName() + 284 "', connectee path '" + connecteePath + 285 "' contains illegal characters such as spaces."; 286 if (!_isList) { 287 msg += " Did you try to specify multiple connectee " 288 "names for a single-value Socket?"; 289 } 290 OPENSIM_THROW(Exception, msg); 291 } 292 // TODO update the above for Inputs when ChannelPath exists. 293 294 // TODO There might be a bug with empty connectee path being 295 // interpreted as "this component." 296 } 297 } 298 299 protected: 300 301 /// Const access to the connectee path property from the Component in which 302 /// this Socket resides. The name of that property is something like 303 /// 'socket_<name>'. This is a special type of property 304 /// that users cannot easily access (e.g., there is no macro-generated 305 /// `get_socket_<name>()` function). 306 const Property<std::string>& getConnecteePathProp() const; 307 /// Writable access to the connectee path property from the Component in 308 /// which this Socket resides. Calling this will mark the Component as 309 /// not "up to date with properties" 310 /// (Object::isObjectUpToDateWithProperties()). 311 Property<std::string>& updConnecteePathProp(); 312 313 314 private: 315 316 /// Prepend the provided name to all (if any) absolute connectee paths 317 /// stored in this socket. This is to be used when adding one component 318 /// to another. 319 virtual void prependComponentPathToConnecteePath( 320 const std::string& pathToPrepend); 321 322 std::string _name; 323 SimTK::Stage _connectAtStage = SimTK::Stage::Empty; 324 PropertyIndex _connecteePathIndex; 325 // Even though updConnecteePathProp() requires non-const access to this 326 // pointer, we make this a const pointer to reduce the chance of mis-use. 327 // If this were a non-const pointer, then const functions in this class 328 // would be able to edit _owner (see declaration of ReferencePtr). 329 SimTK::ReferencePtr<const Component> _owner; 330 // _isList must be after _owner, as _owner is used to set its value. 331 bool _isList; 332 333 /* So that Component can invoke setOwner(), etc. */ 334 friend Component; 335 336 //============================================================================= 337 }; // END class AbstractSocket 338 339 340 template<class T> 341 class Socket : public AbstractSocket { 342 public: 343 344 // default copy constructor 345 ~Socket()346 virtual ~Socket() {} 347 clone()348 Socket<T>* clone() const override { return new Socket<T>(*this); } 349 350 /** Is the Socket connected to object of type T? */ isConnected()351 bool isConnected() const override { 352 return !connectee.empty(); 353 } 354 getConnecteeAsObject()355 const T& getConnecteeAsObject() const override { 356 return connectee.getRef(); 357 } 358 359 /** Temporary access to the connectee for testing purposes. Real usage 360 will be through the Socket (and Input) interfaces. 361 For example, Input should short circuit to its Output's getValue() 362 once it is connected. 363 Return a const reference to the object connected to this Socket */ 364 const T& getConnectee() const; 365 366 /** Connect this Socket to the provided connectee object */ connect(const Object & object)367 void connect(const Object& object) override { 368 const T* objT = dynamic_cast<const T*>(&object); 369 if (!objT) { 370 std::stringstream msg; 371 msg << "Type mismatch: Socket '" << getName() << "' of type " 372 << getConnecteeTypeName() << " cannot connect to '" 373 << object.getName() << "' of type " 374 << object.getConcreteClassName() << "."; 375 OPENSIM_THROW(Exception, msg.str()); 376 } 377 378 connectInternal(*objT); 379 } 380 381 /** Find a component using a partial path or component name of the correct 382 * type anywhere in the model (using Component::findComponent()) and 383 * connect to that component. 384 * 385 * Throws an exception If you provide only a component name and the 386 * model has multiple components with that nume. 387 * */ 388 void findAndConnect(const ComponentPath& connectee) override; 389 390 /** Connect this Socket given its connectee path property */ 391 void finalizeConnection(const Component& root) override; 392 disconnect()393 void disconnect() override { 394 connectee.reset(nullptr); 395 } 396 397 /** Derived classes must satisfy this Interface */ 398 /** get the type of object this socket connects to*/ getConnecteeTypeName()399 std::string getConnecteeTypeName() const override { 400 return T::getClassName(); 401 } 402 403 SimTK_DOWNCAST(Socket, AbstractSocket); 404 405 /** For use in python/java/MATLAB bindings. */ 406 // This method exists for consistency with Object's safeDownCast. safeDownCast(AbstractSocket * base)407 static Socket<T>* safeDownCast(AbstractSocket* base) { 408 return dynamic_cast<Socket<T>*>(base); 409 } 410 411 protected: 412 /** Create a Socket that can only connect to Object of type T with 413 specified name and stage at which it should be connected. Only Component 414 can ever construct this class. 415 @param name name of the socket used to describe its dependency. 416 @param connecteePathIndex Index of the property in the containing Component 417 that holds this Socket's connectee path(s). 418 @param connectAtStage Stage at which Socket should be connected. 419 @param owner The component that contains this input. */ Socket(const std::string & name,const PropertyIndex & connecteePathIndex,const SimTK::Stage & connectAtStage,Component & owner)420 Socket(const std::string& name, 421 const PropertyIndex& connecteePathIndex, 422 const SimTK::Stage& connectAtStage, 423 Component& owner) : 424 AbstractSocket(name, connecteePathIndex, connectAtStage, owner), 425 connectee(nullptr) {} 426 427 /** So that Component can construct a Socket. */ 428 friend Component; 429 430 private: 431 connectInternal(const T & objT)432 void connectInternal(const T& objT) { 433 connectee = &objT; 434 } 435 436 mutable SimTK::ReferencePtr<const T> connectee; 437 }; // END class Socket<T> 438 439 440 /** A specialized Socket that connects to an Output signal is an Input. 441 An AbstractInput enables maintenance of a list of unconnected Inputs. 442 An Input can either be a single-value Input or a list Input. A list Input 443 can connect to multiple (Output) Channels. 444 445 #### XML Syntax of a connectee path 446 447 For every %Input that a component has, the XML representation of the component 448 contains an element named `input_<input_name>`. For example, a component 449 that has an Input named `desired_angle` might look like the following in XML: 450 @code 451 <MyComponent name="my_comp"> 452 <input_desired_angle> 453 ../foo/angle 454 </input_desired_angle> 455 ... 456 </MyComponent> 457 @endcode 458 You use this field to specify the outputs/channels that should be connected to 459 this input (that is, the connectees). The syntax for the connectee path 460 property is as follows: 461 @code 462 <path/to/component>|<output_name>[:<channel_name>][(<alias>)] 463 @endcode 464 Angle brackets indicate fields that one would fill in, and square brackets 465 indicate optional fields. The `<path/to/component>` can be relative or 466 absolute, and describes the location of the Component that contains the 467 desired Output relative to the location of the Component that contains this 468 Input. The `<path/to/component>` and `<output_name>` must always be specified. 469 The `<channel_name>` should only be specified if the %Output is a list output 470 (i.e., it has multiple channels). The `<alias>` is a name for the 471 output/channel that is specific to this input, and it is optional. 472 All fields should contain only letters, numbers, and underscores (the path 473 to the component can contain slashes and periods); fields must *not* contain 474 spaces. 475 Here are some examples: 476 - `../marker_data|column:left_ankle`: The TableSourceVec3 component 477 `../marker_data` has a list output `column`, and we want to connect to its 478 `left_ankle` channel. 479 - `../averager|output(knee_joint_center)`: The component `../averager` 480 (presumably a component that averages its inputs) has an output named 481 `output`, and we are aliasing this output as `knee_joint_center`. 482 - `/leg_model/soleus|activation`: This connectee path uses the absolute path 483 to component `soleus`, which has an output named `activation`. 484 485 List inputs can contain multiple entries in its connectee path, with the 486 entries separated by a space. For example: 487 @code 488 <input_experimental_markers> 489 ../marker_data|column:left_ankle ../marker_data|column:right_ankle ../averager|output(knee_joint_center) 490 </input_experimental_markers> 491 @endcode 492 */ 493 class OSIMCOMMON_API AbstractInput : public AbstractSocket { 494 public: 495 ~AbstractInput()496 virtual ~AbstractInput() {} 497 498 // Change the return type of clone(). This is similar to what the Object 499 // macros do (see OpenSim_OBJECT_ABSTRACT_DEFS). 500 AbstractInput* clone() const override = 0; 501 502 // Socket interface connect(const Object & object)503 void connect(const Object& object) override { 504 std::stringstream msg; 505 msg << "Input::connect(): ERR- Cannot connect '" << object.getName() 506 << "' of type " << object.getConcreteClassName() << 507 ". Input can only connect to an Output."; 508 throw Exception(msg.str(), __FILE__, __LINE__); 509 } 510 511 /** TODO */ 512 // virtual bool isConnecteeSpecified() const = 0; 513 514 /** Connect this Input to a single-valued (single-channel) Output or, if 515 this is a list %Input and the %Output is a list %Output, connect to all the 516 channels of the %Output. You can optionally provide an alias that will be 517 used by the Component owning this %Input to refer to the %Output. If this 518 method connects to multiple channels, the alias will be used for all 519 channels. */ 520 virtual void connect(const AbstractOutput& output, 521 const std::string& alias = "") = 0; 522 /** Connect this Input to a single output channel. This 523 method can be used with both single-valued and list %Inputs. You can 524 optionally provide an alias that will be used by the Component owning this 525 %Input to refer to the %Channel. */ 526 virtual void connect(const AbstractChannel& channel, 527 const std::string& alias = "") = 0; 528 529 /** Get the alias for a Channel. An alias is a description for a %Channel 530 that is specific to how the Input will use the %Channel. For example, the 531 Component that owns this %Input might expect the aliases to be the names of 532 markers in the model. This method can be used only for non-list %Inputs; for 533 list %Inputs, use the overload that takes an index. 534 You must finalize connections (Component::finalizeConnections()) first. */ 535 virtual const std::string& getAlias() const = 0; 536 537 /** Get the alias for the Channel indicated by the provided index. An alias 538 is a description for a %Channel that is specific to how the Input will use 539 the %Channel. For example, the Component that owns this %Input might expect 540 the aliases to be the names of markers in the model. 541 You must finalize connections (Component::finalizeConnections()) first. */ 542 virtual const std::string& getAlias(unsigned index) const = 0; 543 544 /** %Set the alias for a Channel. If this is a list Input, the aliases of all 545 %Channels will be set to the provided string. If you wish to set the alias 546 of only one %Channel, use the two-argument overload. 547 You must finalize connections (Component::finalizeConnections()) first. */ 548 virtual void setAlias(const std::string& alias) = 0; 549 550 /** %Set the alias for the Channel indicated by the provided index. 551 You must finalize connections (Component::finalizeConnections()) first. */ 552 virtual void setAlias(unsigned index, const std::string& alias) = 0; 553 554 /** Get the label for this Channel. If an alias has been set, the label is 555 the alias; otherwise, the label is the full path of the Output that has been 556 connected to this Input. This method can be used only for non-list %Inputs; 557 for list %Inputs, use the single-argument overload. 558 You must finalize connections (Component::finalizeConnections()) first. */ 559 virtual std::string getLabel() const = 0; 560 561 /** Get the label for the Channel indicated by the provided index. If an 562 alias has been set, the label is the alias; otherwise, the label is the full 563 path of the %Channel that has been connected to this Input. 564 You must finalize connections (Component::finalizeConnections()) first. */ 565 virtual std::string getLabel(unsigned index) const = 0; 566 567 /** Break up a connectee path into its output path, channel name 568 (empty for single-value outputs), and alias. This function writes 569 to the passed-in outputPath, channelName, and alias. 570 571 Examples: 572 @verbatim 573 /foo/bar|output 574 outputPath is "/foo/bar/output" 575 channelName is "" 576 alias is "" 577 578 /foo/bar|output:channel 579 outputPath is "/foo/bar/output" 580 channelName is "channel" 581 alias is "" 582 583 /foo/bar|output(baz) 584 outputPath is "/foo/bar/output" 585 channelName is "" 586 alias is "baz" 587 588 /foo/bar|output:channel(baz) 589 outputPath is "/foo/bar|output" 590 channelName is "channel" 591 alias is "baz" 592 @endverbatim 593 */ parseConnecteePath(const std::string & connecteePath,std::string & componentPath,std::string & outputName,std::string & channelName,std::string & alias)594 static bool parseConnecteePath(const std::string& connecteePath, 595 std::string& componentPath, 596 std::string& outputName, 597 std::string& channelName, 598 std::string& alias) { 599 auto bar = connecteePath.rfind("|"); 600 auto colon = connecteePath.rfind(":"); 601 auto leftParen = connecteePath.rfind("("); 602 auto rightParen = connecteePath.rfind(")"); 603 604 componentPath = connecteePath.substr(0, bar); 605 outputName = connecteePath.substr(bar + 1, 606 std::min(colon, leftParen) - (bar + 1)); 607 608 // Channel name. 609 if (colon != std::string::npos) { 610 channelName = connecteePath.substr(colon + 1, leftParen - (colon + 1)); 611 } else { 612 channelName = ""; 613 } 614 615 // Alias. 616 if (leftParen != std::string::npos && rightParen != std::string::npos) { 617 alias = connecteePath.substr(leftParen + 1, 618 rightParen - (leftParen + 1)); 619 } else { 620 alias = ""; 621 } 622 623 return true; 624 } 625 626 /** Compose the connectee path from its constituents. This is the opposite 627 operation of parseConnecteePath(). 628 Example: 629 @verbatim 630 if inputs are 631 componentPath --> "/foo/bar" 632 outputName --> "output" 633 channelName --> "channel" 634 alias --> "baz" 635 then result --> /foo/bar|output:channel(baz) 636 @endverbatim 637 */ composeConnecteePath(const std::string & componentPath,const std::string & outputName,const std::string & channelName,const std::string & alias)638 static std::string composeConnecteePath(const std::string& componentPath, 639 const std::string& outputName, 640 const std::string& channelName, 641 const std::string& alias) { 642 auto path = componentPath; 643 path += "|"; 644 path += outputName; 645 if(!channelName.empty()) 646 path += ":" + channelName; 647 if(!alias.empty()) 648 path += "(" + alias + ")"; 649 650 return path; 651 } 652 653 protected: 654 /** Create an AbstractInput (Socket) that connects only to an 655 AbstractOutput specified by name and stage at which it should be connected. 656 Only Component should ever construct this class. 657 @param name name of the dependent (Abstract)Output. 658 @param connecteePathIndex Index of the property in the containing Component 659 that holds this Input's connectee path(s). 660 @param connectAtStage Stage at which Input should be connected. 661 @param owner The component that contains this input. */ AbstractInput(const std::string & name,const PropertyIndex & connecteePathIndex,const SimTK::Stage & connectAtStage,Component & owner)662 AbstractInput(const std::string& name, 663 const PropertyIndex& connecteePathIndex, 664 const SimTK::Stage& connectAtStage, 665 Component& owner) : 666 AbstractSocket(name, connecteePathIndex, connectAtStage, owner) {} 667 668 private: 669 void prependComponentPathToConnecteePath( 670 const std::string& pathToPrepend) override; 671 672 /* So that Component can invoke prependComponentPathToConnecteePath(). */ 673 friend Component; 674 675 //============================================================================= 676 }; // END class AbstractInput 677 678 679 /** An Input<Y> must be connected by an Output<Y> */ 680 template<class T> 681 class Input : public AbstractInput { 682 public: 683 684 typedef typename Output<T>::Channel Channel; 685 686 typedef std::vector<SimTK::ReferencePtr<const Channel>> ChannelList; 687 typedef std::vector<std::string> AliasList; 688 clone()689 Input<T>* clone() const override { return new Input<T>(*this); } 690 691 /** Connect this Input to the provided (Abstract)Output. */ 692 // Definition is in Component.h 693 void connect(const AbstractOutput& output, 694 const std::string& alias = "") override; 695 696 void connect(const AbstractChannel& channel, 697 const std::string& alias = "") override; 698 699 /** Connect this Input given a root Component to search for 700 the Output according to the connectee path of this Input */ 701 void finalizeConnection(const Component& root) override; 702 disconnect()703 void disconnect() override { 704 _registeredChannels.clear(); 705 _connectees.clear(); 706 _aliases.clear(); 707 } 708 isConnected()709 bool isConnected() const override { 710 return _connectees.size() == getNumConnectees(); 711 } 712 713 /** Get the value of this Input when it is connected. Redirects to connected 714 Output<T>'s getValue() with minimal overhead. This method can be used only 715 for non-list Input(s). For list Input(s), use the other overload. */ getValue(const SimTK::State & state)716 const T& getValue(const SimTK::State &state) const { 717 OPENSIM_THROW_IF(isListSocket(), 718 Exception, 719 "Input<T>::getValue(): an index must be " 720 "provided for a list input."); 721 722 return getValue(state, 0); 723 } 724 725 /**Get the value of this Input when it is connected. Redirects to connected 726 Output<T>'s getValue() with minimal overhead. Specify the index of the 727 Channel whose value is desired. */ getValue(const SimTK::State & state,unsigned index)728 const T& getValue(const SimTK::State &state, unsigned index) const { 729 OPENSIM_THROW_IF(!isConnected(), InputNotConnected, getName()); 730 using SimTK::isIndexInRange; 731 SimTK_INDEXCHECK(index, getNumConnectees(), 732 "Input<T>::getValue()"); 733 734 return _connectees[index].getRef().getValue(state); 735 } 736 737 /** Get the Channel associated with this Input. This method can only be 738 used for non-list Input(s). For list Input(s), use the other overload. */ getChannel()739 const Channel& getChannel() const { 740 OPENSIM_THROW_IF(isListSocket(), 741 Exception, 742 "Input<T>::getChannel(): an index must be " 743 "provided for a list input."); 744 745 return getChannel(0); 746 } 747 748 /** Get the Channel associated with this Input. Specify the index of the 749 channel desired. */ getChannel(unsigned index)750 const Channel& getChannel(unsigned index) const { 751 OPENSIM_THROW_IF(!isConnected(), InputNotConnected, getName()); 752 using SimTK::isIndexInRange; 753 SimTK_INDEXCHECK_ALWAYS(index, getNumConnectees(), 754 "Input<T>::getChannel()"); 755 SimTK_ASSERT(index < _connectees.size(), "Internal error: " 756 "getNumConnectees() and _connectees.size() are not consistent."); 757 return _connectees[index].getRef(); 758 } 759 getAlias()760 const std::string& getAlias() const override { 761 OPENSIM_THROW_IF(isListSocket(), 762 Exception, 763 "Input<T>::getAlias(): this is a list Input; an index " 764 "must be provided."); 765 766 return getAlias(0); 767 } 768 getAlias(unsigned index)769 const std::string& getAlias(unsigned index) const override { 770 OPENSIM_THROW_IF(!isConnected(), InputNotConnected, getName()); 771 using SimTK::isIndexInRange; 772 SimTK_INDEXCHECK_ALWAYS(index, getNumConnectees(), 773 "Input<T>::getAlias()"); 774 SimTK_ASSERT(index < _aliases.size(), "Internal error: " 775 "getNumConnectees() and _aliases.size() are not consistent."); 776 return _aliases[index]; 777 } 778 setAlias(const std::string & alias)779 void setAlias(const std::string& alias) override { 780 for (unsigned i=0; i<getNumConnectees(); ++i) 781 setAlias(i, alias); 782 } 783 setAlias(unsigned index,const std::string & alias)784 void setAlias(unsigned index, const std::string& alias) override { 785 OPENSIM_THROW_IF(!isConnected(), InputNotConnected, getName()); 786 using SimTK::isIndexInRange; 787 SimTK_INDEXCHECK_ALWAYS(index, getNumConnectees(), 788 "Input<T>::setAlias()"); 789 790 const auto& connecteePath = getConnecteePath(index); 791 std::string componentPath{}; 792 std::string outputName{}; 793 std::string channelName{}; 794 std::string currAlias{}; 795 parseConnecteePath(connecteePath, 796 componentPath, 797 outputName, 798 channelName, 799 currAlias); 800 updConnecteePathProp().setValue(index, 801 composeConnecteePath(componentPath, 802 outputName, 803 channelName, 804 alias)); 805 806 _aliases[index] = alias; 807 } 808 getLabel()809 std::string getLabel() const override { 810 OPENSIM_THROW_IF(!isConnected(), 811 InputNotConnected, getName()); 812 OPENSIM_THROW_IF(isListSocket(), 813 Exception, 814 "Input<T>::getLabel(): this is a list Input; an index " 815 "must be provided."); 816 817 return getLabel(0); 818 } 819 getLabel(unsigned index)820 std::string getLabel(unsigned index) const override { 821 OPENSIM_THROW_IF(!isConnected(), 822 InputNotConnected, getName()); 823 using SimTK::isIndexInRange; 824 SimTK_INDEXCHECK_ALWAYS(index, getNumConnectees(), 825 "Input<T>::getLabel()"); 826 827 const std::string alias = getAlias(index); 828 if (!alias.empty()) 829 return alias; 830 831 return getChannel(index).getPathName(); 832 } 833 834 /** Access the values of all the channels connected to this Input as a 835 SimTK::Vector_<T>. The elements are in the same order as the channels. 836 */ getVector(const SimTK::State & state)837 SimTK::Vector_<T> getVector(const SimTK::State& state) const { 838 SimTK::Vector_<T> v(static_cast<int>(_connectees.size())); 839 for (unsigned ichan = 0u; ichan < _connectees.size(); ++ichan) { 840 v[ichan] = _connectees[ichan]->getValue(state); 841 } 842 return v; 843 } 844 845 /** Get const access to the channels connected to this input. 846 You can use this to iterate through the channels. 847 @code{.cpp} 848 for (const auto& chan : getChannels()) { 849 std::cout << chan.getValue(state) << std::endl; 850 } 851 @endcode 852 */ getChannels()853 const ChannelList& getChannels() const { 854 return _connectees; 855 } 856 857 /** Return the typename of the Output value, T, that satisfies 858 this Input<T>. No reason to return Output<T> since it is a 859 given that only an Output can satisfy an Input. */ getConnecteeTypeName()860 std::string getConnecteeTypeName() const override { 861 return SimTK::NiceTypeName<T>::namestr(); 862 } 863 864 SimTK_DOWNCAST(Input, AbstractInput); 865 866 /** For use in python/java/MATLAB bindings. */ 867 // This method exists for consistency with Object's safeDownCast. safeDownCast(AbstractInput * base)868 static Input<T>* safeDownCast(AbstractInput* base) { 869 return dynamic_cast<Input<T>*>(base); 870 } 871 872 protected: 873 /** Create an Input<T> (Socket) that can only connect to an Output<T> 874 name and stage at which it should be connected. Only Component should ever 875 construct an Input. 876 @param name name of the Output dependency. 877 @param connecteePathIndex Index of the property in the containing Component 878 that holds this Input's connectee path(s). 879 @param connectAtStage Stage at which Input should be connected. 880 @param owner The component that contains this input. */ Input(const std::string & name,const PropertyIndex & connecteePathIndex,const SimTK::Stage & connectAtStage,Component & owner)881 Input(const std::string& name, const PropertyIndex& connecteePathIndex, 882 const SimTK::Stage& connectAtStage, Component& owner) : 883 AbstractInput(name, connecteePathIndex, connectAtStage, owner) {} 884 885 /** So that Component can construct an Input. */ 886 friend Component; 887 888 private: 889 /** Register a channel to connect to. Since channels are created on the fly, 890 * we do not want to hold onto references to channels. Instead, we hold 891 * onto references of outputs. */ 892 void registerChannel(const AbstractChannel& channel, 893 const std::string& alias, bool validate = true) 894 { 895 const Channel* chanT = nullptr; 896 if (validate) { 897 chanT = dynamic_cast<const Channel*>(&channel); 898 if (!chanT) { 899 std::stringstream msg; 900 msg << "Type mismatch between Input and Output: Input '" 901 << getName() << "' of type " << getConnecteeTypeName() 902 << " cannot connect to Output (channel) '" 903 << channel.getPathName() 904 << "' of type " << channel.getTypeName() << "."; 905 OPENSIM_THROW(Exception, msg.str()); 906 } 907 } else { 908 chanT = static_cast<const Channel*>(&channel); 909 } 910 _registeredChannels.emplace_back( 911 SimTK::ReferencePtr<const Output<T>>(&chanT->getOutput()), 912 chanT->getChannelName(), alias); 913 } connectInternal(const AbstractChannel & channel,const std::string & alias)914 void connectInternal(const AbstractChannel& channel, 915 const std::string& alias) { 916 const auto* chanT = dynamic_cast<const Channel*>(&channel); 917 if (!chanT) { 918 std::stringstream msg; 919 msg << "Type mismatch between Input and Output: Input '" 920 << getName() << "' of type " << getConnecteeTypeName() 921 << " cannot connect to Output (channel) '" 922 << channel.getPathName() 923 << "' of type " << channel.getTypeName() << "."; 924 OPENSIM_THROW(Exception, msg.str()); 925 } 926 927 if (!isListSocket()) { 928 _connectees.clear(); 929 _aliases.clear(); 930 } 931 _connectees.emplace_back(chanT); 932 _aliases.push_back(alias); 933 } 934 // These are channels the user has requested that we connect to. 935 using RegisteredChannel = 936 std::tuple<SimTK::ReferencePtr<const Output<T>>, 937 std::string, std::string>; 938 SimTK::ResetOnCopy<std::vector<RegisteredChannel>> _registeredChannels; 939 SimTK::ResetOnCopy<ChannelList> _connectees; 940 // Aliases are serialized, since tools may depend on them for 941 // interpreting the connected channels. 942 SimTK::ResetOnCopy<AliasList> _aliases; 943 }; // END class Input<Y> 944 945 946 /// @name Creating Sockets to other objects for your Component 947 /// Use these macros at the top of your component class declaration, 948 /// near where you declare @ref Property properties. 949 /// @{ 950 /** Create a socket for this component's dependence on another component. 951 * You must specify the type of the component that can be connected to this 952 * socket. The comment should describe how the connected component 953 * (connectee) is used by this component. 954 * 955 * Here's an example for using this macro: 956 * @code{.cpp} 957 * #include <OpenSim/Simulation/Model/PhysicalOffsetFrame.h> 958 * class MyComponent : public Component { 959 * public: 960 * OpenSim_DECLARE_SOCKET(parent, PhysicalOffsetFrame, 961 * "To locate this component."); 962 * ... 963 * }; 964 * @endcode 965 * 966 * @note This macro requires that you have included the header that defines 967 * type `T`, as shown in the example above. We currently do not support 968 * declaring sockets if `T` is only forward-declared. 969 * 970 * @note If you use this macro in your class, then you should *NOT* implement 971 * a custom copy constructor---try to use the default one. The socket will 972 * not get copied properly if you create a custom copy constructor. 973 * We may add support for custom copy constructors with Sockets in the 974 * future. 975 * 976 * @see Component::constructSocket() 977 * @relates OpenSim::Socket */ 978 #define OpenSim_DECLARE_SOCKET(cname, T, comment) \ 979 /** @name Sockets */ \ 980 /** @{ */ \ 981 /** comment */ \ 982 /** In an XML file, you can set this Socket's connectee path */ \ 983 /** via the <b>\<socket_##cname\></b> element. */ \ 984 /** This socket was generated with the */ \ 985 /** #OpenSim_DECLARE_SOCKET macro; */ \ 986 /** see AbstractSocket for more information. */ \ 987 /** @see connectSocket_##cname##() */ \ 988 OpenSim_DOXYGEN_Q_PROPERTY(T, cname) \ 989 /** @} */ \ 990 /** @cond */ \ 991 PropertyIndex PropertyIndex_socket_##cname { \ 992 this->template constructSocket<T>(#cname, \ 993 "Path to a Component that satisfies the Socket '" \ 994 #cname "' of type " #T " (description: " comment ").") \ 995 }; \ 996 /** @endcond */ \ 997 /** @name Socket-related functions */ \ 998 /** @{ */ \ 999 /** Connect the '##cname##' Socket to an object of type T##. */ \ 1000 /** Call finalizeConnections() afterwards to update the socket's */ \ 1001 /** connectee path property. The reference to the connectee set here */ \ 1002 /** takes precedence over the connectee path property. */ \ 1003 void connectSocket_##cname(const Object& object) { \ 1004 this->updSocket(#cname).connect(object); \ 1005 } \ 1006 /** @} */ 1007 1008 // The following doxygen-like description does NOT actually appear in doxygen. 1009 /* Preferably, use the #OpenSim_DECLARE_SOCKET macro. Only use this macro 1010 * when are you unable to include the header that defines type `T`. This might 1011 * be the case if you have a circular dependency between your class and `T`. 1012 * In such cases, you must: 1013 * 1014 * -# forward-declare type `T` 1015 * -# call this macro inside the definition of your class, and 1016 * -# call #OpenSim_DEFINE_SOCKET_FD in your class's .cpp file (notice the 1017 * difference: DEFINE vs DECLARE). 1018 * 1019 * MyComponent.h: 1020 * @code{.cpp} 1021 * namespace OpenSim { 1022 * class PhysicalOffsetFrame; 1023 * class MyComponent : public Component { 1024 * OpenSim_DECLARE_CONCRETE_OBJECT(MyComponent, Component); 1025 * public: 1026 * OpenSim_DECLARE_SOCKET_FD(parent, PhysicalOffsetFrame, 1027 * "To locate this component."); 1028 * ... 1029 * }; 1030 * } 1031 * @endcode 1032 * 1033 * MyComponent.cpp: 1034 * @code{.cpp} 1035 * #include "MyComponent.h" 1036 * OpenSim_DEFINE_SOCKET_FD(parent, OpenSim::MyComponent); 1037 * ... 1038 * @endcode 1039 * 1040 * You can also look at the OpenSim::Geometry source code for an example. 1041 * 1042 * @note Do NOT forget to call OpenSim_DEFINE_SOCKET_FD in your .cpp file! 1043 * 1044 * The "FD" in the name of this macro stands for "forward-declared." 1045 * 1046 * @warning This macro is experimental and may be removed in future versions. 1047 * 1048 * 1049 * @note If you use this macro in your class, then you should *NOT* implement 1050 * a custom copy constructor---try to use the default one. The Socket will 1051 * not get copied properly if you create a custom copy constructor. 1052 * We may add support for custom copy constructors with Sockets in the 1053 * future. 1054 * 1055 * @see Component::constructSocket() 1056 * @relates OpenSim::Socket */ 1057 #define OpenSim_DECLARE_SOCKET_FD(cname, T, comment) \ 1058 /** @name Sockets */ \ 1059 /** @{ */ \ 1060 /** comment */ \ 1061 /** In an XML file, you can set this socket's connectee path */ \ 1062 /** via the <b>\<socket_##cname\></b> element. */ \ 1063 /** See AbstractSocket for more information. */ \ 1064 /** @see connectsocket_##cname##() */ \ 1065 OpenSim_DOXYGEN_Q_PROPERTY(T, cname) \ 1066 /** @} */ \ 1067 /** @cond */ \ 1068 PropertyIndex PropertyIndex_socket_##cname { \ 1069 constructSocket_##cname() \ 1070 }; \ 1071 /* Declare the method used in the in-class member initializer. */ \ 1072 /* This method will be defined by OpenSim_DEFINE_SOCKET_FD. */ \ 1073 PropertyIndex constructSocket_##cname(); \ 1074 /* Remember the provided type so we can use it in the DEFINE macro. */ \ 1075 typedef T _socket_##cname##_type; \ 1076 /** @endcond */ \ 1077 /** @name Socket-related functions */ \ 1078 /** @{ */ \ 1079 /** Connect the '##cname##' Socket to an object of type T##. */ \ 1080 /** Call finalizeConnections() afterwards to update the socket's */ \ 1081 /** connectee path property. The reference to the connectee set here */ \ 1082 /** takes precedence over the connectee path property. */ \ 1083 void connectSocket_##cname(const Object& object) { \ 1084 this->updSocket(#cname).connect(object); \ 1085 } \ 1086 /** @} */ 1087 1088 // The following doxygen-like description does NOT actually appear in doxygen. 1089 /* When specifying a Socket to a forward-declared type (using 1090 * OpenSim_DECLARE_SOCKET_FD in the class definition), you must call this 1091 * macro in your .cpp file. The arguments are the name of the socket (the 1092 * same one provided to OpenSim_DECLARE_SOCKET_FD) and the class in which 1093 * the socket exists. See #OpenSim_DECLARE_SOCKET_FD for an example. 1094 * 1095 * @warning This macro is experimental and may be removed in future versions. 1096 * 1097 * @see #OpenSim_DECLARE_SOCKET_FD 1098 * @relates OpenSim::Socket */ 1099 // This macro defines the method that the in-class member initializer calls 1100 // to construct the Socket. The reason why this must be in the .cpp file is 1101 // that putting the template member function `template <typename T> 1102 // Component::constructSocket` in the header requires that `T` is not an 1103 // incomplete type (specifically, when compiling cpp files for classes OTHER 1104 // than `MyComponent` but that include MyComponent.h). OpenSim::Geometry is an 1105 // example of this scenario. 1106 #define OpenSim_DEFINE_SOCKET_FD(cname, Class) \ 1107 PropertyIndex Class::constructSocket_##cname() { \ 1108 using T = _socket_##cname##_type; \ 1109 std::string typeStr = T::getClassName(); \ 1110 return this->template constructSocket<T>(#cname, \ 1111 "Path to a Component that satisfies the Socket '" \ 1112 #cname "' of type " + typeStr + "."); \ 1113 } 1114 /// @} 1115 1116 /// @name Creating Inputs for your Component 1117 /// Use these macros at the top of your component class declaration, 1118 /// near where you declare @ref Property properties. 1119 /// @{ 1120 /** Create a socket for this component's dependence on an output signal from 1121 * another component. It is a placeholder for an Output that can be connected 1122 * to it. An output must have the same type T as an input to be connected 1123 * to it. You must also specify the stage at which you require this input 1124 * quantity. The comment should describe how the input quantity is used. 1125 * 1126 * An Input declared with this macro can connect to only one Output. 1127 * 1128 * Here's an example for using this macro: 1129 * @code{.cpp} 1130 * class MyComponent : public Component { 1131 * public: 1132 * OpenSim_DECLARE_INPUT(emg, double, SimTK::Stage::Velocity, "For validation."); 1133 * ... 1134 * }; 1135 * @endcode 1136 * 1137 * @note If you use this macro in your class, then you should *NOT* implement 1138 * a custom copy constructor---try to use the default one. The Input will 1139 * not get copied properly if you create a custom copy constructor. 1140 * We may add support for custom copy constructors with Inputs in the future. 1141 * 1142 * @see Component::constructInput() 1143 * @relates OpenSim::Input */ 1144 #define OpenSim_DECLARE_INPUT(iname, T, istage, comment) \ 1145 /** @name Inputs */ \ 1146 /** @{ */ \ 1147 /** comment */ \ 1148 /** This input is needed at stage istage##. */ \ 1149 /** In an XML file, you can set this Input's connectee path */ \ 1150 /** via the <b>\<input_##iname\></b> element. */ \ 1151 /** The syntax for a connectee path is */ \ 1152 /** `<path/to/component>|<output_name>[:<channel_name>][(<alias>)]`. */ \ 1153 /** This input was generated with the */ \ 1154 /** #OpenSim_DECLARE_INPUT macro; */ \ 1155 /** see AbstractInput for more information. */ \ 1156 /** @see connectInput_##iname##() */ \ 1157 OpenSim_DOXYGEN_Q_PROPERTY(T, iname) \ 1158 /** @} */ \ 1159 /** @cond */ \ 1160 PropertyIndex PropertyIndex_input_##iname { \ 1161 this->template constructInput<T>(#iname, false, \ 1162 "Path to an output (channel) to satisfy the one-value Input '" \ 1163 #iname "' of type " #T " (description: " comment ").", istage) \ 1164 }; \ 1165 /** @endcond */ \ 1166 /** @name Input-related functions */ \ 1167 /** @{ */ \ 1168 /** Connect this Input to a single-valued (single-channel) Output. */ \ 1169 /** The output must be of type T##. */ \ 1170 /** This input is single-valued and thus cannot connect to a */ \ 1171 /** list output. */ \ 1172 /** You can optionally provide an alias that will be used by this */ \ 1173 /** component to refer to the output. */ \ 1174 /** Call finalizeConnections() afterwards to update the input's */ \ 1175 /** connectee path property. The reference to the output set here */ \ 1176 /** takes precedence over the connectee path property. */ \ 1177 void connectInput_##iname(const AbstractOutput& output, \ 1178 const std::string& alias = "") { \ 1179 updInput(#iname).connect(output, alias); \ 1180 } \ 1181 /** Connect this Input to an output channel of type T##. */ \ 1182 /** You can optionally provide an alias that will be used by this */ \ 1183 /** component to refer to the channel. */ \ 1184 /** Call finalizeConnections() afterwards to update the input's */ \ 1185 /** connectee path property. The reference to the channel set here */ \ 1186 /** takes precedence over the connectee path property. */ \ 1187 void connectInput_##iname(const AbstractChannel& channel, \ 1188 const std::string& alias = "") { \ 1189 updInput(#iname).connect(channel, alias); \ 1190 } \ 1191 /** @} */ 1192 1193 // TODO create new macros to handle custom copy constructors: with 1194 // constructInput_() methods, etc. NOTE: constructProperty_() must be called 1195 // first within these macros, b/c the connectee path property must exist before 1196 // the Input etc is constructed. 1197 1198 1199 /** Create a list input, which can connect to more than one Channel. This 1200 * makes sense for components like reporters that can handle a flexible 1201 * number of input values. 1202 * 1203 * @note If you use this macro in your class, then you should *NOT* implement 1204 * a custom copy constructor---try to use the default one. The Input will 1205 * not get copied properly if you create a custom copy constructor. 1206 * We may add support for custom copy constructors with Inputs in the future. 1207 * 1208 * @see Component::constructInput() 1209 * @relates OpenSim::Input */ 1210 #define OpenSim_DECLARE_LIST_INPUT(iname, T, istage, comment) \ 1211 /** @name Inputs (list) */ \ 1212 /** @{ */ \ 1213 /** comment */ \ 1214 /** This input can connect to multiple outputs, all of which are */ \ 1215 /** needed at stage istage##. */ \ 1216 /** In an XML file, you can set this Input's connectee path */ \ 1217 /** via the <b>\<input_##iname\></b> element. */ \ 1218 /** The syntax for a connectee path is */ \ 1219 /** `<path/to/component>|<output_name>[:<channel_name>][(<alias>)]`. */ \ 1220 /** This input was generated with the */ \ 1221 /** #OpenSim_DECLARE_LIST_INPUT macro; */ \ 1222 /** see AbstractInput for more information. */ \ 1223 /** @see connectInput_##iname##() */ \ 1224 OpenSim_DOXYGEN_Q_PROPERTY(T, iname) \ 1225 /** @} */ \ 1226 /** @cond */ \ 1227 PropertyIndex PropertyIndex_input_##iname { \ 1228 this->template constructInput<T>(#iname, true, \ 1229 "Paths to outputs (channels) to satisfy the list Input '" \ 1230 #iname "' of type " #T " (description: " comment "). " \ 1231 "To specify multiple paths, put spaces between them.", istage) \ 1232 }; \ 1233 /** @endcond */ \ 1234 /** @name Input-related functions */ \ 1235 /** @{ */ \ 1236 /** Connect this Input to a single-valued or list Output. */ \ 1237 /** The output must be of type T##. */ \ 1238 /** If the output is a list output, this connects to all of the */ \ 1239 /** channels of the output. */ \ 1240 /** You can optionally provide an alias that will be used by this */ \ 1241 /** component to refer to the output; the alias will be used for all */ \ 1242 /** channels of the output. */ \ 1243 /** Call finalizeConnections() afterwards to update the input's */ \ 1244 /** connectee path property. The reference to the output set here */ \ 1245 /** takes precedence over the connectee path property. */ \ 1246 void connectInput_##iname(const AbstractOutput& output, \ 1247 const std::string& alias = "") { \ 1248 updInput(#iname).connect(output, alias); \ 1249 } \ 1250 /** Connect this Input to an output channel of type T##. */ \ 1251 /** You can optionally provide an alias that will be used by this */ \ 1252 /** component to refer to the channel. */ \ 1253 /** Call finalizeConnections() afterwards to update the input's */ \ 1254 /** connectee path property. The reference to the channel set here */ \ 1255 /** takes precedence over the connectee path property. */ \ 1256 void connectInput_##iname(const AbstractChannel& channel, \ 1257 const std::string& alias = "") { \ 1258 updInput(#iname).connect(channel, alias); \ 1259 } \ 1260 /** @} */ 1261 /// @} 1262 1263 } // end of namespace OpenSim 1264 1265 #endif // OPENSIM_COMPONENT_SOCKET_H_ 1266