FlexScriptの拡張

概要と主要な概念

モジュールSDKを使用すると、コマンドやFlexScriptクラスを追加して、FlexScript言語を拡張できます。たとえば、AGVモジュールはcpdistance()cpnumconnections()などのコマンドを追加します。また、AGVAGV.TrailerなどのFlexScriptクラスも追加します。

新しいFlexScriptクラスを追加するには、モジュールにC++ dllを追加する必要があります。FlexScriptクラスは、通常C++クラスに基づいています。同様に、新しいコマンドはC++で定義でき、FlexScriptコマンドはそのC++定義を呼び出すことができます。また、新しいコマンドは、C++ dllを使用せずにFlexScriptだけで定義することもできます。

このトピックは、モジュールが作成済みであると仮定します。モジュールの作成と、モジュールへのC++ dllの追加の詳細については、「クイックスタート」のトピックを参照してください。

新しいコマンドを追加する

モジュールは新しいコマンドをFlexScriptに追加できます。ユーザーコマンドと異なり、モジュールによって追加されたコマンドは、すべてのモデルで使用できます。

新しいコマンドを追加するには、次の手順に従います。

  1. FlexSimでツリービューを開きます。
  2. 次のノードに移動します:MAIN:/project/exec/usercommandlist
  3. 使用するモジュールの名前を持つサブノードをまだ追加していない場合は、そのサブノードを追加します。このモジュールによって追加するコマンドはすべて、このサブノードの下に追加する必要があります。
  4. モジュールにモジュールのサブノードをまだ追加していない場合は、「追加」としてモジュールに追加します。
  5. モジュールのサブノードの中に、コマンドのFlexScript名を持つサブノードを追加します。このノードのフルパスは次のようになります。MAIN:/project/exec/usercommandlist/MyModule/mycommand
  6. サブノードは次のように追加します。
    ランク 提案された名前 データタイプ 注意
    1 code 文字列 FlexScriptまたはDLLのいずれかです。FlexScriptの場合、値はコマンドの定義です。DLLの場合、値はdll内の関数を指す、特別なフォーマットのテキストです。この表の後で説明します。
    2 description 文字列 値はコマンドの詳細です。ドキュメントを生成するために使用されます。
    3 parameters 文字列 値はFlexScriptコマンドのパラメータリストです。このパラメータリストは、コマンドが正しく呼び出されているかどうかを判定するために、FlexScriptパーサーによって使用されます。詳細については、「ユーザーコマンド」を参照してください。
    4 example 文字列 値は、このコマンドを使用するFlexScriptの例です。ドキュメントを生成するために使用されます。
    5 returntype 文字列 値はFlexScriptコマンドの戻り値のタイプです。詳細については、「ユーザーコマンド」を参照してください。
    6 このノードには名前もデータもありません。次のノードを追加する場合にのみ必要です。
    7 shortdesc 文字列 このノードはオプションです。値はコマンドの簡単な説明です。コマンドのドキュメントを生成するときに使用されます。スクリプトウィンドウでコマンドの上にカーソルを移動したときのツールチップにも表示されます。

C++を使用してコマンドを定義する

どのノードも、評価されるときにdll内の関数に呼び出しとパラメータを転送し、戻り値のタイプを取得するように設定できます。そのノードがコマンドの最初のサブノードを定義した場合、そのコマンドはdllの関数を呼び出します。

ノードがC++関数を呼び出すように設定するには、ノードを右クリックして[ビルド]をポイントし、[ノードをDLLとしてトグル]を選択します。さらに、必ずテキストデータを追加してください。テキスト値は次のように特別なフォーマットの文字列です。

dll:"module:[MyModule]" func:"[MyCommand]"

このC++ノードの値は上記の値と一致する必要があります。ただし、[MyModule]は実際のモジュールの名前に、[MyCommand]はコマンドの名前に置き換えてください。

C++側で、次の特定のシグネチャを持つコマンドを追加します。

__declspec(dllexport) Variant command_name(FLEXSIMINTERFACE)

コマンド名は任意ですが、他の要素はすべて必須です。

たとえば、FlexScriptDemoというモジュールに次の関数が定義されていると仮定します。

double average(double x, double y) {
    return (x + y) / 2.0;
}

この関数を呼び出し可能にするには、正しいシグネチャを持つラッパー関数を定義し、その関数で元の関数を呼び出します。

__declspec(dllexport) Variant cmd_average(FLEXSIMINTERFACE) {
// param(n) is a Variant. You can cast a Variant to a double,
// and a double can cast to a Variant.
    return average(param(1), param(2)); 
}

モジュールはFlexScriptDemoサブノードを追加し、次のようにaverageコマンドを追加します。

これらの要素がすべて準備できると、FlexScriptでaverageコマンドを呼び出せるようになります。

新しいFlexScriptクラスを追加する

モジュールは新しいFlexScriptクラスを追加できます。FlexScriptクラスは常にモジュールのdllで定義されているC++クラスに基づいています。新しいFlexScriptクラスの追加は、一般に次の3段階で行います。

  1. モジュール内にC++クラスを実装します。
  2. そのクラスのbindInterface()メソッドを実装します。
  3. このモジュールを使用してFlexSimのツリーに専用のノードを追加します。

SimpleDataTypeとCouplingDataTypeの派生クラスを追加する

SimpleDataTypeクラスには次の仮想メソッドがあります。

virtual void bindInterface();

追加するクラスがSimpleDataTypeまたはCouplingDataTypeを継承する場合、このメソッドをオーバーライドできます。次のコードは、単純なCircleクラスの例です。

class Circle : public SimpleDataType
{
public:
  double radius;
  double _getRadius() { return radius; }
  void _setRadius(double newValue) { radius = newValue; }

  double sliceArea(double sliceAngle);
  double _getArea();
  __declspec(property(get = _getArea)) double area;

  static Circle* init(TreeNode* target, double radius = 0);
  virtual void bind() override;
  virtual const char* getClassFactory() override { return "FlexScriptDemo::Circle"; }

  virtual void bindInterface() override {
      bindTypedProperty(radius, double, &Circle::_getRadius, &Circle::_setRadius);
      bindTypedProperty(area, double, &Circle::_getArea, nullptr);
      bindMethod(sliceArea, Circle, "double sliceArea(double sliceAngle)");
      bindMethod(init, Circle, "Circle init(treenode target, double radius = 0)", BIND_METHOD_STATIC);
  }
};

上のbindInterface()メソッドは、次のメソッドとプロパティをFlexScriptに公開します。

  • radius - 倍精度浮動小数点数値を取得または設定できるプロパティ。
  • area - 読み取り専用のプロパティ。倍精度浮動小数点数値を取得します。「setter」関数が提供されていないため、読み取り専用です。
  • sliceArea() - 倍精度浮動小数点数を受け取り、倍精度浮動小数点数を返すメソッド。
  • init() - ノードに円(circle)のデータを追加する静的メソッド。

bindInterface()メソッドを実装した場合、FlexSimがクラスのそのメソッドを呼び出せるように、ノードの追加が必要です。モジュールは次の場所にノードを追加する必要があります。

MAIN:/project/exec/globals/sdtinterfaces

ノードの名前は、getClassFactory()が返す名前でなければなりません。ノードの値は、クラスのFlexScript名である必要があります。モジュール名がFlexScriptDemoであれば、ノードは次のようになります。

ノードがモジュールに追加された後で、デフォルトを保存し、FlexSimを閉じる必要があります。FlexScriptクラスは、このアプリケーションが起動するときにのみ追加されます。

Circleクラスを使用するFlexScriptの例を示します。

treenode circleNode = Model.find("Tools").subnodes.assert("circle");
Circle circle = Circle.init(circleNode);
circle.radius = 1.5;
return circle.area; // returns 9.42

C++とFlexScriptで1つのクラスに対して同じAPIを作成することをお勧めします。たとえば、このCircleクラスは、C++とFlexScriptで同じメソッドとプロパティを持ちます。上のサンプルコードは、次のようにC++でもよく似ています。

treenode circleNode = Model::find("Tools")->subnodes._assert("circle");
Circle* circle = Circle::init(circleNode);
circle->radius = 1.5;
return circle->area; // returns 9.42

ObjectDataTypeの派生クラスを追加する

ObjectDataTypeはSimpleDataTypeを継承するため、これもbindInterface()メソッドをオーバーライドします。主な違いは、モジュールがsdtinterfacesノードの代わりにオブジェクトのlibraryノードにノードを追加する点です。

このインターフェイスノードは、そのクラスのライブラリオブジェクトのeventfunctionsノードに配置する必要があります。ノード名はflexScriptInterfaceに、ノードの値はFlexScriptクラスの名前にする必要があります。

たとえば、ListクラスでbindInterface()を実装すると仮定します。Listクラスのインターフェイスノードは次のようになります。

MAIN:/project/library/List>behaviour/eventfunctions/flexScriptInterface

一般的なC++クラス用のクラスを追加する

FlexScriptクラスの大部分は、C++のSimpleDataTypeクラスまたはObjectDataTypeクラスに基づいています。これらのクラスはツリーのノードにバインドされます。とはいえ、ツリーのノードにバインドされないクラスを追加しなければならない場合があります。たとえば、FlexSimにはVec3クラスがあります。このクラスは役立つプロパティとメソッドを持ちますが、ツリーのノードにはバインドされていません。

モジュールでこの種のクラスを追加できます。まず、次のメソッドを持つC++クラスを定義する必要があります。

static void bindInterface();

なお、このメソッドは親クラスから継承したものではありません。この名前とシグネチャを持ち、静的である必要があります。このメソッドの実装は、bindMethod()bindTypedProperty()、そしてクラスで必要な他のバインドメソッドを呼び出すことができます。

ツリーに含まれないクラスの場合、bindClass()メソッドを使用してこのクラスをバインドし、別のクラスのbindInterface()メソッドから呼び出す必要があります。

詳細については、次のセクションに示すサンプルコードを確認してください。

サンプルコード

次のファイルには、前述の各トピックのC++の例が含まれています。

module.cpp

クイックスタート」の手順に従ってモジュールを作成し、dllを追加した場合、デフォルトのmodule.cppファイルをこのサンプルファイルに置き換えることができます。

サンプルコードには次の要素が含まれています。

  • Point - 一般的なC++クラスの実装方法を示すクラス。
  • _PointBinder - bindInterface()からbindClass()を呼び出す方法を示すクラス。この例はその目的専用のクラスです。ただし、bindClass()は、ツリー内に該当するインターフェイスノードがあるどのbindInterface()からでも呼び出すことができます。
  • Circle - 単純なプロパティとメソッドを持つSimpleDataTypeクラス。
  • Heap - STLコンテナをカプセル化するSimpleDataType。このクラスは、カスタムデータ構造のバインドも示します。
  • Mound - Heapオブジェクトのコレクションを使用するObjectDataTypeクラス。FlexSimObjectの代わりにObjectDataTypeを継承するクラスは、一般にToolsとして使用されます。Group、StatisticsCollector、Listはすべて、ObjectDataTypeを直接継承しています。
  • average - 2つの数値の平均を求める関数。

このトピックに示すサンプルコード以外に、bindInterface()の使用例がAStarモジュールにあります。

  • CouplingDataTypeの例 - Traveler.cppには、bindInterface()のTravelerクラスの実装があります。Travelerクラスは、CouplingDataTypeクラスを継承します。
  • ObjectDataTypeの例 - AStarNavigator.cppには、bindInterface()のAStarNavigatorクラスの実装があります。このクラスはNavigatorクラスを継承しており、NavigatorクラスはObjectDataTypeクラスから派生したものです。自身のインターフェイスをバインドするだけでなく、多くのSDT以外のヘルパークラスのインターフェイスもバインドします。
  • 一般的なC++クラスの例 - AStarTypes.cppには、多くの一般的なC++クラスのbindInterface()実装があります(TravelPathクラスなど)。