EASTL

EASTL

EASTL は Electronic Arts 社が整備した標準テンプレートライブラリ(コンテナ・アルゴリズム)で、C++ の STL (Standard Template Library) に倣い実装されていますが、制約の多いゲーム機の開発で整備されてきた経緯があり、メモリの取り扱いに制約が大きい環境を意識したコンテナやアルゴリズムが用意されています。

本ライブラリは EASTL を TWENET 内で利用できるようにしています。

以下はバッファ最大長が固定の配列とソートアルゴリズムの適用です。

以下にEASTLの特徴を記載します。

  • メモリ固定確保のコンテナ (fixed_) : 動的確保を行わず、固定長の要素数を持つコンテナの宣言が可能です。グローバル宣言すれば、コンパイル時に固定的にメモリ領域が確保され、ローカル宣言すればスタックエリアに確保されそのスコープ内で利用できます。

  • Intrusive コンテナ:通常のコンテナは任意のデータ構造を格納できますが、Intrusiveコンテナはデータ構造に対して専用の基底クラスを継承することで、コンテナ内のリンク構造などを維持するためのリンク情報などを保持します。コンテナ内の各要素はそのコンテナ専用になりますが、リストやマップ構造では非常にメモリ利用効率の良くなります。(参考: Intrusive and non-intrusive containers

2007年の記事EASTL (open-std.org)に開発動機などが記されています。(関連記事:EASTL から垣間見るゲームソフトウェア開発現場の現状 その 1, その 2)

TWENETでの利用

以下に留意してください。

当社ではライブラリの動作については包括的な検証は行っておりません。お客様での動作検証をお願いいたします。また EASTL の利用方法についてのお問い合わせについても当社では対応できません。配布元の開設資料・ライブラリソースコードなどの情報を参照してください。

  • EASTL3.07 (2018/1/31) のバージョンを利用します。(C++11でコンパイルできる一番最後のバージョン)

  • 以下のライブラリは組み込んでいません。

    • test/packages/EAAssert, source/assert.cpp

    • test/packages/EATest

    • test/packages/EAThread, source/thread_support.cpp

  • テストコード test/source の動作移植はしていません。

  • _sprintf_関連ではEA::StdC::Vsnprintf(char8_t*, ...) のみを printf.h ライブラリ中の vsnprintf_() を呼び出すことで解決しています。

組み込み・コンパイル方法

EASTL はアクト Act の記述の際に利用できます。

TWELITE 向けの開発環境で必要なインクルードパスの追加、ライブラリの追加は行います。作成するコード中でライブラリヘッダのインクルードを行ってください。

組み込み方法(詳細)

ライブラリのコンパイルやインクルードパスの設定は、MWSDK/TWENET 以下のディレクトリで実施済みですが、内部的な設定を以下に記載します。

  • EASTL/source 内のコードをコンパイルして、ライブラリアーカイブとしておく (libEASTL.a)。リンク時にはこのライブラリの参照が必須です。

  • コンパイル時に以下のインクルードパスを追加しておく。

$(PATH_EASTL)を EASTL ディレクトリとした場合、インクルードパスは以下となります。

コーディングについて

std::eastl:: について

MWXライブ内部でも std:: 名前空間の標準ライブラリを利用しています。

標準ライブラリ (std::) と EASTL(eastl::)では同じ名前で同じ機能を持つものが定義されています。これらは混在できる場合もありますが、使用するとエラーになる場合もあります。つまりEASTLで使用するものは、通常はEASTL内の定義を用います(例:std::unique_ptreastl::fixed_string を格納しようとするとコンパイラエラーになります)。

またusing namespace std;といったような記述を行う場合は、名前の衝突に注意してください。

グローバルオブジェクトの初期化1 (配置new)

TWENET の開発では、コンパイラの制約により、グローバル宣言のオブジェクトのコンストラクタが実行されません。グローバル宣言宣言したオブジェクトのメモリ領域がゼロクリアされるだけです。そのまま、コードを実行すると大抵の場合 null pointer access によりハングアップします。

このオブジェクトを初期化するためには*placement new (配置new)*を用います。

placement new のコードは少し乱雑に見えるため、補助関数mwx::pnew() を用意しています。先ほどの例を以下のように書き換えることができます。

※ 2番目の引数以降は可変数で、コンストラクタにそのまま渡されます。

グローバルオブジェクトの初期化2 (unique_ptr)

グローバルオブジェクトの初期化方法として unique_ptr(std::unique_ptrの解説)を用いる方法もあります。unique_ptrstd:: にも eastl:: にもありますが、EASTLのクラスではeastl::のものを使用します。

以下のように初期化のタイミングで .reset() を呼び出します。

intrusive コンテナについて

以下の例は intrusive_list の要素定義例です。メンバーは int mX のみです。

intrusive_listの要素は、必ず intrusive_list_node を基底クラスに持っている必要があります。基底クラス内にはリストを維持するためのリンクポインタが含まれます。ここではさらに sortなどで使用する比較演算子の定義も行います。

参考資料

本サンプルについて

EASTLのライセンス記述は以下です。

Modified BSD License (3-Clause BSD license) see the file LICENSE in the project root.

サンプルコードは MWSLA-1J/E を適用します。

コード例

fixed_vector

最大長が固定された(つまり拡張しない)配列。 (※ mwx::smplbufも同様に最大長固定の配列ですが、MWXライブラリの内部処理に一部特化しています)

fixed_vector のテンプレート引数は3つあり、1番目が型、2番目が最大数、3番目は_false_にします。 配列の操作については、一般の std::vector と類似した .puch_back().pop_back()[]演算子などが利用できます。

また、ソートアルゴリズムなどの適用も可能です。上記の例では eastl::sort を昇順 eastl::less にて行っています。

fixed_list

最大エレメント数が固定されたリスト構造(intrusive_list についても要参照)。

fixed_list のテンプレート引数は3つあり、1番目が型、2番目が最大数、3番目は_false_にします。リストの操作については、一般の std::list に類似した .insert(), .erase() などが利用できます。

上記のコードでは、リストにはペア eastl::pair の要素を格納し、ペアの first を uint8_t 型の整数、second を void (*)(uint8_t) の関数ポインタとしています。コード中はラムダ式を直接記述しています。コード中の x.second(x.first); は second から得られる関数に対して first の値を与えるという意味合いになります。

このリストに対して eastl::find_if による要素の検索を行ったり、bubble_sortによるソートが可能です。

intrusive_list

通常のリストは任意のデータ構造を要素とできますが、intrusive_list は要素に特定のデータを付与し、そのデータを用いることでデータ構造を構築します。

以下の例では、intruslve_list データ構造の要素となるには eastl::intrusive_list_node を継承したデータ要素型であることを要件とします。eastl::intrusive_list_nodeには前後の要素に対するポインタを格納できるような拡張です。

この例で eastl::fixed_vector<> が使用されているのは、IntNode の要素を必要数確保する目的で、fixed_vectorが必要だったわけではありません。5つの要素にテストの値を格納して intrusive_list を構築します。例では l.push_pront() を呼び出し要素をひとつずつリストに格納しています。実際は格納するのではなく、各要素 IntNode が持つポインタの繋ぎ変えです。

ソートの記述は l.sort() のようにメンバ関数の呼び出しで行います。

ring_buffer

リングバッファ ring_buffer は、他のコンテナ(例では fixed_vector) との組み合わせで構築されます。

ring_buffer の定義は、要素型とそのコンテナ型の組み合わせです。要素型は余分に一つ要素を持たせておきます。

上記の例では .push_front() で先頭に要素を挿入します。オーバーフローすると末尾は消えてしまいます。 .back()により一番古い要素を取り出します。pop_back()で一番古い要素を削除します。

intrusive_hash_map

マップ構造は、キーと値をもつデータ構造で、キー値により効率よく要素を抽出できるように設計されたデータ構造です。 intrusive_hash_map は intrusive 構造とハッシュ値を用いて実装しています。いくばくか定義は煩雑ですが、メモリ消費量は抑制できます。

intrusive_list と同様独自の要素型 IntNodeeastl::intrusive_hash_node_key<要素型> を継承した形で定義する必要があります。またハッシュを用いるためハッシュ最大値 (N_BUCKET_CT) を定めておく必要があります。この値は、想定される格納要素数に応じて適切に素数の値を設定します。

上記の例は、マップのキーを uint8_t 型の1文字とし、マップの値部分を関数ポインタとします。loop()ではキーの入力に応じた関数を実行するといった処理を行います。

最初に、グローバルオブジェクトとしてテーブルと要素を定義したため、setup()中で mwx::pnew() を呼び出すことでデータ要素(nd_a, nd_b, nd_c, nd_d)の初期化、ハッシュマップの初期化 (mp) を行っておきます。mwx::pnew() の戻り値は、構築したオブジェクトへのポインタですので、初期化後直接メンバ変数に値(ラムダ式)を書き込んでいます。

要素(nd_a, nd_b, nd_c, nd_d)の初期と値の設定が終わったら mp.insert(nd_a) のようにマップに要素を挿入していきます。

loop()ではシリアルから文字を入力するたびに、ハッシュマップの検索を行います。検索は mp.find() メソッドを呼び出し、戻り値はイテレータで、検索失敗時は mp.end() が戻ります。検索が成功したら (*it) により検索出来た要素を参照できます。

intrusive_hash_multimap は値の重複を許容するマルチマップです。利用方法はハッシュマップとほぼ同じですが、検索時に .equal_range() 用い、イテレータのペアとして取り扱う点が違います。

最終更新