FlexSimObject
Description
Inherits ObjectDataType
FlexSimObject is meant to be the de-facto start object that objects inherit from. If you want any other Flexsim objects to be able to automatically interact with your object through standard mechanisms like messaging, stop/resume, loading/unloading items to or from that object, creating connections, etc., then your object should at least inherit from FlexSimObject. FlexSimObject is primarily a "virtual" object that just sets up interfaces that sub-classes then implement. It defines some very basic default functionality for these interfaces so that sub-classes can just use "vanilla" logic, but the main intent of the FlexSimObject is to allow sub-classes to override those interfaces.
Direct Child Classes
Class | Description |
---|---|
FixedResource | |
Dispatcher | |
Navigator | |
NetworkNode | |
VisualTool | |
Recorder |
Implementation
Note: Many of these functions are also called utility functions, which are not meant to be re-implemented by child classes. However, they need to be virtual for other reasons. More information on specific utility methods can be found in the documentation for the child classes, such as TaskExecuter.
Method |
---|
double onReceive(treenode item, int port) |
double onSend(treenode item, int port) |
double onTimerEvent(treenode involved, int code, char *strdata) |
double onCreate(double dropx, double dropy, double dropz, int iscopy = 0) |
double onDestroy(double xloc, double yloc, double zloc) |
double onDraw(treenode view) |
double onDrawPlanar(treenode view) |
double onPreDraw(treenode view) |
double onOutOpen(int port) |
double onInOpen(int port) |
double onClick(treenode view, int code) |
double onKeyedClick(treenode view, int code, char k) |
double onMessage(treenode fromobject, double par1, double par2, double par3) |
double onReset(void) |
double onDrag(treenode view) |
double onRunWarm(void) |
Other Virtual Methods
Method | Description |
---|---|
double stopObject(int stopstate) | See Stopping/Resuming |
double resumeObject(void) | See Stopping/Resuming |
double dragConnection(treenode toobject, char characterpressed, unsigned int classtype) | See Drag-Connections/Class Type |
double getPickOffset(treenode involvedobj, treenode toobject, double* returnarray) | See Pick/Place Offset |
double getPlaceOffset(treenode involvedobj, treenode fromobject, double* returnarray) | See Pick/Place Offset |
double updateLocations() | See updateLocations() and onPreDraw() |
unsigned int getClassType(void) | See Drag-Connections/Class Type |
double saveState(void) | See Save/Load State |
double loadState(void) | See Save/Load State |
double rotateAroundAxis(double angle, double x, double y) | See Various User Interaction Logic |
double fliparoundAxis(int axis, double x , double y) | See Various User Interaction Logic |
double copyVariables(treenode otherobject) | See Various User Interaction Logic |
Event Handling
Like the ObjectDataType, the FlexSimObject class can catch events using attribute nodes. A FlexSimObject, however, automatically maps the event attribute nodes to its virtual methods. Consequently, sub-classes only need to override the virtual methods to tap into standard Flexsim events.
To understand this sequence in more detail, look in the tree at MAIN:/project/library/FlexSimObject>behaviour. In the eventfunctions sub-node you'll find several attributes that correspond to various events associated with that object, like OnReset, OnTimerEvent, OnMessage, etc. Now look in the cppfunctions sub-node of behaviour. There you'll find C++ - style function declaration nodes. These declare C++ virtual class methods for the FlexSimObject. Note that many of the methods have essentially the same name as the attributes found in eventfunctions. Now go back to the eventfunctions attributes and analyze the code in the various event attributes. Notice that for the most part, the code in these attributes just gets a reference to the FlexSimObject associated with the current object (c), and simply calls the associated method on the FlexSimObject. Look back into the cppfunctions node, and you'll also see that usually the FlexSimObject doesn't do anything in those methods. The intention is that sub-classes can implement logic associated with these events by overriding these methods. This is what is meant by this "mapping." The FlexSimObject automatically catches events that are dispatched via the engine's dispatching mechanism, and maps them to class method calls, so FlexSimObject sub-classes merely need to override the associated class method in order to define custom event logic, instead of having to put event attributes on the class and then define global C++ code in those attributes.
Messaging
FlexSimObject provides an automatic mechanism for user messaging. In the initial design of Flexsim we decided that the messaging system, which is triggered with the sendmessage() and senddelayedmessage() commands, should primarily be pushed to the user, so all messaging with sendmessage will just fire a trigger that the user can define on each object. To do this, the FlexSimObject has a variable called messagetrigger. This node contains code for the end user to define, so the end user will call sendmessage() when he wants to execute some common functionality, then will define the code in the message trigger variable. The FlexSimObject catches the message event through its OnMessage attribute, dispatches it to its OnMessage() method, and the OnMessage() method just calls nodefunction() on that message trigger node.
While the sendmessage() command is implemented in the engine, senddelayedmessage() is actually implemented at the content level, and FlexSimObject is key in that implementation. senddelayedmessage() calls createevent() on the object with EVENT_MESSAGE as the event code. Then in the FlexSimObject's OnTimerEvent attribute, it checks if it's a message event, and if so, calls sendmessage() to itself, which triggers the sequence mentioned above.
Note that this architecture doesn't preclude a sub-class from actually implementing it's own onMessage() method that override's FlexSimObject's. You could define your own sub-class logic for certain types of messages and then call FlexSimObject::onMessage() for all the rest. However, this is more the exception than the rule, since there are avenues other than sendmessage() for giving the user access to your sub-class' custom logic, i.e. by adding commands to the command list. The one scenario where I've found it useful to override the FlexSimObject's onMessage() is for task sequences. If your class builds task sequences to be executed, it may be useful to execute your class' code at some point in the task sequence, or do a call-sub-task that is determined by class code. The most direct way to do this is to use the TASKTYPE_SENDMESSAGE (or TASKTYPE_CALLSUBTASKS) command, and then override the FlexSimObject's OnMessage() method. Usually you'll want to use message codes (msgparam(1) is typically the "message code" everyone uses) that won't collide with message codes the user might use, so either use really big numbers or negative numbers.
Drawing
FlexSimObject also provides an automatic mechanism for user-defined drawing. We found that for essentially all of our library objects, we wanted the end user the be able to customize draw, so we implemented this on the FlexSimObject. Essentially FlexsimObjects have a drawtrigger variable where the end user can define code that will be fired as part of the draw sequence. Flexsim catches the OnDraw event attribute, calls the end user's drawtrigger variable, and if that variable returns 0 or is not implemented, FlexSimObject will call the OnDraw() method. This will allow the end user to actually override any class-defined draw code if he wants to.
Note: If an object has a "shape" attribute node, and that node has a path to a .3ds file, the .3ds shape will be drawn as well as the custom draw code.
Stopping/Resuming
FlexSimObject sets up mechanisms by which objects can be stopped or resumed. In our design, we decided there should be some common mechanism by which any object can be stopped by another object, and then later be allowed to resume its operations. This is done with the stopobject() and resumeobject() commands. Stop requests accumulate, so that if stopobject() is called multiple times, the object will not actually resume until the same number of resumeobject() calls have been made. FlexSimObject uses the variables nrofstops, statebeforestop, and timeoflaststop to track stop requests. FlexSimObject also declares the virtual methods stopObject() and resumeObject(). These methods should be overridden in sub-classes to define custom logic for how to stop/resume. FlexSimObject defines "vanilla" implementations of stopObject() and resumeObject(), which are simply to delay all pending events associated with the object until it has been resumed, and then to restore those events but delay them by the time that the object was down. Sub-classes of FlexSimObject should override stopObject() and resumeObject() if stopping and resuming requires more than that.
An overridden stopObject() should:
- Increment v_nrofstops (v_nrofstops++)
- If v_nrofstops is 1, store the time() in v_timeoflaststop, the current state in v_statebeforestop, and execute logic to make sure that the object is stopped
- Set the state of the object to the requested state
An overridden resumeObject() should:
- Decrement v_nrofstops (v_nrofstops--)
- If v_nrofstops is 0, execute logic to resume the object
- Set the state of the object to v_statebeforestop
An overridden stopObject()/resumeObject() could also just call FlexSimObject::stopObject()/resumeObject() to do all this automatically, and then do extra stuff.
updateLocations() and onPreDraw()
The FlexSimObject class implements a virtual method called updateLocations(). This method is meant to allow the object to update its location using the current simulation time. For example, a Conveyor's updateLocations() should go through every flowitem on the conveyor and update the location of the flowitem. The flowitem location will depend on factors like belt velocity and accumulation. All this logic should be defined in the updateLocations() function.
There are two times when updateLocations() is called:
- As part of the object's onPreDraw() event function
- As as result of the updatelocations() application command
The onPreDraw() event is fired only if the object is visible in a view. Just before the view is refreshed, the onPreDraw() event is fired for every object in the view. Usually, onPreDraw() just calls updateLocations() so that the view will update properly. Unless you need to do something specific with the view that contains the object, you probably won't need to re-implement the onPreDraw() method.
Be sure to put all location logic in updateLocations() and not in onPreDraw(). If there is no view, onPreDraw() will not be called. Even if there is a view, the simulation time can differ significantly from the time that a view was updated. To ensure that your object's location will be tied precisely to the simulation time, simply place all location logic in updateLocations().
That being said, updateLocations() is called quite often, and can be very computationally expensive, especially for navigator classes. It may be wise to keep a variable that stores the simulation time when updateLocations() is called, and then check to make sure that the simulation time has increased before executing the logic.
Resetting
The FlexSimObject class provides the following reset-related features:
- A virtual onReset() method
- A non-virtual resetVariables() method
- A treenode called node_v_reset
Every class that extends FlexSimObject should overwrite the onReset() method and possess its own non-virtual resetVariables() method. Usually, the onReset() method calls the resetVariables() method where the reset logic is implemented. Each class can then simply reset its own variables and then call the resetVariables() method of its parent. This chain continues until FlexSimObject, which executes the reset trigger node node_v_reset. This node is meant to be defined by the end user. The diagram below shows how this occurs.
Save/Load State
FlexSimObject also defines a set of virtual methods for saving and loading state. Save/Load state is specifically for when you want to save the state of the model in the middle of a simulation run. If sub-classes use data during the simulation that is not stored properly in the tree, then they should implement the saveState() method to stuff their non-conforming data into the tree, and loadState() to pull that data back out of the tree.
Drag-Connections/Class Type
FlexSimObject defines a virtual dragConnection() method. This method is fired when a user holds a key down and click-drags between two objects. dragConnection() is called on the FROM object of the dragging. Before it is called, the system gets the class type of the TO object, using the FlexSimObject's virtual getclasstype() method. This returns a bitwise mask of the different classes that the object is a member of, i.e. CLASSTYPE_FLEXSIMOBJECT, CLASSTYPE_FIXEDRESOURCE, etc. dragConnection() then should determine what type of connection to create. Note that you can also just use the isclasstype() command, and just pass in the actual name of the class.
Also, if you want to handle dragConnection even if you are the TO object of the drag connection, then you can override getclasstype() and return CLASSTYPE_WANTCONNECTLOGIC as part of the bitwise mask returned, and as long as the FROM object doesn't not also return CLASSTYPE_WANTCONNECTLOGIC, then dragConnection() will be called on your object and not FROM object.
Pick/Place Offset
FlexSimObject defines virtual methods for defining pick and place offsets, called getPickOffset() and getPlaceOffset(). getPickOffset() is called on the object when another object is about to load an item from the object, and wants to know where to offset to to pick up that item. getPlaceOffset() is called on the object when another object is about to unload an item to the object, and wants to know where to offset to drop off the item. For more information on this, refer to the TaskExecuter's page in the User Manual, under the Offset Travel section. Also refer to the TASKTYPE_PICKOFFSET and TASKTYPE_PLACEOFFSET documentation.
Collision Detection
The FlexSimObject class implements some basic collision detection logic. It can create collision spheres, which are then checked against all other collision spheres in the model at specified intervals. The check for collisions happens in the checkCollisions() method. The loop that calls the checkCollisions() method must be implemented by the sub-class; the TaskExecuter class has the loop logic ready to use.
Various User Interation Logic
FlexSimObject also implements various virtual methods for user-interaction logic, like copying variables from another object, or flipping the object around an axis. These actions are available in View->Modeling Utilities in FlexSim. The behavior for these events can be defined by any subclass of FlexSimObject.