データ共有機能"DataStore"

DataStoreは、任意のC++オブジェクトを効率的に保存・取得し、任意のオブジェクトをフレームワーク内で簡単かつ効率的に共有できるようにします。この機能では、型が厳しくチェックされ(保存時と同じ型のオブジェクトのみ取得できます)、C++の標準メカニズムのみを使用できます。

DataStoreは、例えば以下の用途に使用できます。

  • 有限オートマトンのStates間でデータを共有する

    • 例えば、既存の有限オートマトンとStabilizerStandingStateLIPMスタビライザーのチュートリアルを参照)を同じ場所に置くだけで、有限オートマトンを簡単に安定させることができます。また、DataStoreを使用してStabilizerStandingStateの目標を指定できます。例えば、StabilizerStandingState::comプロパティを設定するだけで、質量中心の目標を変更できます。
  • 追加の情報を観測器に与えるKinematicInertial観測器が必要とするanchorFrameなど)。

  • プラグインとコントローラーとの間でデータを共有する。例として、歩行コントローラーと歩容プランナープラグインとの間で情報をやり取りする場合について考えてみます。歩容プランナーは、入力として、(i) 現在の足の接触面(ii) 目標地点に関する情報を必要とし、場合によっては追加の情報(障害物など)を必要とします。また、戻り値として、歩行コントローラーが従うべき(iii) 次の歩容を出力する必要があります。さらに、オンラインの歩容プランナーの場合、最新の歩容計画を取得する必要があります(目標やロボットの状態が変化したときや、新たな障害物が検出されたときなど)。

    • (i) の情報はコントローラーあるいはロボットの状態を推定する他のビジョンプラグインから取得可能
    • (ii) の情報はコントローラーあるいは物体検出などの他のプラグインから取得可能
    • (iii) の歩容計画はコントローラーによって使用される

もちろん、他にもいくつかの使用例が考えられます。この機能は、フレームワーク内でデータを簡単に共有できるようにすることを目的としています。データストアにオブジェクトを追加すると、そのオブジェクトが事実上グローバルになり、外部のプラグイン等を使用してどこからでも編集できるようになるため、この機能は慎重に使用してください。

使用方法

mc_control::MCControllerクラスにデフォルトのDataStoreインスタンスが用意されており、mc_control::MCController::datastore()を使ってアクセスできます。コントローラーのインスタンスにアクセスできるあらゆる場所から、このDataStoreインスタンスに任意のc++オブジェクトを作成したり、このインスタンスからオブジェクトを取得することがきます。

  • 以下のように、makeあるいはmake_initializerを使用してデータストア上にオブジェクトを直接作成できます。

    // 長さ4、初期値として42.0が入ったdouble型のベクトルを生成
    datastore().make<std::vector<double>>("key", 4, 42.0);
    // 長さ2、初期値として4と42が入ったdouble型のベクトルを生成
    datastore().make_initializer<std::vector<double>>("key", 4, 42.0);
    
  • 同じ名前のオブジェクトが既に存在する場合は例外が発生します。これを回避するには、そのような要素がデータストアに既に存在するかどうかを以下のようにチェックします。
    if(!datastore().has("key"))
    {
      datastore().make<Eigen::Vector3d>("key", 1, 2, 3);
    }
    

    なお、これらの関数は、作成したオブジェクトへの参照も返すため、その参照を使ってオブジェクトへのアクセスや編集が行えます。

    auto & vec = datastore().make<Eigen::Vector3d>("EigenVector", Eigen::Vector3d::Zero());
    vec.x() = 42; // ベクトルの中身は42, 0, 0となる
    
  • 保存されている値への参照は、get<Type>を使用していつでも取得できます。

    auto & vec = datastore().get<Eigen::Vector3d>("EigenVector");
    // ベクトルの中身は42, 0, 0
    

    指定したキーがデータストアに存在しない場合や作成時と異なる型をTypeに指定した場合は、例外がスローされます。getのオーバーロードを使用すると、mc_rtc::Configurationオブジェクトの場合と同様に、キーが存在しない場合のデフォルト値を指定できます。

    Eigen::Vector3d vec{1,2,3};
    datastore().get<Eigen::Vector3d>("NotAKey", vec); // ベクトルの中身は1, 2, 3のまま
    datastore().get<Eigen::Vector3d>("EigenVector", vec); // ベクトルの中身は42, 0, 0になる
    
    bool hasFeature = datastore().get<bool>("HasFeature", false); // hasFeatureには"HasFeature"キーに対応する値が存在する場合はその値が、それ以外の場合はfalseが格納される。
    

高度な使用方法

  • 継承もサポートされています。ただし、後でオブジェクトを取得するには、オブジェクトの親クラスを明示的に指定する必要があります。以下に、継承とメンバー関数の派生を使用してオブジェクトの保存と取得を行う非常に簡単な例を示します。

    struct A
    {
      virtual std::string hello() const
      {
        return "A";
      }
      std::string type() const
      {
        return "A";
      }
    };
    
    struct B : public A
    {
      std::string hello() const override
      {
        return "B";
      }
      std::string type() const
      {
        return "B";
      }
    };
    datastore().make<B, A>("ObjectB");
    auto & parent = store.get<A>("ObjectB");
    auto & derived = store.get<B>("ObjectB");
    parent.type();   // "A"
    parent.hello();  // "B" (hello()は仮想関数であるため)
    derived.type();  // "B"
    derived.hello(); // "B"
    
  • ラムダ関数も保存できますが、特殊なmake_callを使用する必要があります。

    // ラムダ関数を生成し、std::functionとして格納する
    datastore().make_call("lambda", [](double t) {});
    // ラムダ関数の取得
    auto & lambdaFun = datastore().get<std::function<void(double)>("lambda");
    // 関数の呼び出し
    lambdaFun(42);
    // データストアからの直接呼び出し (関数の返り値と引数の型を繰り返す必要がある)
    datastore().call<void, double>("lambda", 42);