モジュール開発
概要と主要な概念
このドキュメントには、モジュール開発の詳細について記載されています。このドキュメントは非常に長く、多くの情報が含まれており、モジュールの動作を理解するためにはすべての情報が重要です。このドキュメントは飛ばし読みせず、モジュール開発を始める前に熟読してください。
モジュールとは、FlexSimの機能、ライブラリ、オブジェクトを非常に柔軟にカスタマイズできる一連のテクノロジーです。モジュールを使用することで、製品のライフサイクル全体にわたって機能の保守と更新を簡単に行えます。FlexSimのモジュールは、当社の各種の製品をより的確に管理および開発するため役立ちます。ただし、FlexSimのディストリビューターや、エンタープライズライセンスを持つユーザーは、カスタマイズされた機能を開発するため、モジュール開発を自由に使用できます。
モジュールの機能
モジュールを使用して、FlexSimに機能を追加できます。FlexSimでは、次のような強力な機能を追加するためモジュールが使用されています。
- 処理フロー
- コンベヤ
- AGV
- AStar
- エージェント
- GIS
- 人
- FloWorks(モジュールSDKを使用してTalumis BVにより作成されました)
これらの機能はそれぞれ、モジュールにより定義されています。これらの機能の作成に使用されたリソースは、モジュール開発者も使用可能です。開発者はモジュールを、次のいずれか、またはすべてのタスクを実行するように設計できます。
- カスタムまたはサードパーティーのC++ライブラリをFlexSimに組み入れる - C++ライブラリをモジュールに含め、そのライブラリの機能(またはオブジェクトも)をFlexSimに公開できます。
- 3Dライブラリに新しいオブジェクトを追加する。
- 新しいFlexScriptコマンドやクラスを作成する。
- Direct2Dや同様のAPIを使用してカスタム描画コードを作成する - ProcessFlowはこの方法でビューを描画します。
- 新しいユーザーインターフェイスを追加する。
- 既存のユーザーインターフェイスのファイルメニュー、ツールバーなどを簡素化および拡張する。
モジュールは非常に柔軟で強力ですが、モジュール作成は難しくなる可能性があります。モジュールを作成するには、FlexSimのツリー構造を十分に理解した上で、付随するDLLも作成するなら、C++の中級から上級の知識も必要です。
背景
モジュール以前は、FlexSimにはカスタムのライブラリや機能を定義するため、ユーザーライブラリ方法と直接方法の2つの主要な方法が存在していました。モジュールは、両方の方法の利点を組み合わせるよう設計されたものです。
ユーザーライブラリ方法
この方法では、ライブラリ開発者が標準ライブラリオブジェクトをカスタマイズしてから、ユーザーライブラリに追加します。オブジェクトセット以外のものをさらにカスタマイズするには、ユーザーライブラリのロード時やモデルの新規作成時などに起動されるスクリプトを使用します。
この方法の主な利点は、長期的な保守が比較的簡単なことです。新しいFlexSimリリースが公開されたとき、ユーザーライブラリは自動的に更新され、大きな問題はほとんど発生しません。また、簡単にプラグ可能です。ライブラリをFlexSimにロードするだけで使用できます。
これに対して、この方法の欠点は2つあります。まず、開発者が簡単にカスタマイズ可能な部分が多少限定されます。オブジェクトセットのみを開発するなら大きな問題はありませんが、ユーザーインターフェイスを大きくカスタマイズしようとすると、各種のUIウィンドウを置き換えるスクリプトの作成、コマンドコードのカスタマイズなど多くの作業が必要なため、非常に困難です。このため、UIのカスタマイズごとに、そのカスタマイズをFlexSimツリーにインストールするためのスクリプトを作成する必要があります。2番目の欠点は、ユーザーライブラリのメカニズムが極めてモデラー中心なことです。このメカニズムは、モデラーがカスタマイズされたプロセッサを作成し、ライブラリに追加して、そのプロセッサを後で他のエリアやモジュールで再利用することを主な目的として作られています。特定のオブジェクトセットを開発して保守し、顧客に対して継続的に提供することを目的とする開発者にはあまり適していません。ユーザーライブラリオブジェクトは多くの場合、ロジックにラベルを使用しており、これらは「エンドユーザー向け空間」と競合します。また、オブジェクト変数を使用する場合は、変数を名前で参照する必要があり、処理が低速になる可能性があります。
直接開発
カスタム開発を行う2番目の方法は、FlexSimのプロジェクトとビューツリーで直接開発を行うことです。これは、FlexSimが2000年代中盤に、自社の最初の製品であるFlexSim HCを開発するため使用した方法です。基本的には、FlexSimのプロジェクトとビューツリーで直接開発し、独自のdefaultproject.fspおよびdefaultview.fsvファイルを作成して保守することになります。
この方法の利点と欠点は基本的に、ユーザーライブラリ方法の反対になります。まず、ユーザーインターフェイス、ライブラリオブジェクトの機能、その他FlexSimでのモデル構築と操作に関連するすべてのものを極限まで柔軟にカスタマイズできます。また、オブジェクトはC++オブジェクト指向言語のメソッドなどを使用して、よりクリーンな標準ライブラリ方法により開発できます。
一方で、C++オブジェクトの開発にはコンテンツのdllのコンパイルが必要で、ツリーファイルを自分で保有するため、実質的に独自のインストールを提供する必要があり、カスタマイズされた機能はプラグ可能ではありません。この方法に存在するもう1つの主な欠点は、保守性です。FlexSimが主製品の新しいリリースを作成し、そこに含まれている新機能をカスタムソリューションに統合しようとした場合、実質的に最初から作り直しが必要になります。これは、FlexSim HCがFlexSim 5の新機能を使用するように更新されるまで1年以上を要した理由の1つです。
モジュール
上述の2つの開発方法に存在する本質的な利点と欠点を踏まえ、FlexSimは、ユーザーインターフェイスと機能のどちらも極めて柔軟にカスタマイズできる新しいソリューションが必要だと認識しました。こうしたソリューションがあれば、FlexSim HC製品では、合理化された業界固有のモデリングパラダイムに合わせてFlexSimのユーザー操作を完全に再設計できます。同時に機能の保守も可能になり、新しいメインラインのFlexSimリリースを業界固有の製品に簡単に統合できるだけでなく、必要な機能をユーザーがうまく組み合わせられるように、業界固有の製品をメインライン製品に「プラグ可能」にするオプションも提供できます。この要求から、モジュールテクノロジーが生み出されました。
注:モジュールテクノロジーではカスタム開発の保守がはるかに容易化されていますが、モジュールを使用しても、カスタマイズされた開発が自動的に、無期限に上位互換となるわけではありません。まったく逆です。モジュールテクノロジーを選択した場合、FlexSimが新しいリリースを作成するとき、モジュールを積極的に保守する必要があります。モジュールテクノロジーは、UIを大幅にカスタマイズした場合、他の方法よりも更新プロセスを容易にするだけです。自動的に更新されるわけではなく、多くの場合は更新プロセスに依然として多くの手作業が必要です。カスタムオブジェクトを作成し、そのオブジェクトがFlexSimの将来バージョンでも自動的に動作することを希望する場合、モジュールではなくユーザーライブラリを使用するのが最適です。
モジュールの使用開始
以下では、モジュールを使い始める手順について説明します。モジュールを開発する場合、次の作業を行うことをお勧めします。
- 独自のカスタムFlexSimインストールフォルダを使用します。Program Files内のFlexSimフォルダを、開発専用の独自のフォルダにコピーします。また、コピー先の開発用フォルダは、書き込みアクセス許可の問題が発生しないように「My Documents」ディレクトリ内に置くことをお勧めします。
- merge/diffツールを用意します。FlexSimでは現在、ソースコード管理にGit(WindowsフロントエンドのGitExtensions)を使用しており、KDiff3というdiffプログラムが含まれています。WinMergeなどのスタンドアロンのツールも使用できます。自分またはFlexSimが各種のXMLファイルに加えた変更を分析するには、diffツールが必要です。
- ユーザーマニュアルに文書化されているFlexSimのXMLスキーマを調べます。さらに、FlexSimのプロジェクトとビューツリーがどのような構造であるかにも馴染んでおく必要があります。残念ながら、プロジェクトとビューツリー内で機能がどのよう配置されているかは文書化されていないため、これは開発者が自分で調べる必要があります。
準備が整ったら実際の開発に移ります。まず、FlexSimのデフォルトの状態を保存するボタンをツールバーに追加する必要があります。このためには、ツールバーに([グローバル設定]ウィンドウから)カスタムボタンを作成し、コードとして「applicationcommand("savedefaultviewproject")」を追加します。
このコマンドはツリー内を走査し、セッションを「初期」状態に復元してから、defaultproject.fsxとdefaultview.fsxをXMLツリーファイルとして保存します。このボタンをユーザーツールバーに追加してから、クリックします。FlexSim/programフォルダを調べると、これらのXMLファイルが作成されています。以後に保存を行ったとき比較できるよう、これらのファイルのコピーを保存しておきます。このチュートリアルでデフォルトの保存の指示がある場合、モデルではなくdefaulviewとdefaultprojectの保存を意味します。
その後で、次の手順に従います。
- VIEW:/environment/devにノードを追加し、ノードに番号データを与え、1に設定します。これにより「開発者モード」が有効になり、ツリービューの右クリックメニューに[モジュール]が追加されます。
- ツリーのMAIN:/project/modulesに移動します。サブノードを追加し、「testmodule」という名前を付けます。
- testmodule内にサブノードを追加し、「installdata」という名前を付けます。
- WindowsエクスプローラでFlexSim\modulesディレクトリに移動し、「testmodule」という名前のディレクトリを追加します。
- FlexSimに戻り、すべてのウィンドウを閉じて、ユーザーツールバーの保存ボタンをクリックします。
- 最後にFlexSimを閉じます。
これでFlexSim\modules\testmoduleディレクトリを見ると、testmodule.fsxとtestmodule.tファイルが存在しています。これら2つのファイルは同じツリーで、形式が異なるだけです。.fsxファイルは開発用で、リビジョン管理にXML形式を使用できます。.tファイルは配布用です。これらは本質的に、MAIN:/project/modules/testmoduleノードがツリーファイルとして保存されたものです。
FlexSimは、defaultproject.fsxまたはdefaultview.fsxを保存するとき(すなわち、新しいツールバーボタンを押したとき)、最初にMAIN:/project/modulesを走査し、メインツリーからこれらのノードを見つけ出して、ノードの名前に基づいてFlexSim\modules内のディレクトリに保存します(モジュールのパッキングロジックも実行されますが、これについては後で解説します)。その後でFlexSimを開くと、FlexSimは、FlexSim\modules内のすべてのサブディレクトリを走査し、<modulename>\<modulename>.fsxまたは<modulename>\<modulename>.tという名前のファイルを見つけ、それらのノードをMAIN:/project/modulesにロードします(ここでもアンパックが行われます。これについても後で解説します)。結果として、MAIN:/project/modulesに何かを入れてからプロジェクトとビューを保存しても、defaultproject.fsxとdefaultview.fsxのファイルは変更されません(diffツールで確認できます)。
モジュールに「追加」ノードを追加する
今度は、モジュールに何かを追加してみましょう。FlexSimを開き、ツリービューに移動します。ここで、プロセッサの処理時間ピックリストのオプションセットにピックオプションを追加するモジュールを想定します。ツリービューでVIEW:/picklists/timeitempicklistに移動し、ピックオプションリストのどこかにサブノードを追加します。名前は「My Cool Pick Option」とし、次のコードを入力します。
/**My Cool Pick Option*/
return 5*normal(4,1)/uniform(1,3);
次に、ツリーのノードを右クリックし、右クリックメニューで[モジュール] > [testmodules] > [追加として追加]を選択します。これはFlexSimに対して、追加したノードがモジュールの一部であると通知することになります。「追加ノードとして」の部分は、ツリーに新しいノードを追加しただけで、ツリーの既存のノードは変更していないことを意味します。
次にすべてのウィンドウを閉じ、ユーザーツールバーのカスタムの保存ボタンを再度クリックします。これでFlexSim\modules\testmodule.fsxファイルのXMLを調べると、モジュールのツリーファイルにスクリプトが追加されています。
モジュールに「置き換え」ノードを追加する
最初に行ったモジュールの変更は「追加」操作で、モジュールを使用して、以前に存在していなかった新しいノードをツリーに追加しました。モジュールの別のオプションとして、「置き換え」操作を追加できます。これは、モジュールを使用して、FlexSimに既に存在するノードのデータやサブツリーを変更する操作です。たとえば、プロセッサの[処理時間]ピックリストにある[指数関数]分布オプションのデフォルト値を変更することを考えます。平均値10の代わりに、20に設定するとしましょう。モジュールで、ツリーの[Exponential]ノードを置き換えるようにします。
このためには、VIEW:/picklists/statisticaldistributionに移動します。[Exponential]ノードを右クリックし、[モジュール] > [testmodule] > [置き換えとして追加]を選択します。
置き換え操作を追加したら、10を20に変更します。置き換え操作の場合、最初にノードをモジュールに追加してから変更を加える必要があり、順序を逆にしてはいけないことに注意してください。この理由については、後ほど説明します。置き換え操作は、置き換えるノードに加えて、そのすべてのサブノードとオブジェクト属性ノードにも適用されます。
置き換えと追加の両方の操作について、開発者モードではノードとサブノードが、その属するモジュールに従って強調表示されます。たとえば、複数のモジュールをロードしていると、最初のモジュールのノードは赤色、2番目のモジュールのノードは緑色、3番目のモジュールのノードは青緑色などで強調表示されます。また、ノードをクリックすると、そのノードが関連付けられているモジュールの名前と、そのノードがパックされるモジュール内でのノードへのパスが表示されます。たとえば、「My Cool Pick Option」ノードをクリックすると、ノードの左側に「testmodule add /1/1」というテキストが表示されます。これは、そのノードがtestmodule内の追加操作で、モジュールノード内でそのノードと関連付けられているノードへの、installdataからのパスが/1/1であることを意味します(モジュールの内部構造については後で解説します)。
パック/アンパックのロジック
既に述べたように、FlexSimがプロジェクトとビューを保存するとき、最初にモジュールノードを対応するモジュールディレクトリに保存してから、メインツリーを保存する前に、それらのノードをメインツリーから削除します。ただし、多少のパックとアンパックロジックも実行されます。「パック」とはFlexSimがノードを走査し、モジュールの一部として追加されたすべてのノードをモジュールノードに移動して、置き換えの場合は元の変更されていないノードを元の場所に戻すことを意味します。「アンパック」とはFlexSimがモジュールノード内に格納されているすべてのモジュール情報を取得し、プロジェクトまたはビューツリー内で適切な場所に置くことを意味します。保存を行うとき、プロジェクトとビューツリーに加えられたすべての変更がモジュールに正しく追加されていれば、それらの変更はすべてモジュールツリーに保存され、defaultproject.fsxとdefaultview.fsxは「初期状態」に保たれます。これにより、モジュールへのすべての変更は完全にモジュール式となります。モジュールディレクトリを削除(または単に名前を変更)してからFlexSimを開くと、モジュールが存在しない場合と同じようにすべてがロードされます。モジュールディレクトリを元の場所に戻すと、FlexSimはカスタマイズされた内容もすべて含めてロードします。defaultviewまたはdefaultprojectの保存操作を行うとき、これらの動作は次に示す順序で行われます。
- モジュールをパックする。
- モジュールノードを保存する。
- プロジェクトツリーからモジュールノードを削除する。
- プロジェクトまたはビューのツリーを保存する。
- モジュールノードをプロジェクトツリーの元の場所に戻す。
- モジュールをアンパックする。
FlexSimが最初に開くときは、次のロード手順が実行されます。
- defaultviewとdefaultprojectをロードする。
- FlexSim\modulesディレクトリを走査し、モジュールノードをMAIN:/project/modulesにロードする。
- モジュールをアンパックする。
モジュールのロードステップはツリーのロードステップの一部として発生するため、動的にロードされるユーザーライブラリで発生する順序の問題、たとえばFlexScriptコードがモジュール固有のコマンドを呼び出すなどは回避されます。モジュールは、そのコンポーネントがdefaultproject defaultviewの一部であるかのようにロードされるため、FlexSimのロードシーケンスに自動的に統合されます。同じ理由から、モジュールがロードされた後で「アンロード」や「再ロード」はできません。FlexSimには/excludemodulesというコマンドラインオプションがあり、ロードから除外するモジュール名をカンマで区切って定義できます。「all」を指定するとすべてのモジュールが除外されます。
追加のルールと注意事項
モジュールのメカニズムでは、追加および置き換えの操作を使用して、プロジェクトやビューのツリーに含まれるどの部分のデータやロジックでもカスタマイズできます。しかし、次の注意事項が適用されます。
- モジュールを使用してツリーに存在するノードを取り除く、「取り除き」操作はまだ組み込まれていません。この機能は将来組み込まれる可能性がありますが、今のところは取り除くノードの上にあるノードを置き換えてからノードを削除してください。
- C++の切り替え済みノードは、独自のビルドを行って独自のカスタムインストールを配布する予定がない限りは、置き換えまたは削除してはいけません。すべてのC++切り替え済みノード用のコードは、FlexSimのコンテンツdll(FlexSim\program\FlexSimcontent.dll)内にコンパイルされ、FlexSimとともにインストールされているため、変更は無効になります。また、dllに定義済みのバインドロジックで、dllが特定のノードを関連するC++グローバル関数にバインドする部分は、プロジェクトツリーを深さ優先で探索したときにノードが出現する順序に基づきます。すなわち、モジュールを使用してC++切り替え済みノードをツリーから取り除き(または追加)し、FlexSimで提供されているのと同じdllを使用すると、深さ優先走査でそのノードより後に出現するすべてのC++ノードは誤ったC++関数にバインドされるため、全体の動作が異常になります。
- 前のポイントと同じ理由で、ライブラリオブジェクトのC++メソッドに含まれるコードは、独自のインストールを作成する予定がない限りは変更してはいけません。カスタマイズされたC++の動作については、後ほど説明します。
- 置き換えまたは追加されたノードは、ビューまたはプロジェクトのツリー内で一意に定義された名前付きノードパスでアドレス指定可能な必要があります。たとえば、ノード自体、またはノードの祖先に「Options / Parameters」などの名前が含まれているノードは置き換えできません。「/」文字はFlexSimのパス構文で特別な意味を持ち、「/」文字によりパスが捨てられるため、そのノードへの一意な名前付きパスを得ることができません。また、サブツリー内で複数のノードが同じ名前のとき、それらのノードへのパスはすべて同じで、そのパスは常に最初のノードをアドレス指定するため、技術的にはセット内の最初のノードしか置き換えできません。ただし、ノードをモジュールに追加してからノード名を変更することはできます。モジュールに追加したノードには、好きな操作を実行できます。
- 置き換えおよび追加操作は、プロジェクト内で「静的」であることが判明している、つまり定期的な取り除きや置き換えが行われないノードにのみ実行します。たとえば、モデルツリー内のノードには置き換えや追加の操作を行ってはいけません。新規モデルが作成されるたびにモデルツリー全体が置き換えられるためです。その代わりに、新しいモデルが作成されたときにモジュール固有のノードをモデルに追加するスクリプトノードをプロジェクトに追加します。
- 残念ながら、現時点ではモジュールのUIのエラーチェック機構は最小限でしかないため、多くの場合にはこれらのルールを開発者自身がチェックする必要があります。
モジュールの内部構造
モジュールのパック/アンパックのロジックが内部でどのように動作するかを理解するため、モジュールノードの構造について説明します。
すべてのパックとアンパックのデータは、モジュールのinstalldataノードに格納されます。このノードは、モジュール内で最初のノードの必要があります。まず、追加動作について説明します。
追加動作を追加すると、FlexSimは追加するノードの親ノードを指すinstalldataにノードを追加してから、目的のノードを表すレコードをそのノード内に追加します。同じ親ノードを使用する他の追加動作はすべて、この親ノード内に追加されます。ノードをどのランクに置くか、オブジェクトデータのサブツリー内に置く必要があるかどうか、およびモジュールがパックされるときに実際のノードが配置されるノードが存在するかどうかは、サブノードにより示されます。
次に、置き換え動作について説明します。
置き換え動作を追加すると、FlexSimはそのノードを指すinstalldata内に置き換えノードを作成してから、置き換えノード内にノードのコピーを2つ作成します。最初のコピーは「元」のコピーで、2番目のコピーは「マスター」コピーです。モジュールがパックされるとき、FlexSimはモジュールが保存される前に、ツリー内で変更されたノードとともにマスターコピーをスワップアウトし、カスタマイズされたノードが2番目の位置に置かれるようにします。モジュールがアンパックされるとき、FlexSimはカスタムノードをツリーからスワップアウトし、マスターコピーを置き換えノードの2番目の位置に戻します。次の図は、正しいプロセスを示しています。
マスターコピーは通常、元のコピーと完全に同じです。ただし、FlexSimを新しいバージョンに更新した場合、マスターコピーが元のものと変更されている可能性があります。元のコピーはこの目的、すなわちFlexSimの新しいバージョンの変更内容をモジュールに結合できるようにするためにのみ使用されます。これについては、後ほど詳しく説明します。また、この用途の関係で、モジュールノードのディストリビューションコピーである<modulename>\<modulename>.tを保存する前に元のコピーは取り除かれます。このため、testmodule.fsxとtestmodule.tは技術的には完全に同じではありません。置き換え動作の元のコピーはtestmodule.tから取り除かれるためです。
上述の理由から、ノードに変更を加える前に、モジュールに置き換え動作を追加することが重要です。これにより、元のコピーとマスターコピーが変更済みのバージョンではなく、FlexSimが配布した正しい初期状態のバージョンであることが保証されます。次の図は、正しくないプロセスを示しています。
置き換えとしてノードを追加する前に、誤ってツリーに変更を加えた場合、マスターコピーは「変更済み」になり、defaultprojectおよびdefaultviewを保存すると、FlexSimのディストリビューションから「変更済み」の状態で保存されることになります。
これは、次の方法で常に修正できます。
- FlexSimを閉じます。
- defaultprojectとdefaultviewを、最初に作成したコピーに置き換えます。
- 再度FlexSimを開きます。
- ツリーの置き換えノード(installdataではなく、置き換えの場所)を右クリックし、[モジュール] > [testmodule] > [置き換えマスターをオリジナルとして保存]を選択します。
defaultprojectとdefaultviewを置き換えることにより、ターゲットノードがリセットされ、モジュールがアンパックされたとき「初期状態の」ノードがマスタースポットにスワップインされます。これはFlexSimのロード時に発生するため、後はマスターコピーを元のノードにコピーするだけで、これはステップ4で行われます。
この誤りを修正するのは簡単ですが、むやみに行ってしまわないように十分に注意してください。defaultprojectとdefaultviewを保存することで、ツリーに加えた変更をモジュールに追加することを忘れた場合のための対処方法を用意しておくことができます。変更を加え、それをモジュールに追加することを忘れてしまっても、defaultprojectとdefaultviewを保存するときにそれらの変更は保存されるので問題ありません。しかし、defaultprojectとdefaultviewを頻繁に初期状態のコピーに置き換えると、モジュールに追加することを忘れた変更を意図せずに消去してしまう可能性があります。このため、defaultprojectとdefaultviewについて、初期状態のコピーと定期的にdiffで比較し、変更を加えながらモジュールへの追加を忘れているものがないかを確認することをお勧めします。これは必ず、defaultprojectとdefaultviewを初期状態のコピーと置き換える前に行ってください。この場合、XMLを読み取るのが難しいことから、diffでの走査が困難になる可能性があります。しかし、XML形式とdiffツールは多用することになるため、習熟する必要があります。defaultprojectとdefaultviewを各セクションに分割するため、ユーザーマニュアルに記載されている.ffm(FlexSimファイルマップ)形式を使用することもできます。これらには多少の相違がありますが、ノイズに過ぎないため(VIEW:/active、またはモジュールで多くの内容を持つ新規モデルを初期化する場合はモデルツリーにも存在することがあります)、各部分を別のファイルに分割することで、変更されていないことが明らかなファイルを無視できます。
モジュールの保守
testmoduleの開発が終わったと仮定します。このtestmoduleは、プロセッサの[処理時間]ピックリストに新しい「My Cool Pick Option」を追加し、デフォルトの「指数関数分布」の平均を10から20に変更するものです。このモジュールは、FlexSimのX.0リリース(Xは22、23などのバージョンです)で構築されたものです。そこで、FlexSimが新しいバージョンX.1をリリースしたとします。このモジュールがFlexSim X.1で正しく動作し、改良点が適切に統合されることを確認する必要があります。いくつかの選択オプションに些細な変更を加えただけなので、このモジュールは新しいバージョンに自動的に更新されるはずです。しかし、バージョンX.1でも[処理時間]ピックリストに改良や調整が加えられている場合は必ずしもそうとは限りません。
ここで注意しておきたいのは、FlexSimはdefaultprojectとdefaultviewに対して、有益とみなした変更を加える権利を留保していることです。当社はFlexSimモデルの下位互換性の維持を最優先とし、ユーザーライブラリの下位互換性を次の優先事項としています。しかし同時に当社はユーザーインターフェイスの改良や機能の追加も積極的に進めており、必要ならプロジェクトやビューのツリーの構造を変更します。[処理時間]ピックリストのオプションの一部を変更する可能性もあり、さらにはVIEW:/picklists/timeitempicklistノード全体をツリーの別の場所に移動するか、ピックリストとは別の優れたオプションを使用し、ノードを完全に削除する可能性もあります。これは、当社がこのピックリストを近い将来に移動することを計画しているという意味ではありません。ツリー内の特定の場所は常に変更される可能性があるということです。比較的新しいいくつかの機能では、多くの調整とリファクタリングが必要となるため、ツリーの再構成が多少頻繁に行われることになります。これに対して、いくつかの古い機能は固定化されており、既に成熟しているため、または下位互換性のためそのまま維持する必要があるという理由から、ほとんど変更されません。または、古い機能が新たに注目され、その部分についてリファクタリングが行われることもあります。肝心なことは、何を変更するかはFlexSimの裁量範囲であるということです。モジュール開発者および保守担当者には、当社が新しいリリースを公開したとき、構造にどのような変更が加えられたか、それらの変更がモジュールにどのような影響を及ぼすかを当社に問い合わせることをお勧めします。当社は、ツリーに加えられたすべての変更についての詳しいリストを保有していません。理由の1つは、そのような長大なリストを調べて、作成したモジュールにどの変更が適用されるかを見つけ出すのは膨大な手間となることです。2つ目の理由は、当社が加えるあらゆる変更を常に追跡するのは開発プロセスにおいて余計な作業であるため、その変更がモデルの下位互換性に何かの影響を及ぼす場合を除いては、そのような追加作業が開発の妨げとなることを望まないためです。ただし、モジュールの機能はこのような問題点を識別するため役立つよう特に設計されているため、新しいリリースが公開され、更新を試みるとき、特定のエラーや変更を確認できます。その場合、開発者や保守担当者は「この新しいバージョンでは、この場所でモジュールにエラーが発生します。この変更はどのようなもので、モジュールが正しく動作するにはどのような更新が必要ですか?」と当社に問い合わせできます。その場合、当社は「その変更はこのような理由で行われました。変更内容はこのとおりで、お使いのモジュールを更新するにはこのような方法があります」と回答できます。
モジュールの保守の話に戻りましょう。バージョンX.1で[処理時間]ピックリストに変更が加えられていない限り、モジュールは問題なく更新されるはずです。モジュールを更新する方法は、新しいバージョンで加えられた変更の種類によって異なります。
最初の可能性は、置き換えのうち1つ以上のパスが新しいリリースで変更され、モジュールに保存されているパスが無効になることです。この場合、FlexSimをロードするとき、モジュールの動作場所が無効であるというエラーが表示されます。このときはその場所を調べ、何が変更されたのかを確認し、変更内容とその理由などをFlexSimの開発チームにメールで問い合わせてください。2番目の可能性は置き換え動作にのみ当てはまるもので、ノードのパスは変更されていないが、FlexSimの新しいリリースでノードのデータやサブツリーが変更されることです。場合によっては、モジュールは依然として問題なく、ノードを作成したカスタムノードに置き換えられることもあります。しかし、FlexSimが加えた変更が、モジュール作成者が置き換えていないツリー内の他の変更と相互依存性がある、またはモジュール作成者がほとんど触れていない部分でバグを修正した場合もあります。このような場合、これらの変更をモジュール置き換えノードに結合することが必要な可能性があります。次の図は、この動作を示したものです。
ここでは、マスターと元のノードのすべての部分が関係します。モジュールをX.0で構築してから、そのモジュールをX.1でロードすると、上の図は次のように解釈可能になります。
モジュールで置き換えた各ノードについて、元のコピー、またはバージョンX.0で使用されていたノードの両方が存在します。同時にマスターコピーも存在し、これはバージョンX.1に含まれているノードです。最後にtestmoduleバージョン、またはモジュール作成者が変更したノードが存在します。バージョンX.0からX.1で変更された部分をtestmoduleバージョンに統合し、新しい統合バージョンを作成する作業を行います。
例を示します。FlexSimのバージョンX.0からX.1で、[処理時間]ピックリストの[指数関数]オプションにコメントが追加されたと考えます。最初に、変更が加えられたことを見つけ出してから、それをtestmoduleにどのように統合するかを見つける必要があります。例を示しましょう。
まず、X.0からX.1への変更をエミュレーションします。次の手順に従います。
- FlexSimを閉じます。
- FlexSim\modules\testmodulesの名前を別の名前に変更し、FlexSimを開いたときにモジュールがロードされないようにします。
- FlexSimを開きます。
- VIEW:/picklists/statisticaldistribution/Exponentialに移動します。
- 行2に、「// Different stuff is here」というコメントを入れます。
- ユーザーツールバーの保存ボタンをクリックします。
- FlexSimを閉じます。
- FlexSim\modules\testmodulesを元の名前に戻します。
- 再度FlexSimを開きます。
これで、VIEW:/picklists/statisticaldistribution/Exponentialの置き換え動作について、X.0のバージョンが元のものとしてinstalldataに格納され、X.1バージョンがマスターコピーとしてinstalldataに格納され、testmoduleバージョンはツリーに格納されます。統合を開始するには、ツリービューを右クリックし、[モジュール] > [testmodule] > [すべての置き換えを統合用に保存]を選択します。次に、FlexSim\Modules\testmoduleディレクトリに移動します。FlexSimは、fssavedreplacementsという名前の新しいディレクトリを作成しています。ここには4つのサブディレクトリがあり、それぞれが前の図に示されている4つのボックスである「元」、「マスター」、「モジュール」、「統合」を表します。mergeディレクトリは統合されたコピーを格納するディレクトリなので、この時点では空です。各ディレクトリには1つ以上のXMLツリーファイルが含まれており、各ファイルはモジュール内での特定の置き換え動作を表しています。この場合、ファイルは1つだけで、VIEW:/picklists/statisticaldistribution/Exponentialに対応します。元、マスター、モジュールのコピーを取得し、それらのコピーから統合されたコピーを作成し、FlexSimにロードできるようにします。手順について説明します。
上述のように、FlexSimではバージョンコントロールにGitを使用し、KDiff3というdiffツールを使用しています。これらは強力なツールですが、熟練したプログラマーでも扱いが難しいので、時間があるときにマニュアルをざっとでも読んでおくことをお勧めします。KDiff3は個別にダウンロードできます。この例ではKDiff3を使用します。
KDiff3を開きます。diffを実行するファイルやディレクトリへのパスを指定するよう求めるダイアログが表示されます。A、B、Cという名前の3つのフィールドが存在します。Aには、fssavedreplacements\originalディレクトリへのディレクトリを指定します。Bには、masterディレクトリを指定します。Cには、moduleディレクトリを指定します。次に[Merge]ボックスをチェックし、出力先をmergeディレクトリに定義します。[OK]をクリックします。
KDiffにより、変更のdiff分析が行われます。
この表示は、異なるファイルが1つ見つかり、チェックすべき統合が1つ存在することを示しています。[OK]をクリックします。次にメインメニューに移動し、[Directory] > [Start/Continue Directory Merge]を選択します。次に表示されるダイアログで、[Do It]をクリックします。これにより、チェックする変更部分が表示されます。
上端のペインは比較された3つのバージョンを表し、下端のペインは提案される統合バージョンを表します。左上は元のA、中央上はマスターのB、右上は作成したモジュールのCを表しています。KDiffにより変更された部分が強調表示されています。コメントが追加され、10が20に変更されています。下端のペインに、統合が基本的に正しく表示されていることに注目してください。値の10が正しく変更され、コメントが追加されています。
これ以外に、いくつかのステップを実行します。
- 上端にある[Save]ボタンをクリックします。KDiffにより、ファイルがFlexSim\modules\testmodule\fssavedreplacements\mergeディレクトリに保存されます。
- FlexSimに戻って、ツリーの右クリックメニューで[モジュール] > [testmodule] > [統合されたすべての置き換えをロード]を選択します。これにより、mergeディレクトリのファイルが、ツリー内の対応する置き換えノードにロードされます。
- 同じメニューで、[すべての置き換えマスターを元のものとして保存]を選択します。
統合された置き換えがロードされると、ツールバーのボタンを使用してモジュールを保存でき、統合された置き換えがモデルとともに保存されるようになります。置き換えマスターは元のものとして保存されるので、FlexSimの将来のY.0に結合を行うと、X.0ではなくX.1から開始されます。
この例では、KDiff3は統合をシームレスに行います。場合によっては、これよりも統合がはるかに難しいこともあります。特に、変更がツリーの複数のノードにわたっている場合にその可能性があります。このような場合、KDiff3を使用してX.0からX.1への変更のみを識別してから、FlexSim内でコードやノードをマスターコピーからモジュールコピーへ直接コピーして貼り付けて統合を行う方が実用的なことがあります。しかし、少なくともモジュールのメカニズムにより、どの部分で統合が必要かをごく短時間で識別できます。
ベストプラクティス
複数のモジュールが同時にロードされる場合、互いに競合せず、片方のモジュールが意図せず他のモジュールの必要なUI要素を隠してしまわないようにモジュールを実装する方法については、ベストプラクティスにまだ多くの改良の余地があります。モジュールの開発を始めると、適切な方法のいくつかは自然に理解できると思います。このような問題を回避する簡単な方法の1つは、モジュールに固有のデータは可能ならMAIN:/project/modules/<modulename>ノード自体に含めることです。installdataノードは存在する必要がありますが、それ以外のものは好きな場所に配置でき、既にモジュールノード内に存在するため、モジュールに追加する必要はありません。また、ノードをVIEW:/modulesに追加しているため、新しいUIウィンドウをモジュールの一部として作成する場合は、ノードをVIEW:/modulesに追加し、そのノードにモジュールの名前を付け、追加としてモジュールに追加するだけです。その後で、そのノード内にGUIを追加します。これにより、モジュール間の競合をより的確に予防できます。
ファイルマップを使用してモジュールツリーファイルを分割する
FlexSimでは、FlexSimファイルマップ(.ffm)を使用して、ツリーファイルを複数の異なるファイルに分割するのが便利です。これには2つの主な利点があります。最初に、ノイズの変化を除去して、より明確なリビジョンコントロールが可能です。これは、意図的に変更を加えなくても、FlexSimの操作によりツリーの特定の部分が変更されることが多いためです。2番目に、同じプロジェクトで複数の開発者が、異なる部分の作業を行っているとき、統合の競合が発生する可能性を最小限に抑えることができます。
この目的で、モジュールを別々の部分に簡単に分割できるよう、モジュール構造にいくつかの機能が追加されています。すべての追加と置き換えはinstalldataノード内に保存されるので、installdataサブツリーは柔軟に管理できるようになっています。
まず、パック/アンパックのメカニズムでは、installdata内で、名前のテキストが「add」または「replace」で始まるノードのみが検索されます。これ以外に、「remove」という名前は将来の開発のため予約されています。ノードの名前の先頭がこれらの名前のどれでもない場合、そのノードは無視されます。すなわち、「replace」ノードを「replace_experimenter」のような名前に変更しても、先頭が「replace」であれば、そのノードは依然としてFlexSimで置き換え動作と解釈されます。このため、独自の名前を定義すれば、その動作は固有の名前を持つパスとなり、ファイルマップで使用することで別のノードを分割できます。これらのノードはinstalldataサブツリーのさらに深い階層に含まれていても問題ありません。
2番目に、パック/アンパックのロジックの関係で、installdata内にノードが出現する順序は意味を持ちません。
これら2つの動作により、installdataのサブツリー内に独自のサブノードを追加し、独自の名前を与えてから、ツリー内で上または下の階層に別の追加または置き換えレコードを移動し、自分で定義した論理グループとしてグループ化し、その後でファイルマップの「split-points」メソッドを使用して、モジュールツリーを好きなように分割できます。
モジュールのDLLの実装
モジュールの機能により、モジュールと関連付けられているDLLを実装して、C++の機能をカスタマイズできます。このDLLは標準のユーザーDLLと似ていますが、多少の相違があります。まず、モジュールDLLはFlexSimの起動時にロードされます。また、モジュールDLLを使用して標準ライブラリオブジェクトのC++継承を実装できるため、モジュールDLL内でのオブジェクト開発は、標準ライブラリでのC++オブジェクト開発とほとんど同じです。このため、モジュールDLLを実装するには、標準ライブラリオブジェクトがどのように動作するか、どのようなメソッドが存在するか、およびC++の継承やクラスメソッドなどに習熟しておく必要があります。別の相違点として、FlexSimの新しいバージョンがリリースされたとき、ユーザーDLLは再コンパイルの必要がないのに対し、モジュールDLLは新しいリリースのヘッダーファイルを使用して再コンパイルする必要があります。
testmoduleでDLLの例を作成してみましょう。FlexSimのモジュールSDKには、module_templateという空のモジュールプロジェクトが含まれています。このファイルをコピーし、testmoduleディレクトリに貼り付けます。このフォルダをtestmoduleDLLのような名前に変更してもかまいませんが、他の場所でこの名前は使用されません。
次に、最新版のVisual Studioで、コピー先ディレクトリ内の.slnファイルを開きます。ファイルが開いたら、Solution Explorerを開きます。通常は横にタブがあります。モジュールプロジェクトを右クリックし、プロパティを開きます。[ターゲット名]を$(ProjectName)からtestmodule、または自分のモジュールの名前に変更します。最後のチェックとして、ソリューションを構築します。エラーなしに構築が完了するはずです。
このモジュールでは、少しカスタマイズされたTaskExecuterを作成します。このために、標準のTaskExecuterを継承するクラスを作成しますが、オフセットはオフセットのx部分だけを移動するようにします。モジュールクラスを作成する手順は次のとおりです。
- クラスの名前(MyTaskExecuter)を持つ.hおよび.cppファイルを追加します。
- ヘッダーファイルは次のようになります。
- MyTaskExecuter.cppに次のコードを入力します。
- モジュールのプライマリcppファイル(module.cpp)のcreateodtderivative()関数に、新しいクラスのインスタンスを返すコードの行を追加します。この関数は次のようになります。
次に、TaskExecuterのbeginOffset()メソッドをオーバーライドします。このために、Visual Studioの検索機能(ctrl-shift-f)を使用して「beginOffset」を検索し、allobjects.h内にあるこのメソッドの宣言を見つけます。この宣言を自分のクラス定義に貼り付け、DLL_FUNCTIONの部分を取り除きます。また、xOnlyという名前の変数を追加します。でき上がるクラス定義は次のようになります。
次にmytaskexecuter.cppに移動し、メソッド定義を追加します。バックグラウンドで、TaskExecuterにはoffsetlocというdouble[3]メンバーがあり、オフセット先として必要な位置を定義するために使用されます。TaskExecuterにはbeginOffsetDefault()というメソッドもあり、これはbeginOffsetのデフォルト実装です。そこで、メソッドの実装において、beginOffsetDefault()を呼び出すときにoffsetlocのyおよびz成分を0に設定します。最終的なメソッド実装は次のようになります。
これでDLLを構築します。この時点で、このDLLはFlexSimに統合する準備が整っているため、FlexSimに移動して新しいクラスをライブラリに追加します。次の操作を行います。
- FlexSimを開きます。
- ツリービューでMAIN:/project/library/taskexecutersに移動し([ツリーナビゲーション]パネルの上のフィールドにこの文字列を貼り付けます)、ノードを展開します。
- TaskExecuterをコピーし、その直後にノードを追加して(スペースバーを押します)、そのノードにコピーを貼り付けます。
- コピーの名前を「MyTaskExecuter」に変更します。
- これを、追加としてモジュールに追加します。
- オブジェクトのvariablesサブツリーをクリアします([ノードプロパティ]パネルの[コンテンツをクリア]ボタン)。ただし、variablesノードは削除しません。
- オブジェクトのbehaviourサブツリーをクリアします(ただし、behaviourノードは削除しません)。
- オブジェクトのclasses属性内で「TaskExecuter」サブノードをコピーし、superclasses属性内の「Dispatcher」サブノード上に貼り付けます。
- classes属性内に戻り、「TaskExecuter」を「testmodule::MyTaskExecuter」に変更します。
- MyTaskExecuterを指すように、ノードのポインターデータを設定します([ノードプロパティ]パネルの[データ]フィールドの横にあるサンプラーボタンをクリックし、MyTaskExecuterをサンプリングします)。
- オブジェクトのvariables属性内で、xOnlyというサブノードを追加し(Enterを押します)、値を1に設定します(Nを押して数値データを指定します)。
ここで、ドラッグドロップライブラリビューに新しいオブジェクト表示を作成します。
- MAIN:/project/exec/globals/LibraryGroups/TaskExecutersに移動し、このノードを展開します。
- TaskExecuterノードのコピーを作成し、名前を「MyTaskExecuter」に変更します。
- これを、追加としてモジュールに追加します。
- 新しいノードのオブジェクトデータを展開します([>]ボタンを押します)。
- picture属性のサブノードを削除します。
- windowtitleを「MyTaskExecuter」に変更します。
- popOutChoicesノードの名前をdroppathに変更し、テキストをMAIN:/project/library/taskexecuters/MyTaskExecuterに変更します。
- 新しいモデルを作成し、[デフォルトを保存]ボタンを使用します。
- FlexSimを閉じてから、再度開きます。
次に、ライブラリのアイコングリッドで、MyTaskExecuterオブジェクトをモデルにドラッグし、他のTaskExecuterと同様に他のオブジェクトと接続すると、そのオブジェクトはオフセットのx部分しか移動しないことが分かります。これは、このオブジェクトのbeginOffset実装に、継承されたクラスメソッドが使用されていることを示しています。確認のため、モデルのTaskExecuterのツリーを開き、xOnlyノード(variables属性内)を0に変更します。TaskExecuterは通常に動作するようになります。
他の有益な事項
DLLの詳細
上の例に示したように、ライブラリオブジェクトがモジュールDLLの一部として実装されることを定義するには、オブジェクトのclasses属性でクラスを<modulename>::<classname>と定義します。また、ノードをDLLとして切り替え、通常のdllと同様にモジュールDLL内の関数を参照させるには、dll-toggledノードのテキストの中で、使用するdllを引用符で囲んで指定する("module::<modulename>")必要があります。たとえば、testmoduleのDLLにmyfuncというDLL関数があり、dll-toggledノードをその関数にリンクするには、ノードのテキストを「dll: "module::testmodule" function: "myfunc"」にします。それ以外の点では、手続きは通常のDLLと本質的に同じです。
モジュールの依存関係
モデルがモジュールのオブジェクトや関数を使用する場合、そのモジュールに依存します。この依存関係は、モデルツリーのToolsフォルダに保存されます。モデルの依存関係を処理するアプリケーションコマンドは2つあります。
- applicationcommand("assertmoduledependency")
- applicationcommand("ismodeldependent", moduleName)
最初のコマンドは、適切な依存関係が作成されていることを確認します。これは、モジュールオブジェクトをライブラリからモデルにドラッグするとき自動的に行われます。ただし、多くの状況においては依存関係を手動で作成する必要があります。2番目のコマンドは、モデルが名前で指定されたモジュールに依存しているかどうかを、trueまたはfalseで返します。
新しいバージョンへの更新
前に述べたように、FlexSimが新しいリリースを作成したときには、新しいヘッダーファイルとライブラリを使用してDLLを再コンパイルする必要があります。ファイルを更新するには、次の手順に従います。
- モジュールのflexsimcontentディレクトリに移動します(flexsim_dev_dir\modules\moduleName\dll_folder_name\flexsimcontent)。
- モジュールのflexsimcontentディレクトリから古いflexsimcontentファイルを削除します。
- FlexSim\program\system\includeのファイルを、モジュールのflexsimcontentディレクトリにコピーします。
- FlexSim\program\system\libにあるflexsim.libを、モジュールのflexsimcontentディレクトリにコピーします。
- DLLを再コンパイルします。
繰り返しますが、FlexSimは、標準ライブラリに適切と見なした変更を加える権利を留保します。このため、モジュールDLLで利用可能なメソッドが変更される、または他の変更が行われる可能性があるので、適切なヘッダーファイルを移動するよりも複雑な更新プロセスが必要となる可能性があります。モジュールDLLの更新で問題が発生した場合は、変更内容と、モジュールDLLをどのように修正すべきかについて、当社にお問い合わせください。