12. 複製オブジェクトの基本機能

複製オブジェクト機能とは、ゲームオブジェクトの通信処理を自動的に行う機能です。 アプリケーションは、ゲーム中で扱うオブジェクトのパラメータを変更するだけでライブラリが自動的に必要な送受信処理を行います。

複製オブジェクト DO の操作、管理、移行について説明します。 複製オブジェクト定義言語(以下、DDL)については 15. を参照してください。

12.1. DO の作成

NetZ の API には DO をインスタンス化または破棄するためのメソッドがあります。 デフォルトでは DO がインスタンス化されると、ローカルオブジェクトは DO マスターとして初期化され、 リモートステーションに作成された DO はデュプリカとして初期化されます。 リモートでオブジェクトを作成するには、RMC を実装する必要があります。

また、DuplicatedObject::Emigrate メソッドと DuplicatedObject::AttemptEmigration メソッドを使用して、 オブジェクトの DO マスターをステーション間で移行できます。 プレイヤーがすでに進行中のセッションに参加する場合、 セッションの既存のすべての DO のデュプリカが新しいプレイヤーのステーションに自動的に作成されます。

オブジェクトは、DuplicatedObject::Create または DuplicatedObject::CreateWellKnown のいずれかを呼び出す 2 通りの方法で作成できます。 どちらのメソッドも、オブジェクトをグローバルにパブリッシュします。 セッションが作成される前に WellKnown オブジェクトが検出されることも保証されます。 オブジェクトはグローバルにパブリッシュされますが、デュプリカのデータセットすべてが各ステーションで更新される必要があるわけではありません。 ステーションフィルタを使用して、どのステーションでどの情報が更新されるかを制御できるので、 帯域幅使用の削減や特定のデータセットにセキュリティ制限を実装するのに役立ちます。 ステーションフィルタ更新ポリシーの詳細については、 15.1. を参照してください。

ユーザーは、次の宣言を使用して DDL ファイルに宣言されるすべての DO クラスを実装する必要があります。

Code 12.1 クラスの実装
class ObjectName : public DOCLASS(ObjectName) {
// User specified variables and methods
// ユーザー指定の変数とメソッド
};

実装されると、Create メソッドと Publish メソッドを使用して DO が DO クラス内で直接作成およびパブリッシュされ、 DDL コンパイラで生成された DO クラスをそのオブジェクトにインスタンス化します。 NetZ は、DDL で宣言された各 DO クラスに Create メソッドと Publish メソッドを生成します。 Create メソッドはオブジェクトの DO マスターを作成し、Session クラスの CreateSession メソッドまたは JoinSession メソッドのいずれかの前に呼び出されることができますが、 オブジェクトがパブリッシュされるまでは Create メソッドで返されるポインタを使用して参照されるだけです。 DO がパブリッシュされると、オブジェクトが必要なセッションに接続されている全リモートステーションまたは セッションに参加しているすべてのステーションによって自動的に検出され DOHandle または Ref を使用して参照されます。 IsPublished メソッドは、オブジェクトがパブリッシュされているかどうかを調べるために使用できます。 オブジェクトがパブリッシュされている場合、メソッドは真を返し、パブリッシュされていない場合は偽を返します。 DuplicatedObject::Create メソッドには次の構文がありますが、DuplicatedObject* Create(DOID idUserDefinedID) メソッドは特定のクラスで定義されます。

Code 12.2 DO を作成するメソッド
static DuplicatedObject* Create(DOID idUserDefinedID);
static DuplicatedObject* Create(DOClassID idDOClass,
                                qUnsignedInt32 uiTimeout = WAIT_INFINITE);
static DuplicatedObject* Create(DOClassID idDOClass, DOID idUserDefinedID);
static DuplicatedObject* Create(DOHandle &dohHandle);

ここでは、idUserDefinedID はユーザー定義 ID です。 このパラメータはオプションで、指定されると作成された DO には、 NetZ の自動 DOID 生成メカニズムで指定された ID ではなく、指定された DOID が割り当てられます。 場所にかかわらず、同じクラスにある各 DO には固有の DOID が割り当てられる必要があります。

idDOClass はオブジェクトの DOClassID です。

uiTimeout はシステムが待機する最長時間です。

dohHandle はオブジェクトの DOHandle です。

また、オブジェクトの DOClassID として、その DOHandle はプロジェクトの再コンパイル時、 DDL ファイルの変更時、NetZ の新しいバージョンへのアップグレード時に変更されることがあり、 データベースで DOHandle を使用するのではなく DOID を保管し、取得する方が安全です。 そのため、Avatar が DDL ファイルで doclass として宣言されている場合、 Avatar オブジェクトは次のいずれかの方法で作成およびパブリッシュできます。

Code 12.3 Avatar オブジェクトの作成およびパブリッシュ
// 1. Let NetZ assign the IDs for the object.
{
    Avatar* pNewAvatar=Avatar::Create();
    pNewAvatar->Publish();
}

// 2. Assign the DOID.
{
    ID idNewAvatar=0;
    IDGenerator::Ref refIDGenerator(Avatar::GetIDGenerator());
    if (refIDGenerator->GenerateID(&idNewAvatar, 0)) {
        Avatar* pNewAvatar=Avatar::Create(idNewAvatar);
        pNewAvatar->Publish();
    } else {
        return false;
    }
}

// 3. Assign the DOClassID and let NetZ assign the DOID.
{
    DuplicatedObject* pNewAvatar=DuplicatedObject::Create DOCLASSID(Avatar));
    pNewAvatar->Publish();
}

// 4. Assign both the DOClassID and DOID.
{
    ID idNewAvatar=0;
    IDGenerator::Ref refIDGenerator(Avatar::GetIDGenerator());
    if (refIDGenerator->GenerateID(&idNewAvatar, 0)) {
        Avatar* pNewAvatar=(Avatar*)DuplicatedObject::Create(
                    DOCLASSID(Avatar), idNewAvatar);
        pNewAvatar->Publish();
    } else {
        return false;
    }
}

// 5. Assign a DOHandle.

// Assume there is a database from which we
// save/retrieve information.

DOHandle hPreviouslySavedAvatar = ReadHandleFromDatabase();
Avatar* pNewAvatar=(Avatar*) DuplicatedObject::Create(hPreviouslySavedAvatar);
// Initializes the attributes of pNewAvatar with the
// info saved previously in the database, and then publish
// the new avatar.
pNewAvatar->Publish();

例えば、SphereZ の例で球体を作成するには、次のようにまず NetZ オブジェクトをインスタンス化し、 セッションを作成し、その後 Sphere DO を作成およびパブリッシュします。

Code 12.4 Sphere Duplicated Object の作成およびパブリッシュ
// Initialize NetZ
NetZ* pNetZ = new NetZ();

// Create a session
Session::CreateSession();

// Create and publish a Sphere Duplicated Object
Sphere* pNewSphere = Sphere::Create();
pNewSphere->Publish();

12.2. DO マスターの権限

大部分の期間、各 DO は 1 つの DO マスターと一定数のデュプリカを持ちます。 ただし、 DO マスターが移行する場合、短期間 2 つの DO マスターが存在します。 Figure 12.1 に示すように、NetZ では各オブジェクトに常に DO マスターが必要なので、 マイグレーション中、移行する DO マスターはマイグレーションが成功し、 新しい DO マスターが別のステーションに存在することの確認を受信するまではデュプリカにならないためです。 そのため、マイグレーション中の特定の時間中、2 つの DO マスターが同じオブジェクトに存在することになります。 ただし、いずれの時点でも各ステーションは DO マスターが 1 つ存在すると認識しているため、 2 つの異なるステーションではマスターが別のステーションにあるとみなすことがあります。 これは、マイグレーションをステーションに通知するメッセージを送信するのに一定の時間がかかることと、 レイテンシのため、すべてのステーションが同時にこの情報を受信するのではないためです。

2 つの DO マスターの存在は呼び出しの競合を引き起こす可能性があるため、 NetZ では 2 つの DO マスターの可能性がある DO マスターに権限のコンセプトを追加し、 オブジェクトに対する権限を同時に持たせるようにします。 Figure 12.1 に示すように、 移行元の DO マスターがメッセージを移行先のステーションに送信すると、移行元はオブジェクトへの権限を放棄して、 2 つめのステーションのオブジェクトが DO マスターになると、そのステーションが権限を受けます。マイグレーションが失敗すると、 マイグレーションが成功しなかったことの確認を受信すると移行元の DO マスターは権限を再び引き受けます。 そのため、マイグレーションの進行中、短期間(レイテンシによって異なる)は、オブジェクトに権限がなく、特定の呼び出しができなくなります。 DuplicatedObject::HasAuthority メソッドは DO マスターに権限があるかどうかを返します。 権限を持つ DO マスターには、移行し、オブジェクトの主要な参照を削除する権限があります。 また、開発者は権限のコンセプトにその他の制限を追加することができます。 例えば、固有の ID を持つオブジェクトを生成する工場オブジェクトがゲームにあるとします。 2 つのオブジェクトが同じ ID で作成されないようにするため、権限のある DO マスターのみにオブジェクトの作成が許可されるようにします。 メソッドが権限のある DO マスターのみで呼び出されるようにするため、呼び出しの DOCallContext 内では TargetObjectMustHaveAuthority フラグが使用されます。 詳細については、 12.9. を参照してください。

_images/Fig_DO_Basic_ObjectStateTransaction_In_Migration.png

Figure 12.1 オブジェクトマイグレーション中のオブジェクト状態とオブジェクト権限の移動

12.3. DO の操作

DuplicatedObject クラスには、特定のオブジェクトのプロパティを指定する際に使用できるメソッドがいくつかあります DO が DO マスターかデュプリカかを調べるには、 IsADuplicationMaster メソッドと IsADuplica メソッドが使用できます。 オブジェクトが WellKnown オブジェクトかどうかを調べるには、IsAWellKnownDO メソッドが使用できます。 特定のオブジェクトが属しているクラスに関する情報を取得するには、IsA メソッドと IsAKindOf メソッドを使用して、 オブジェクトが特定のクラスのメンバーか、特定のクラスを継承するかどうかを調べることができます。 また、GetClassID メソッドと GetClassNameString メソッドは、それぞれオブジェクトのクラスの ID と名前を返します。 IsAUserDO メソッドと IsACoreDO メソッドは、 それぞれ DO が DDL のユーザーに宣言されているユーザー定義のオブジェクトかどうか、コアオブジェクトかどうかを返します。 コアオブジェクトは、セッションの作成時、またはステーションの参加時または離脱時に NetZ によって作成される DO です。 このオブジェクトは NetZ を正常に動作させるために必須のオブジェクトで、すべてのグローバルおよび非グローバルの DO 、 StationSessionClockIDGenerator の各クラス、またフォルトトレランスのメカニズムに関係するシステムクラスのルートクラスを含みます。

IDGenerator クラスは、オブジェクトまたはゲームの他のエンティティに割り当てられる固有な ID の生成に使用できます。 IDGenerator インスタンスによって生成された ID は固有であることが保証されていますが、2 つの IDGenerator インスタンスが同じ ID を生成することがあります。 IDGenerator の一般的な使用は、上述のとおり、NetZ で ID を自動的に割り当てるのではなく、作成時に DO に指定できる特定の ID を生成することです。 ただし、このクラスが DO の ID の生成に使用される場合、ID が生成される各 DO クラスにこのクラスのインスタンスが作成される必要があります。 IDGenerator が使用される場合、ID を生成する範囲を SetIDRange で設定してから、GenerateID メソッドを呼び出して ID を生成する必要があります。 ステーションがセッションに参加すると、セッションがそのステーションで始まる前に、 すべての IDGenerator オブジェクトがステーションによって検出されることが保証されています。

DO は DO のローカルインスタンスを参照する DOHandle を使用してネットワーク全体で参照されます。 NetZ は、セッションが作成または参加された後 DO が作成されるか、 パブリッシュされるかのいずれか早いほうが起こったときに DOHandle を生成します。 DO の DOHandle はオブジェクトのクラス ID である DOClassID と、その複製オブジェクト ID である DOID で構成されます。 DOHandle のこの 2 つの部分の値は、それぞれ GetDOClassID メソッドと GetDOID メソッドで返されます。 DOClassID は 各 DO クラスの固有の識別子で、DDL に宣言される各 DO クラスに DDL コンパイラによって自動的に生成されます。 反対に DOID は、NetZ によって自動的に生成されることも、ユーザーによって割り当てられることもでき、特定の DO クラス内のみで固有です。 DOID がユーザーによって設定される場合、ユーザーは各 DO クラス内で、各 DOID が固有であることを保証する必要があります。 DOHandle は、RMC またはアクションの引数として、またはデータセットとしてネットワーク全体に渡すことができます。 DuplicatedObject::GetHandle メソッドは DOHandle を DO のローカルインスタンスに返すのに使用されますが、 オブジェクトがパブリッシュされた後にしか呼び出せません。 ハンドルで参照されているオブジェクトが WellKnown オブジェクトであるかどうかを調べるには、IsAWKHandle メソッドを使用します。 DuplicatedObject クラス内と同様、特定のハンドルが参照するオブジェクトのクラスに関する情報を取得するには、 IsA メソッドと IsAKindOf メソッドを使用して、オブジェクトが特定のクラスのメンバーであるか、または特定のクラスを継承するかどうかを調べ、 IsAUserDO と IsACoreDO を使用してハンドルがユーザー定義またはコア DO を参照しているかどうかを調べます。 また DOHandle::GetClassNameString メソッドを使用して、特定の DOHandle が参照する DO のクラス名を表す文字列を取得できます。

ローカルステーションのみのオブジェクトを参照するには、Ref を作成できます。 これは、オブジェクトの任意のメソッドやメソッドのグループを呼び出すのに使用できる、オブジェクトへの直接参照を作成します。 参照は、メソッドが実行される前に作成され、ブロックが終了すると破棄されます。 ブロックとは、{ } のペアで区切られているコードのセクションのことです。 存在しないオブジェクトを参照しないようにするため、NetZ では、 適切にスレッドモードを使用している場合、参照するオブジェクトはブロックが終了するまでは削除されないように保証しています。 そのため、オブジェクトへの参照をできるだけ短時間保持するようにすることが重要です。 オブジェクトへの参照の作成方法には DOHandle をオブジェクトに指定する方法と、参照をオブジェクトに指定する方法があります。 デフォルトのコンストラクタも使用でき、オブジェクトは代入演算子を使用して Ref に割り当てられます。 その後 DORef::IsValid メソッドを使用して、構成物が正しく機能することを確認します。 参照が有効であるかどうかは DORef::Poll メソッドと DORef::Wait メソッドを使用して確認できます。 Wait では、タイムアウト時間が過ぎるまで継続して有効性を確認します DORef::GetHandle メソッドと DORef::GetDOPtr メソッドは、 それぞれ参照を構築するのに使用された DOHandle と特定の DO へのポインタを返すのに使用できます。 例えば、オブジェクトの DOHandle を指定して MySphere オブジェクトのメソッドをローカルに実行するには、次のコードを使用します。

Code 12.5 DOHandle を指定して MySphere オブジェクトのメソッドをローカルに実行する
{
    Sphere::Ref refMySphere(hMySphere);
        if (refMySphere.IsValid()){
            // call a method
            refMySphere->method();
        }
}

または、次の 2 つのどちらかの方法で、オブジェクトへの参照を指定して参照を作成することもできます。

Code 12.6 オブジェクトへの参照を指定して参照を作成する
// create a Ref using the default constructor and then assign it a value
Sphere::Ref refToASphere;
RefToASphere=refMySphere;

// or create a Ref by specifying a Ref to an object
Sphere::Ref refToASphere(refMySphere);

DO は DuplicatedObject::DeleteMainRef() を使用して削除します。 このメソッドは、 DO マスターのみが呼び出すことができ、パブリッシュされていないオブジェクトでも呼び出すことができます。 DeleteMainRef は DO ストアに格納されている DO への主要参照を削除します。 これがオブジェクトへの唯一の参照の場合、デストラクタが呼び出され、 DO マスターとそのすべてのデュプリカがこの呼び出しによって削除されます。 オブジェクトへの他の参照がある場合、最後の参照がリリースされるまではデストラクタは呼び出されません。 例えば、RefTemplate を使用して作成されたオブジェクトへの参照がある場合、オブジェクトのこの参照が削除されるまではオブジェクトのデストラクタは呼び出されません。 DuplicatedObject::MainRefIsDeleted メソッドは DO の主要参照が削除されているかどうかを調べるのに使用できます。 オブジェクトが削除される際に行われるイベントの完全なシーケンスについては、 14.1. の RemoveFromStore の操作の箇所を参照してください。 例えば、MySphere オブジェクトを破棄する場合、次のコードを使用します。

Code 12.7 MySphere オブジェクトの破棄
{
    Sphere::Ref refMySphere(hMySphere);
    refMySphere->DeleteMainRef();
}

ステーションがセッションを離脱すると、NetZ はステーションのすべての DO マスターに DuplicatedObject::ApproveEmigration() を呼び出し、 オブジェクトを別のステーションに移行し、存続させるかどうかを決定します。 このメソッドが DO に偽を返すと、NetZ はオブジェクトの主要参照を自動的に削除します。 同様に、障害のためにステーションがセッションを離脱する場合、そのステーションの DO に DuplicatedObject::ApproveFaultRecovery() が呼び出されます。 このメソッドが DO に偽を返すと、NetZ はオブジェクトの主要参照を自動的に削除します。

NetZ は、実行中に特定の各 DuplicatedObject クラスにキャスティング(明確な型変換)のための DynamicCast メソッドを定義します。 このメソッドは、標準 C++ の dynamic_cast 演算子と同様に機能しますが、NetZ のランタイム情報を使用します。 例えば、Duplicated3DObject クラスを継承する Avatar クラスがあり、Duplicated3DObject クラスはルートの DuplicatedObject クラスを継承するとします。 pObj が Avatar オブジェクトをポイントする場合、次を使用して Avatar オブジェクトへのポインタを返すことができます。

Code 12.8 DynamicCast メソッドによる Avatar オブジェクトへのポインタの取得
Avatar *pAvatar = Avatar::DynamicCast(pObj)

12.4. WellKnown オブジェクト

WellKnown オブジェクトは、セッションに参加する全ステーションに共通の事実上のグローバルオブジェクトです。 通常これは、ゲーム環境や、プレイヤーがセッションに参加する前に検出する必要のある他のオブジェクトなどのことです。 通常 WellKnown オブジェクトはコンピュータで制御されるオブジェクトで、作成されると、 DO マスターがセッションマスターに常駐します。

DO と WellKnownDO の違いは、 WellKnown オブジェクトはセッションの作成前に作成され、 セッションセッションマスターに作成される必要があることです。 ステーションがセッションに参加すると、Session::JoinSession メソッドがそのステーションに返る前に、 ステーションがすべての WellKnown オブジェクトを検出することが保証されています。 また、 WellKnown オブジェクトはデフォルトではフォルトトレラントで、 DO マスターのあるステーションに障害が発生するか、 セッションから離脱すると、新しいステーションに移行します。

WellKnown DO は DDL(15.1.) に定義され、 セッションの作成前にセッション内の WellKnown インスタンスの数がわかっているようにする必要があります。 例えば、 WellKnown オブジェクトの World とグローバル変数の g_hTheWorld を次のように DDL に定義します。

Code 12.9 WellKnown オブジェクト World とそのグローバル変数の定義
doclass World {
};
wellknown World g_hTheWorld;

WellKnownDO が DDL で宣言されると、WKHandle 型のグローバル変数が DDL コンパイラで生成されます。 WKHandle 変数は WellKnown オブジェクトを参照します。 この例では、g_hTheWorld が生成された WKHandle グローバル変数です。 WKHandle インスタンスは DOHandle クラスを継承します。

DDL で指定されているすべての WellKnownDO は、セッションが作成される前に構築され、パブリッシュされる必要があります。 これは、Session::CreateSession が呼び出される前です。 WellKnownDO は、DuplicatedObject::CreateWellKnown メソッドを使用して DO と同様に構築されます。 このメソッドは、新しく作成された DO へのポインタを返します。 ポインタは DuplicatedObject::Publish が呼び出されるまで使用でき、その後ポインタは NetZ に属し、 セッション中にオブジェクトが作成および削除できるようにいつでも無効にされる可能性があります。 WellKnownDO の DO マスターは、セッションマスターであるステーションにある必要があります。 そのため、CreateWellKnown メソッドは、CreateSession メソッドを呼び出すステーションのみで有効です。 SphereZ で World の作成とパブリッシュを行うには、次のコードを使用します。

Code 12.10 World の作成とパブリッシュ
{
    World* pNewWorld;
    pNewWorld = World::CreateWellKnown(g_hTheWorld);
    pNewWorld->Publish();
}

DO が WellKnown オブジェクトかどうかを調べるには、DuplicatedObject::IsAWellKnownDO メソッドが使用できます。 このメソッドは、呼び出し先のオブジェクトが WellKnown である場合には真を返し、そうでない場合は偽を返します。

12.5. イテレータ

DO でローカルに呼び出され、実行されるメソッドは、標準の C++ プログラム技術を使用して実装されます。 特定のオブジェクトにメソッドを実行するには、オブジェクトへの参照を作成し、 12.1. で示したようにメソッドを直接そこに呼び出します DO のグループにメソッドを呼び出すには、イテレータを使用します。

NetZ では、各ステーションにすべての DO のリストと、全 DO マスターと全 DO デュプリカを含むこのリストの 2 つのサブセットを保持しています。 これらのリストには SelectionIteratorTemplate クラスを使用してアクセスできます。 このクラスを使用すると、オブジェクトの特定のクラスを繰り返すことができます。 すべての DO 、すべての DO マスター、またはすべての DO デュプリカを保持する 3 つの異なる選択イテレータを構成できます。 デフォルトの構成では、すべての DO を保持するイテレータを作成します。 GotoStart、GotoEnd、EndReached、Count の各メソッドをイテレータループ内で使用して、 それぞれイテレータのポインタをオブジェクトリストの最初のオブジェクトまたは最後のオブジェクトにポイントし、 リストの最後尾を示し、リストのすべてのオブジェクト数を返します。

例えば次のように、Sphere クラスのすべての DO マスターに DoControl メソッドを呼び出す場合、 すべての DO マスターを保持する選択イテレータを作成し、イテレータリストの最初の DO マスターをポイントし、 繰り返しリストの最後に来るまでリストのすべてのオブジェクトにメソッドを呼び出します。

Code 12.11 すべての DO マスターで DoControl メソッドを呼び出す場合
Sphere::SelectionIterator itDMSphere(DUPLICATION_MASTER);
    itDMSphere.GotoStart();
        while(! (itDMSphere.EndReached() ));
            itDMSphere->DoControl();
            itDMsphere++;
        }

同様に、セッションのすべてのステーションをリストするには、次のようにします。

Code 12.12 セッションのすべてのステーションをリストする
Station::SelectionIterator   itStations;
    itStations.GotoStart();
        while (!itStations.EndReached()) {
            itStations->Trace();
        }

12.6. データセット

ユーザーは、次のように、DDL ファイルで宣言されたすべてのデータセットクラスを実装し、 データセットが DDL で生成された DATASET クラスを継承することも宣言する必要があります。

Code 12.13 データセットクラスの実装
class DatasetName : public DATASET(DatasetName) {
    // User specified variables and methods
};

例えば、SphereZ の例の Position データセットと SphereData データセットは、次のように DDL で宣言されます。

Code 12.14 SphereZ の Position, SphereData データセットの宣言
dataset Position {
    double x;
    double y;
    double z;
} extrapolation_filter, unreliable;

dataset SphereData {
    uint16 m_ui16Texture;
} upon_request_filter;

そのため、ユーザー指定のクラスの Position と SphereData は次のように実装されます。 ここでは、Set メソッドはユーザー実装の一部です。

Code 12.15 SphereZ の Position, SphereData データセットの実装
class Position : public DATASET(Position) {
    void Set(qDouble dx, qDouble dy, qDouble dz);
};

class SphereData : public DATASET(SphereData) {
    Set (qUnsignedInt16 ui16Texture);
};

データセットはパラメータとしてコンテナを取ることができます。 NetZ で使用されるコンテナの種類は membervector、memberlist、memberqueue で、 コンテナに使用できる型は 15.1. に説明されているすべての simple DDL データ型と有効なユーザー定義型です。 コンテナを使用する場合、その種類に従って、削除、追加、挿入、繰り返しなどの操作がコンテナのデータセットメンバーで実行できます。 NetZ では、このような操作を行うために使用できるメソッドは MemberVector クラス、MemberList クラス、 MemberQueue クラスにあり、C++ 標準ライブラリの標準コンテナに使用できるメソッドと同様です。 例えば、NetZ メソッドの Pop_Back はコンテナの最後の要素を削除し、標準 C++ メソッドの pop_back と同様に機能します。 コンテナのイテレータが宣言される方法は、次に示される構文を使用し、標準の技法とは多少異なります。 MyContainer データセットが次のように DDL に宣言されるとします。

Code 12.16 MyContainer データセットの宣言
dataset MyContainer {
    membervector<real> vec;
};

その後、次の構文を使用してこのコンテナのイテレータを作成します。

Code 12.17 MyContainer のイテレータの作成
MemberVectorIterator<DDLTYPE(qReal)> it;

12.6.1. データセットの伝播

データセットの内容は、信頼性のあるチャンネルまたは信頼性のないチャンネルでネットワークに送信できますが、DDL ファイルに定義されているデータセットの更新ポリシーで設定されます。 更新ポリシーに関する説明については、 15.1. を参照してください。 DataSet::ReliableUpdate メソッドは、特定のデータセットの更新が信頼性のあるチャンネルか信頼性のないチャンネルで送信されるかを調べるのに使用できます。

信頼性のあるチャンネルを利用していて、複数本のReliableの利用の初期化を行っている場合には、DataSet::SetSubStreamID により、Update時に送信に利用するSubStreamIDを指定できます。 サブストリームの初期化設定は、 8.3.4. を参照にしてください。 システムと共有の SubStreamIDDefine::SYSTEM (=0) 以外のメッセージは、 BundlingPolicy による メッセージバンドリングが無効です。ただし、 StreamBundlingで設定されるストリームごとのパケットバンドリングは有効です。

DO のデータセットの内容を DO マスターからそのデュプリカに伝播するには、DuplicatedObject::Update メソッドを使用します。 このメソッドはオブジェクトの DO マスターで呼び出され、特定のデータセットまたは DO のすべてのデータセットで呼び出すことができます。 データセットへの変更は常に DO マスターからデュプリカに行われるので、メソッドは DO マスターで呼び出される必要があります。 Update メソッドは仮想メソッドです。 そのため、オブジェクトが基本クラスのポインタにキャスティングされている場合、Update は基本クラスで呼び出され、基本クラスと継承クラスの両方のオブジェクトのデータセットが更新されます。 Update が呼び出されると、データセットがバッファされていない場合はデュプリカのデータセット変数が直接更新されますが、データセットがバッファされている場合はデュプリカのバッファが更新されます。 データセットがバッファされている場合、バッファの内容を DO のデュプリカに転送するために、DuplicatedObject::Refresh メソッドがデュプリカで呼び出される必要があります。 このメソッドは特定のデータセットまたはデュプリカのすべてのバッファされているデータセットのどちらかで呼び出すことができます。 DDL で buffered DDL プロパティがデータセットに宣言されている場合、データセットがバッファされます。 また、extrapolation_filter DDL プロパティが特定のデータセットに宣言されている場合、データセットを更新するために DuplicatedObject::Refresh メソッドを呼び出す必要があります。 これは、推定が使用されている場合、推定モデルを保管するために特別のバッファが使用されるためです。

DO のデータセットで推定フィルタを使用する場合、推定されているデータの予測モデルを維持するため、データセットの内容が変更されていなくても Update メソッドを呼び出す必要があります。 メソッドの呼び出しが必要な頻度は、データセットが変更される頻度によって異なりますが、ほとんどのゲームで各 物理計算 ループに 1 回の呼び出しで十分です。 データセットが upon_request_filter を使用する場合、Update メソッドを使用してデータセットの更新を呼び出しても、その前に DataSet::RequestUpdate メソッドが呼び出されない限り、ネットワークメッセージは生成されません。 そのため、データセットの更新が必要なときに、毎回 RequestUpdate メソッドを呼び出す必要があります。 upon_request_filter が使用される場合、データセットが更新される条件はユーザーが定義できます。 例えばユーザーは、特定の変数が特定の程度で変更すると更新が行われるように定義できます。

主に帯域幅の最適化、またはセキュリティ上の理由、またはこの両方の理由により、station_filter データセットプロパティは、すべてのデュプリカではなくデュプリカのサブセットでデータセットが更新できるようにします。 このフィルタを設定すると、DataSet::UpdateIsRequired システムコールバックを実装して、特定のデータセットが更新されるステーションを指定する必要があります。 このシステムコールバックは、特定のデータセットが更新されるたびに呼び出され、更新するステーションを決定します。 ステーションフィルタを使用する 2 つの主な理由は、更新(帯域幅)の最適化とセキュリティ制限を適用するためです。 最初の理由では、ステーションフィルタを使用して不要な情報をネットワークに送信するのを回避できます。 例えば、遠くにあるオブジェクトの場合、オブジェクトは点として示されるだけなのでほとんどの場合そのオブジェクトの向きを知る必要はなく、データセットを更新する必要がなくなり、帯域幅を節約できます。 2 つめの理由では、データをパブリック、セミパブリック、プライベート、その他の希望する任意の分類に定義し、 それに従って特定のデータが更新されるステーションを制限できます。 例えば、アバターの位置はすべてのプレイヤーに知らせる必要があるので、これをパブリックにしてすべてのステーションで更新されるようにします。 自分のアイテムに関するデータは協定ギルドメンバーのみに知らされる必要があるので、このデータはセミプライベートにしてギルドメンバーのステーションのみに更新します。 次の例では、まったく共有されないか、サーバーのみで共有されるように実装する方法を示します。

Code 12.18 サーバーのみで共有される DataSet::UpdateIsRequired システムコールバック
qBool UpdateIsRequired(const DuplicatedObject * pDO, const Station * pStation)
{
    Avatar( pDO );
    // Only update this DataSet if the Station is a server.
    return pStation->GetProcessType()==Station ::ServerProcess;
}

コンテナは、MemberContainer::SetContentUpdate と SetOperationUpdate を使用する、内容モードまたは操作モードの 2 つのモードのいずれかで更新できます。 内容モードでは、Update が呼び出されるたびにコンテナの全内容が各デュプリカに更新され、操作モードでは最終更新時以降に行われた操作が送信され、デュプリカに再度適用されます。 操作モードが使用されている場合、ユーザーの責任で、データが標準のコンテナ操作でアクセスできるようにして操作がデュプリカに再度適用できるようにする必要があります。 Update メソッドが、コンテナで構成されるデータセットで呼び出されると、通常は帯域幅の使用を削減するためにデータセットメンバーに行われた変更のみが DO マスターからそのデュプリカに伝播されます。 ステーションがセッションに参加する際には、この更新方法が採用されず、すべてのデータセットメンバーが更新されます。 この場合 DO の検出時にコンテナの完全な更新がすべてのデュプリカに送信されます。 また、コンテナメンバーで実行される操作のリストがコンテナ内のメンバー数より大きい場合、完全な更新が送信されます。

12.6.2. メッセージバンドル

ネットワーク上を送信され DO のためのメッセージは BundlingPolicy クラスを使用してまとめられます。 この場合、各メッセージを個別に送信するのではなく、メッセージをグループで送信して帯域幅の使用を削減できます。 BundlingPolicy クラスは BundlingPolicy::GetInstance メソッドでアクセスでき、BundlingPolicy オブジェクトへのポインタを返します。

メッセージバンドルはデフォルトで有効になっており、BundlingPolicy::Enable メソッドと BundlingPolicy::Disable メソッドで実行時にそれぞれ有効または無効にすることができます。 メッセージバンドルが有効になっているかどうかは、BundlingPolicy::IsEnabled メソッドが返します。 メッセージバンドルが有効になっている場合、ローカルステーションでメッセージバンドルは、ローカルステーションが認識している各リモートステーションに作成されます。 その後、特定のリモートステーションに送信されるすべてのメッセージは、特定のリモートステーションに関するメッセージバンドルに保管されます。 Reliable通信場合、システムと共有の SubStreamIDDefine::SYSTEM (=0) 以外のメッセージは、バンドリングされません。

メッセージのバンドルは自動または手動で送信できます。 バンドルが自動的に送信される場合、送信される頻度は時間遅延、またはバンドルの最大サイズを指定して設定します。 これらの 2 つの条件のいずれかを満たすと、メッセージのバンドルが送信されます。 時間遅延は、BundlingPolicy::SetMaximumFlushDelay メソッドを使用して設定され、2 つのバンドルの送信される間隔の最大時間です。 デフォルトでは、時間遅延が0に設定されており、Dispatchごとに、送信されるようになっております。通常はこの設定のままの利用を推奨します。

時間遅延は BundlingPolicy::GetMaximumFlushDelay メソッドで取得できます。 最大バンドルサイズは 1250バイトの固定値が使用されます。 メッセージのバンドルがこのサイズに達すると、バンドルが送信されます。

メッセージのバンドルを手動で送信するには、Station::FlushBundle メソッドまたは Station::FlushAllBundles メソッドを使用します。 Station::FlushBundle が呼び出されると、その Station に関連付けられているローカルステーションのメッセージバンドルのすべてのメッセージが送信されます。 同様に、Station::FlushAllBundles が呼び出されると、ローカルステーションのすべてのメッセージバンドルの全メッセージが送信されます。 これらの 2 つのメソッドは、時間遅延と最大バンドルサイズの設定に関係なくメッセージのバンドルを送信します。

12.7. DO のマイグレーション

NetZ では、セッションに参加しているステーション間で移行できるように DO 、つまり DO マスターの制御が可能です。

DO マスターが移行すると、デュプリカから見る唯一の変化は DO マスターがあり、更新が送信されるステーションが変わっていることです。 オブジェクトのマイグレーションは、システムで自動的に実行することも、開発者が任意のときに手動で行うこともできます。一般的には、以下のケースで使用されます。

  1. オブジェクトが障害時にも存続するようにするフォルトトレランス
  2. DO マスターオブジェクトを分散し、処理負荷と帯域幅を分散するための負荷分散
  3. オブジェクトとの対話中(例: バトル中)にそのオブジェクトの DO マスター(例: ノンプレイヤーキャラクター(NPC))をローカルステーションに移行して、レイテンシの影響をマスクする

最後のケースでは、NPC オブジェクトをマイグレーションすると、衝突などのイベントが常にローカル(マスターオブジェクト間)で計算されるため、常に正確に表示されます。

マイグレーションは DO のコントロールが別のステーションに移転されるプロセスです。 マイグレーションには、エミグレーションとイミグレーションの 2 つの操作が関係します。 エミグレーションとは、ステーションが DO のコントロールを別のステーションに移転するプロセスのことで、 イミグレーションは、あるステーションでコントロールされていた DO のコントロールを別のステーションが受け入れるプロセスです。 ステーションがセッションを終了する際に DO を自動的に移行するには、DuplicatedObject::ApproveEmigration システムコールバックを使用します。 このシステムコールバックが真を返すと、 DO マスターのあるステーションがセッションを離脱すると、 DO マスターはセッションの別のステーションに移行します。 オブジェクトの移行先のステーションは NetZ によって決定されます。 コールバックが偽を返すと、オブジェクトは移行できず、その DO マスターがあるステーションがセッションを終了すると、存在しなくなります。 オブジェクトのデフォルト動作では、偽を返します。 開発者がオブジェクトを移行するステーションを制御する必要がある場合、 ステーションがゲームを終了する前にオブジェクトに DuplicatedObject::AttemptEmigration メソッドを呼び出す必要があります。 次のコードは、Sphere オブジェクトがあるステーションから別のステーションに自動的に移行して、 ステーションがゲームを終了してもオブジェクトが存続するようにする DuplicatedObject::ApproveEmigration システムコールバックの実装を示します。

Code 12.19 ApproveEmigration システムコールバックの実装例
Sphere: public DOCLASS(Sphere)
{
    public:
        Sphere();
        virtual ~Sphere();

        qBool ApproveEmigration(qUnsignedInt32 uiReason)
        {
            if (uiReason==MIGRATION_REASON_LEAVING_SESSION)
            {
                return true;
            } else {
                return false;
            }
        }
    // Game specific methods and variables
};

オブジェクトがあるステーションから別のステーションに移行する際、ChangeMasterStationOperation システムオペレーションが実行され、 各ステーションにあるオブジェクトに関する情報が正しいことを保証します。 システムオペレーションは移行するオブジェクトのコピーを持つすべてのステーションで呼び出されます。 システムオペレーションの詳細と、その他のシステム処理については、 14.3. を参照してください。

マイグレーション中、NetZ はすべての関連ネットワーク情報が新しいオブジェクトマスターに転送されるように保証しますが、 オブジェクトによっては、そのデュプリカがまったく把握していない追加データ(例: 物理計算 や AI モデル)へのアクセスがマスターにある場合があります。 ゲームを継続するため、 DO マスターにこの追加情報が必要な場合、この情報が新しい DO マスターに転送されることを保証する必要があります。 情報の転送方法と転送される情報の内容は、オブジェクトとその要件によって大きく異なります。 例えば、オブジェクトの目標位置、方向、動作を送信して、新しいマスターがオブジェクトの動作を継続して正しく計算できるようにする場合があります。

マイグレーションが完了する前にステーションがセッションを終了すると、オブジェクトは間違ってシステムからなくなってしまいます。 これを回避するために、NetZ ではマイグレーションの実行中、 オブジェクトのマイグレーションが完了するまではマイグレーションに関係する両方のステーションがセッションを終了できないようにします。

12.8. フォルトトレランス(障害回復機能)

フォルトトレランスは、実行中のセッションへほとんどまたはまったく影響することなく、ステーションの切断などの障害から回復する機能です。 ステーションに障害が発生することはありますが DO は何事もなかったかのように存在し続けます。 ステーションの切断は、プレイヤーがセッションを終了したか、ネットワーク、電源、またはその他の障害のために起こる場合があります。 NetZ では、セッションマスターのステーションに、 DO マスターに障害が発生したフォルトトレラント DO のコントロールを引き受ける能力を与えることでフォルトトレランスを実装します。 ステーションに障害が発生すると、障害のあるステーションからのフォルトトレラントオブジェクトのすべてのデュプリカがセッションマスターの DO マスターに昇格します。 セッションマスターに障害が発生すると、NetZ は新しいセッションマスターを自動的に選択します。 特定のステーションに障害が発生すると、負荷を再度分散するためにオブジェクトが移行する場合があります。 そのため、ステーションに障害が発生することはありますが DO は何事もなかったかのように存在し続けます。

DO はデフォルトではフォルトトレラントではありません。 オブジェクトをフォルトトレラントにするかどうかは選択できます。 オブジェクトをフォルトトレラントに定義するには、DuplicatedObject::ApproveFaultRecovery システムコールバックを使用します。 コールバックが真を返すと、オブジェクトはフォルトトレラントです。 偽を返すと、オブジェクトはフォルトトレラントではなく、 DO マスターのあるステーションに障害が発生すると、存在しなくなります。

次のコードでは、Sphere オブジェクトがフォルトトレラントであることを指示する ApproveFaultRecovery システムコールバックメソッドの実装を示します。

Code 12.20 ApproveFaultRecovery システムコールバックの実装例
Sphere:: public DOCLASS(Sphere)
{
    public:
        Sphere();
        ~Sphere();

        qBool ApproveFaultRecovery()
        {
            return true;
        }
    // Game specific methods and variables
};

反対に、障害が発生していないのにステーションがセッションを終了すると、DuplicatedObject::ApproveEmigration システムコールバックが真を返す場合以外は、 ステーションがセッションを終了するとステーションの DO マスターは破棄されます。 このコールバックを使用すると、 DO マスターのコントロールがステーション間で移行されるようにします。 コールバックは、オブジェクトが移行する理由を返す必要があります。 NetZ では、この理由はセッションを離脱するためのマイグレーション(MIGRATION_REASON_LEAVING_SESSION)のみです。 コールバックが真を返すと、オブジェクトはステーション間で移行でき、偽を返すとオブジェクトは移行できず、 DO マスターのあるステーションがセッションを離脱すると、存在しなくなります。 そのため、 DO マスターのあるステーションが、障害またはセッション終了のどちらの理由でセッションを離脱したかにかかわらず、 オブジェクトを存続させる場合は、ApproveFaultRecovery と ApproveEmigration の両方のシステムコールバックが真を返す必要があります。 例えば、 DO マスターのあるステーションがゲームを終了した場合にも、障害が発生した場合にも Spheres を存続させるには、次のコードを実装します。

Code 12.21 障害が発生した場合でもオブジェクトを存続させるコード
Sphere:: public DOCLASS(Sphere)
{
    public:
        Sphere();
        ~Sphere();

        qBool ApproveFaultRecovery()
        {
            return true;
        }
        qBool ApproveEmigration(qUnsignedInt32 uiReason)
        {
            if (uiReason==MIGRATION_REASON_LEAVING_SESSION)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    // Game specific methods and variables
};

ステーションがすべての機能を失うのではなく、参加しているセッションへの接続のみを失う障害の場合(停電ではなく、ステーションへの LAN 接続が無効になる場合など)、 セッションは障害が発生しているステーションで実行し続けることが可能です。 このような状況の場合、同じセッションに実質上 2 つのインスタンスを持つようにします。 両方のセッションに他のステーションが参加することはできますが、2 つのセッションが相互のセッションに再参加することはできません。

ステーションに障害が発生すると、PromotionReferee システムオブジェクトは、類似性レベルを用いてフォルトトレラントオブジェクトが移行するステーションを決定します。 障害またはその他の理由により、フォルトトレラントオブジェクトの DO マスターがあるステーションがセッションを終了すると、 オブジェクトのデュプリカを持つ各ステーションは ComputeAffinityCallback を使用して PromotionReferee を呼び出し、 そのオブジェクトのステーションの類似性レベルとそのステーションが新しい DO マスターになろうとしていることを示します。 デフォルトでは、類似性のもっとも高いステーションが新しい DO マスターになります。 各プロセスのデフォルトの類似性レベルは、サーバー = 100、クライアント = 50、ツール = 0 です。 さらに、セッションマスターにはボーナスの 10 ポイントが与えられ、デフォルトで未知プロセスはクライアントプロセスであるとみなされています。 そのため、オブジェクトはクライアントプロセスやツールプロセスではなく、サーバープロセスに優先的に移行されます。 同じ種類の複数のプロセスが PromotionReferee を呼び出し、プロセスのいずれもがセッションマスターでない場合は、 PromotionReferee を呼び出した最初のステーションが新しい DO マスターに選ばれます。

PromotionReferee のデフォルト動作は、ComputeAffinityCallback を使用して変更できます。 このコールバックは、特定のステーションの特定のオブジェクトの類似性と、 新しい DO マスターを指定するまでに特定のステーションが PromotionReferee を待機させる時間を指定します。 デフォルトの待機時間は、別のプロセスでオブジェクトを制御する必要があるのでツールプロセスには 1000 ms が指定され、 クライアントプロセスには 500 ms、サーバープロセスには 0 ms になっています。 そのため、サーバープロセスが孤立したオブジェクトの制御を希望する旨を PromotionReferee に通知すると、呼び出しがすぐに返されます。 このコールバックは、RegisterAffinityCallback メソッドを使用して PromotionReferee の特定のステーションで呼び出される必要があります。 例えば、孤立したオブジェクトを、サーバープロセスがある限り常にサーバープロセスに指定されるようにするには、次のコードで実装できます。 この場合、オブジェクトは特定のサーバープロセスにランダムに指定され、特定のサーバーがすべてのオブジェクトを制御し、 オーバーロードの可能性を引き起こすことを回避しています。 当然サーバーの負荷を分散するため、負荷分散もサーバークラスタ全体に実装できます。

Code 12.22 サーバープロセスが存在する限りサーバープロセスに指定するコード
void ServerTakeOverAllObjects(DOHandle hObject, qByte* pbyAffinity,
                              TimeInterval* ptiWaitTime)
{
    // No matter what hObject is, we do the following. In a real
    // application, the actual affinity would probably depend on
    // the type of hObject.
    Station::Ref refLocal(Station::GetLocalStation());
    SYSTEMCHECK(refLocal.IsValid());
    if (refLocal->GetProcessType()==Station::ServerProcess)
    {
        // If I'm a server, then I select a random value between
        // 1 and 101. This is my affinity. Using a random value
        // will cause a uniform distribution of the objects on
        // the failed station over the servers.
        * pbyAffinity = 1 + static_cast<qByte>(Platform::GetRandomNumber(100));
        // Since this is a server process we use a low wait time
        // as it doesn't really matter which server we choose.
        * ptiWaitTime = 200;
    }
    else
    {
        // The station is not a server. We do not want this
        // station to take control of the object so we set
        // a low affinity.
        * pbyAffinity = 0;
        // And we set a long wait time as we want to wait for
        // a server to see the fault as well.
        * ptiWaitTime = 2000;
    }
}

ステーションが登録する ComputeAffinityCallback が有効であることを保証するため、PromotionReferee は特定のステーションに RegisterAffinityValidationCallback を呼び出すことができます。 このコールバックは、特定のオブジェクトの特定のステーションの類似性が有効であることを確認します。 例えば、サーバープロセスの有効な類似性レベルが 1 ~ 101 で、クライアントプロセスでは 0 であると仮定すると、次を実装できます。

Code 12.23 サーバープロセスの類似性レベルが 1 ~ 101 のとき、クライアントプロセスでは 0 と仮定
qByte DoubleCheckThatServerTakesOver(DOHandle hObject, DOHandle hCalleeStation,
                                     qByte byAffinity)
{
    // The callee station hCalleeStation proposes an affinity,
    // so we check if it is a valid value. Clients are cheating
    // if the value is not zero. Servers are cheating (or most
    // probably there is a bug in the callback that reports
    // affinity) if the value is not between 1 and 101.
    Station::Ref refCallee(hCalleeStation);
    SYSTEMCHECK(refCallee.IsValid());
    if (refCallee->GetProcessType()==Station::ServerProcess)
    {
        SYSTEMCHECK(byAffinity>0);
        return byAffinity;
    }
    else
    {
        // This is not a server.
        if (byAffinity > 0)
        {
            // Then the station must be cheating. At this point,
            // we could decide to flag the station as a cheater
            // and take action. We also report 0 as the affinity
            // and ignore whatever value the station claimed.
            return 0;
        }
        return byAffinity;
    }
}

障害の発生後にオブジェクトの DO マスターになるデュプリカに、PromotionReferee による指定に従って、 DO マスターとして引き継ぐオブジェクトに必要なすべての情報が含まれることを保証するかどうかは、 開発者が決定できます DO の移行時、NetZ はすべての関連ネットワーク情報が新しいオブジェクトマスターに転送されるように保証しますが、 オブジェクトによっては、そのデュプリカがまったく把握していない追加データ(例: 物理計算 や AI モデル)へのアクセスがマスターにある場合があります。 ゲームを継続するため、 DO マスターにこの追加情報が必要な場合、この情報が新しい DO マスターに転送されることを保証する必要があります。 情報の転送方法と転送される情報の内容は、オブジェクトとその要件によって大きく異なります。 例えば、オブジェクトの目標位置、方向、動作を送信して、新しいマスターがオブジェクトの動作を継続して正しく計算できるようにする必要がある場合です。 また、データセットにステーションフィルタを実装する場合、 15.1. に説明するように、 マスターとしてシームレスに引き継ぐために、昇格したデュプリカのデータセットが、オブジェクトに十分な程度に更新されていることを保証する必要があります。

12.9. 呼び出しコンテキスト

RMC を起動する、またはオブジェクトを移行するには、呼び出しコンテキストが RMCContext クラス、または MigrationContext クラスを使用して定義されている必要があります。 これらの 2 つのクラスは DOCallContext クラスを継承し、このクラスは CallContext クラスを継承します。 これらのクラスから、呼び出しコンテキストのターゲットステーションと適切なフラグを設定します。 また、呼び出しコンテキストの State や Outcome に関する情報を取得し、呼び出しコンテキストのキャンセルやリセットを可能にすることもできます。 なお、Outcomeに格納される結果のうち、処理が成功しているかどうかについては QSUCCEEDED マクロまたは qBool へのキャストで判定して下さい。

DOCallContext クラスを使用すると、表 8 1 に示すようなさまざまなフラグを設定して特定の呼び出し方法を制御できます。 SendUnreliableMessage、CallOnDuplicas、CallOnNeighbouringStations のフラグは、OneWayCall フラグが設定されているときにしか設定できません。 TargetObjectMustBeMaster フラグまたは TargetObjectMustHaveAuthority フラグのいずれかが RMC に設定されている場合、 呼び出しは特定のターゲットステーションに関係なく、 DO マスターのあるステーションに行われます。 これらの 2 つのフラグは、RMC が初めて呼び出される際にのみ真になることが保証されています。 そのため、CallMethodOperation::PostponeOperation メソッドを使用して呼び出しを延期すると、フラグは再度確認されず、真ではなくなります。 また、RetryOnTargetValidationFailure フラグはこれらの 2 つのフラグの一方とともにしか設定できません。 SynchronousCall フラグが設定されていない場合、デフォルト動作では、システムは同期方法で動作します。 そのため、呼び出しコンテキストが行われるスレッドは、値またはエラーが返るまで呼び出しを待機してから、次のタスクを実行します。

Table 12.1 DOCallContext フラグ
フラグ 機能
CallOnDuplicas メソッドはすべてのデュプリカで呼び出されます。RMCContext 専用。
CallOnLocalStation メソッドはローカルステーションで呼び出されます。RMCContext 専用。
CallOnMaster メソッドは DO マスターの RMCContext のみで呼び出されます。
CallOnNeighbouringStations メソッドはローカルステーションのすべての近隣ステーションで呼び出されます。RMCContext 専用。
CallOnTargetStation このフラグは直接設定できません DOCallContext::SetTargetStation が呼び出されると自動的に設定されます。
CancellationNotAllowed 呼び出しはキャンセルできません。MigrationContext 専用。
DeleteOnCompletition 呼び出しの CallContext は呼び出し完了と同時に削除されます。
OneWayCall 呼び出しは一方向です。つまり呼び出しは呼び出されたメソッドの結果を返しません。RMCContext 専用。
RetryOnTargetValidationFailure 呼び出しの結果が ErrorInvalidRole または ErrorNoAuthority の場合、呼び出し結果が Success になるか、ErrorInvalidRole または ErrorNoAuthority 以外のエラーが返されるまで呼び出しによって DO マスターへのメッセージの再送信が試行されます。RMCContext 専用。
SendUnreliableMessage 呼び出しは信頼性のないチャンネルから送信されます。RMCContext 専用。
SendUnbundledMessage 呼び出しのメッセージはバンドルされないので、できる限り早く送信されます。RMCContext 専用。
SynchronousCall 呼び出しは同期です。つまり、システムは呼び出しが値またはエラーを返すまで、次のタスクの実行を待機します。
TargetObjectMustBeMaster 呼び出しは DO マスターであるオブジェクトのみに行われます。RMCContext 専用。
TargetObjectMustHaveAuthority 呼び出しは権限のあるオブジェクトのみに行われます。RMCContext 専用。

応答が受信されると、呼び出しコンテキストの State と Outcome が変更されます。 呼び出しコンテキストが非同期で行われる場合、DOCallContext::Wait メソッドを使用してタイムアウトを設定し、DOCallContext::Wait の呼び出し元スレッドが次に進むまで呼び出しコンテストが処理されるのを待つ時間を指定できます。 タイムアウト時間が過ぎる前に応答が受信されると、Wait 呼び出しは終了します。 呼び出しコンテキストが同期方法で行われる場合、呼び出しコンテキストが開始されたスレッドは、呼び出しコンテキストからの応答を受信するか、エラーが発生するまで次に進まずに待機します。 事実上、これは非同期呼び出しの待機時間を無限に設定することと同じです。 DOCallContext::SetFlag メソッドと DOCallContext::ClearFlag メソッドを使用して、それぞれフラグの設定と消去ができ、DOCallContext::FlagIsSet メソッドは特定のフラグを設定するかどうかを指定するのに使用できます。 また、DOCallContext::SetTargetStation メソッドと DOCallContext::GetTargetStation メソッドを使用して、それぞれ呼び出しコンテキストのターゲットステーションの設定と取得ができます。 ターゲットステーションの重要性は、特定の呼び出しコンテキストによって異なります。 RMCContext では、ターゲットステーションは RMC が呼び出されるステーションで、MigrationContext ではオブジェクトを移行するステーションです。

Figure 12.2 に示すように、呼び出しコンテキストは Available、CallInProgress、CallSuccess、CallFailure、CallCancelled の 5 つの状態のいずれかになります。 呼び出しコンテキストは、最初は Available 状態です。 ユーザーによって呼び出されると、CallInProgress 状態になり、その後 CallSuccess、CallFailure、CallCancelled のいずれかの状態になります。 呼び出しコンテキストが CallInProgress 状態の場合、ユーザーは DOCallContext::Cancel メソッドを使用して呼び出しコンテキストをキャンセルでき、この場合の状態は CallCancelled になります。 状態が CallSuccess、CallFailure、CallCancelled のいずれかの場合、DOCallContext::Reset メソッドを呼び出し、状態を Available にリセットすることができます。 DOCallContext::GetState メソッドは呼び出しコンテキストの現在の状態を返します。 DOCallContext::Cancel が呼び出され DOCallContext のキャンセルが許可されない場合、移行呼び出しの場合と同じで、メソッドは呼び出しコンテキストが終了するまでロックされます。

_images/Fig_DO_Basic_State_RMCContext.png

Figure 12.2 呼び出しコンテキストの状態

呼び出しコンテキストの終了方法は、GetOutcome メソッドを使用して取得できます。 これは、以下の結果のいずれかになります。

  • Success: 呼び出しコンテキストが正常に終了する場合
  • ErrorInvalidRole: TargetObjectMustBeMaster フラグが設定されている場合に呼び出しコンテキストが DO デュプリカで呼び出される場合
  • ErrorObjectNotFound: オブジェクトが削除されているか、またはステーションに現在ないためにターゲットステーションにオブジェクトが見つからない場合
  • ErrorLocalStationLeaving: ローカルステーションがセッションから離脱している場合
  • ErrorStationNotReached: リクエストが送信され、ターゲットステーションに届かなかった場合
  • ErrorTargetStationDisconnect: リクエストが送信され、ターゲットステーションが反応を受信する前に切断された場合
  • ErrorInvalidParameters: 無効なパラメータで呼び出しが行われた場合
  • ErrorAccessDenied: 特定の呼び出しがターゲットで拒否された場合
  • ErrorNoAuthority: ターゲットオブジェクトで呼び出しを行う権限がない場合
  • ErrorRMCDispatchFailed: 呼び出しを開始しているステーションからリクエストを送信できなかった場合(RMC のみに有効)
  • ErrorMigrationInProgress: マイグレーション呼び出しが進行中の場合(マイグレーション呼び出しのみに有効)
  • ErrorCallTimeout: 設定されているタイムアウトまでに呼び出しが完了しなかったために呼び出しが失敗した場合
  • ErrorReliableSendBufferFull: 送信バッファが一時的にいっぱいになり、呼び出しができなかった場合
  • ErrorPacketBufferFull: パケットバッファが一時的に不足し、呼び出しができなかった場合
  • CallPostponed: 呼び出しが延期された場合
  • UnknownOutcome: 呼び出しコンテキストの結果が応答を返していないために不明である場合

CallContext クラスを使用すると、CallContext の完了と同時にユーザー定義のコールバックが呼び出されるようにできます。 完了のコールバックは、特定の CallContext が完了すると、特定の関数が確実に呼び出されるようにできる簡単で効率的な方法です。 メソッドが非同期的に呼び出されると、完了コールバックを使用することによって、完了しているかどうかを確認するために CallContext を連続してポーリングすることが回避できます。 複数のコールバックが登録されている場合、デフォルトでは登録された順番で保管され、呼び出されます。 この場合、最初に登録されたコールバックが最初に呼び出されます。 この順番を変更するには、コールバックの登録時に bAddToEnd パラメータを設定する必要があります。 完了コールバックは、RegisterCompletionCallback メソッドを使用して、特定の CallContext に登録され、次の 2 つの構文があります。

Code 12.24 RegisterCompletionCallback メソッドの構文
void CallContext::RegisterCompletionCallback(
                    CompletionCallback pfCompletionCallback,
                    const UserContext & oContext, qBool bAddToEnd=true);
void CallContext::RegisterCompletionCallback(CallbackRoot * pCallback,
                    qBool bCallOnSuccess=true, qBool bAddToEnd=true)

このメソッドは、CallContext の完了時に呼び出される CompletionCallback 関数のポインタの pfCompletionCallback か、CallbackRoot オブジェクトのポインタの pContext を取ります。 2 つめの構文を選択する場合、Callback テンプレートクラスを使用して CallbackRoot オブジェクトを定義する必要があります。

完了コールバック実装の例の一部を次に示します。 この例では、次のように最初に DDL に ChangeOwner RMC を宣言します。

Code 12.25 ChangeOwner RMC の宣言
// DDL declarations for Item class.
doclass Item : Duplicated3DObject {
    ItemOwnership m_dsOwner;
    bool ChangeOwner(int dohandle hCurrentOwner, int dohandle hNewOwner,
                     dohandle hTargetItemMaster);
};

次に、Inventory.cpp ファイルに ChangeOwner RMC を使用する PickUp メソッドを実装します。

Code 12.26 PickUp メソッドの実装
qBool Inventory::Pickup(RMCContext *pContext, qBool* pbResult, DOHandle hItem,
                        qBool bMigrate) {
    // We create a Callback to the method Inventory::AddItem.
    // This callback will be called when the RMC completes. The
    // CallChangeOwner method prepares and performs the actual
    // RMC call.
    return CallChangeOwner(pContext, pbResult, hItem, INVALID_DOHANDLE,
                           m_pContainer->GetHandle(), qNew Callback<Inventory,
                           DOHandle > (this, &Inventory::AddItem, hItem),
                           bMigrate);
}

そして、次のように RMC を呼び出します。 RMC が完了すると、Inventory::AddItem メソッドが呼び出されます。

Code 12.27 ChangeOwner RMC の呼び出し
qBool Inventory::CallChangeOwner(RMCContext* pContext, qBool *pbResult,
            DOHandle hItem, DOHandle hRequiredCurrentOwner,
            DOHandle hNewOwner, CallbackRoot* pCallback, qBool bMigrate)
{
    // Perform a series of checks to ensure that the call is
    // valid. Details are given in the framework source code.
    SYSTEMCHECK(m_pContainer!=NULL);
    if (!m_pContainer->IsADuplicationMaster()) {
        qDelete pCallback;
        return false;
    }
    Item::Ref refItem(hItem);
    if (!refItem.IsValid()) {
        qDelete pCallback;
        return false;
    }
    if (refItem->GetOwner()!=hRequiredCurrentOwner) {
        qDelete pCallback;
        return false;
    }

    // Once we've ensured that the call is valid we proceed to
    // the real call.
    pContext->RegisterCompletionCallback(pCallback);
    pContext->SetTargetStation(refItem->GetMasterStation());
    pContext->SetFlag(DOCallContext::TargetObjectMustHaveAuthority);
    return refItem->CallChangeOwner(pContext, pbResult, hRequiredCurrentOwner,
        hNewOwner, bMigrate ? m_pContainer->GetMasterStation():
                              INVALID_DOHANDLE);
}

12.10. ユーザー定義コンテキスト

UserContext クラスによって渡されたユーザー定義値は、unsigned integer、double、Boolean、オブジェクトへのポインタのいずれかになります。 通常は、NetZ では自動的に保持されない値で、ユーザーが維持することを望む値を渡すのに使用されます。 UserContext クラスは通常 Operation クラスと一緒に使用されますが、ゲームの他の任意のクラスと関連して使用できます。 例えば、ChangeMasterStationOperation が発生する場合、オブジェクトの元のセッションマスター ID は NetZ では維持されません。 この情報を維持するには、操作が始まる前に情報をユーザー定義データとして渡すと、操作終了後に取得できます。 ユーザー定義情報が操作のコンテキストに渡されると、 14.1. に示すように Operation クラスの SetUserData メソッドと GetUserData メソッドを使用する必要があります。