FlexScript Interfaces

Description

Each FlexScript interface is defined by a special object in the engine. That object stores the list of methods, operators, and properties for the interface. To add a new interface, all we have to do is create a new interface object, and make sure it goes in the list that the compiler and code editor read.

In order to create a new interface from a module, all you have to do is override a method called bindInterface(), and then let the engine know that it needs to call bindInterface() for that object. These two steps are slightly different for ObjectDataType objects and SimpleDataType objects, so I'll cover both. There is also a way to make an interface that wraps a pointer (like the Table interface). I'll explain a little about that as well. In all three cases, you will need to implement/overload a bindInterface() method. I'll discuss how to do that last.

ObjectDataType

In the C++ class definition, be sure to add the following method:

virtual void bindInterface() override

You will also need to implement that method, and that will be discussed later. To make sure the engine calls your object's bindInterface() method, you need to add a node to the object's tree definition. This node should be a subnode of behavior/eventfunctions. Its name must be flexScriptInterface, and it must have text data. The text data contains the FlexScript name of the class. The engine searches the library for classes with this node, and calls their bindInterface() method.

SimpleDataType

Like the ObjectDataType, add the following method to your class's C++ definition:

virtual void bindInterface() override

We'll talk about implementation later. Now we need to tell the engine that it should make an interface object for this class. The engine looks at the node MAIN:/project/exec/globals/sdtinterfaces for interfaces to create. Your module just needs to add a node to this list with the following structure:

  • The name of the node is the getClassFactory() name of your class.
  • The text of the node is the interface name.
  • There is one subnode:
    • The name of the subnode is isCoupling.
    • The number value of the subnode reflects whether your class inherits SimpleDataType (0) or CouplingDataType (1)

Pointer-Based Helper Classes

There are some classes which are not based on an SDT or an ODT, like the Conveyor.SlugBuilder class. This interface is defined by the following object:

class SlugBuilderProperty
{
protected:
  TreeNode* conveyorNode = nullptr
  const SlugBuilder& getSlugBuilder()
public:
  // methods and properties
  // ...
  static void bindInterface()
}
		

The important thing to remember is that this class has a static void method called bindInterface(), which takes no parameters. If you class has that, you can make a FlexScript interface for that class.

To get the engine to create the FlexScript interface object, you have to call the bindClass() method within another object's bindInterface() method. The Conveyor's bindInterface() method contains this code:

bindClassByName<SlugBuilderProperty>("Conveyor.SlugBuilder")

This method tells the compiler to create a FlexScript class called Conveyor.SlugBuilder, and to give it the interface defined by the C++ SlugBuilderProperty class. But more on that in the next section.

bindInterface()

Here's the bindInterface() code from the Conveyor class:

void Conveyor::bindInterface()
{
	bindParentClass("Object")
	bindDocumentationXMLPath("modules\\Conveyor\\help\\FlexScriptAPI\\Conveyor.xml")
	bindTypedPropertyByName<Motor>("motor", "Object", force_cast<void*>(&Conveyor::__getMotor), nullptr)
	bindMethod(estimateConveyTime, Conveyor, "double estimateConveyTime(Object origin, double n_a, Object dest, double n_a, double itemLength, int flags = 0)", BIND_METHOD_STATIC)
	bindMethod(sendItem, Conveyor, "void sendItem(Object item, Object dest)", BIND_METHOD_STATIC)
	bindTypedProperty(targetSpeed, double, &Conveyor::__getTargetSpeed, &Conveyor::__setTargetSpeed)
	bindTypedProperty(currentSpeed, double, &Conveyor::__getCurrentSpeed, nullptr)
	bindTypedProperty(defaultSpeed, double, &Conveyor::__getDefaultSpeed, nullptr)
	bindTypedProperty(acceleration, double, &Conveyor::__getAcceleration, nullptr)
	bindTypedProperty(deceleration, double, &Conveyor::__getDeceleration, nullptr)
	bindTypedProperty(width, double, &Conveyor::__getWidth, nullptr)
	bindTypedProperty(length, double, &Conveyor::__getSimLength, nullptr)
	bindNodeListArrayClassByName<ConveyorItems>("Conveyor.Items", "Conveyor.Item", true)
	bindTypedPropertyByName<ConveyorItems>("itemData", "Conveyor.Items", force_cast<void*>(&Conveyor::__getItemData), nullptr)
	bindClassByName<SlugBuilderProperty>("Conveyor.SlugBuilder")
	bindTypedPropertyByName<SlugBuilderProperty>("slugBuilder", "Conveyor.SlugBuilder", force_cast<void*>(&Conveyor::__getSlugBuilder), nullptr)
}
		

Here's a brief description of the bind calls in this code:

  • bindParentClass - This is how FlexScript interfaces can inherit other FlexScript classes. This give the Conveyor class access to Object methods and properties.
  • bindDocumentationXMLPath - This connects a FlexScript interface to specific help documentation.
  • bindTypedPropertyByName - This method adds a property to the interface. You provide the property name, the FlexScript name of the type it returns, and pointers to a getter (and, optionally, a setter) method. For simple properties, you can use the bindTypedProperty macro.
  • bindMethodByName - This method adds a method to the interface. Unfortunately, intellisense almost always gives this a syntax error, but it still builds. If you pass the static flag, be sure that the method you pass is in fact a static method.
    • The method signature should use only type names available in FlexScript. If the engine can't parse this expression, FlexSim will crash on start up.
  • bindNodeListArrayClassByName - This method creates a new class interface, based on a node list array. If you have a custom NodeListArray class, you can use this method to create an interface for this class.
  • bindClassByName - This method creates a new class interface, based on an arbitrary class. That class must have the static void bindInterface() method.
    • By default, classes bound with this method (and bindNodeListArrayByName) cannot be declared. You can only create a local variable for these classes using the var type. You can pass a flag that makes the type user accessible, meaning you can delcare a local variable of that type directly.

Here's the code for the Conveyor.SlugBuilder's bindInterface:

void Conveyor::SlugBuilderProperty::bindInterface()
{
	XS
	bindCopyConstructor(&SlugBuilderProperty::construct)
	bindCopyAssigner(&SlugBuilderProperty::operator=)
	bindTypedProperty(conveyor, Conveyor, &SlugBuilderProperty::__getConveyor, nullptr)
	bindTypedProperty(isEnabled, int, &SlugBuilderProperty::__getEnabled, &SlugBuilderProperty::__setEnabled)
	bindTypedProperty(isClear, int, &SlugBuilderProperty::__getIsClear, nullptr)
	bindTypedProperty(state, int, &SlugBuilderProperty::__getState, nullptr)
	bindMethodByName<decltype(&SlugBuilderProperty::makeReady)>("makeReady", &SlugBuilderProperty::makeReady, "void makeReady()")
	bindMethodByName<decltype(&SlugBuilderProperty::release)>("release", &SlugBuilderProperty::release, "void release()")
	// bindMethodByName<decltype(&SlugBuilderProperty::addItem)>("addItem", &SlugBuilderProperty::addItem, "void addItem(Object item)")
	bindDocumentationXMLPath("modules\\Conveyor\\help\\FlexScriptAPI\\Conveyor.SlugBuilder.xml")
	XE
}
		

Here's a description of the new bind calls in this code:

  • bindCopyConstructor - This is only needed for non-SDT/ODT classes. It allows you to copy the pointers you need from one instance of the class to the other (conveyorNode = other.conveyorNode).
  • bindCopyAssigner - This is only needed for non-SDT/ODT classes. It allows you to assign this variable to a var type in FlexSim.

There are a couple other bind___ methods that can be used in bindInterface:

  • bindBracketOperator - allows the FlexScript interface to use [] syntax.
  • bindCastOperator - allows this object to be cast to other types (usually TreeNode*)

As a final note, you should know that the engine binds classes in two passes. The first pass creates empty type shell for all the outer types (SDT/ODT). That makes the types available for use in method calls. If you are using bindClass or bindNodeListArrayClass, then you should bind those classes before binding properties and methods that deal with that class.

Reference

bindMethod

Use to make member methods available in FlexScript

bindMethod(mehod, className, ...)
template <class FunctionPtrType>
		bindMethodByName(const char* name, FunctionPtrType memberPtr, const char* signature, int flags = 0)

The flags parameter can either be 0, or BIND_METHOD_STATIC. Only use the static flag if the C++ function is a static function. With this flag, the function will be accessed via the class itself, rather than an instance (e.g. Color.random())

bindOperator

Use to make object operators (+, -, etc.) available in FlexScript

bindOperator(method, className, signature)
template <class FunctionPtrType>
		bindOperatorByName(const char* opName, FunctionPtrType memberPtr, const char* signature)

These are the valid operator names:

Add: "+"
Subtract: "-"
Multiply: "*"
Divide: "/"
Mod: "%"
Equal: "=="
NotEqual: "!="
GreaterThan: ">"
GreaterThanOrEqual: ">="
LessThan: "<"
LessThanOrEqual: "<="
LeftBitShift: "<<"
RightBitShift: ">>"
BitwiseXOr: "^"
BitwiseOr: "|"
BitwiseAnd: "&"
LogicalOr: "||"
LogicalAnd: "&&"
Brackets: "[]"
Assignment: "="
PlusAssignment: "+="
MinusAssignment: "-="
MultiplyAssignment: "*="
DivideAssignment: "/="
BitwiseOrAssignment: "|="
BitwiseAndAssignment: "&="
Negate: "unary -"
LogicalNot: "!"
BitwiseNot: "~"
Cast: "cast"
Increment: "--"
Decrement: "++"
Construct: "construct"
Dereference: "dereference"
		

The signature for these methods can omit the function name. Simply provide a return type and arguments.

bindTypedProperty

Use to make object properties available in FlexScript

bindTypedProperty(method, className, signature, ...)
template <class Type>
		bindOperatorByName<Type>(const char* propName, const char* typeName, void* getter, void* setter, const char* version)

The property name must be a valid identifier name (no spaces, begin with letter, etc.). The typeName must be the FlexScript name of the desired type. You only need to use the version string if the type with this property was previously available in FlexSim, and had dynamic properties. For an example of all above rules, here is how the token's name property is bound:

bindTypedPropertyByName<const char*>("name", "char*", 
			force_cast<void*>(&TokenHelper::getName), force_cast<void*>(&TokenHelper::setName), "17.1.0");

However, for type whose class name matches the FlexScript class name, you can use the bindTypedProperty() macro:

bindTypedProperty(activity, treenode, &TokenHelper::getCurActivity, nullptr);

Signatures

Any bind*() method requiring a const char* signature argument is asking you as the user to provide a FlexScript signature. A FlexScript signature follows these rules:

  • Only FlexScript types (the built-in types, or something that calls bindInterface()) can be found in signatures.
  • No argument can be marked const
  • No argument can be passed by pointer, except char*
  • Arguments passed as a reference must be marked

		// Good
		void func1(const Variant& value) // => "void func1(Variant& value)"
		Table getTable(TreeNode* source) // => "Table getTable(treenode source)"
		const char* getText() // => "char* getText()"

		// Bad
		// void func1(const Variant& value) => "void func1(const Variant& value)"
		// Table getTable(TreeNode* source) => "Table getTable(TreeNode* source)"
			

If you implement bindInterface(), you may run in to the issue that FlexSim will not start. This can be caused by a bad signature. Check types, const, and references. To verify that bindInterface() is the problem, try commenting it out, building the dll, and opening FlexSim again.