カスタム ICENode は、Context::PutUserData を使用して追加し、Context::GetUserData を使用して取得できるユーザ データをサポートしています。CValue によってサポートされるデータ タイプであればどのようなものでも、ユーザ データ タイプとして使用できます。ユーザ データは通常、BeginEvaluate コールバックから設定され、BeginEvaluate で割り当てられたメモリはすべて、EndEvaluate コールバックで解放される必要があります。カスタム ノードの作成時に、Init コールバックからユーザ データを設定することもできます。この場合、Init に割り当てられたメモリを解放するには、Term コールバックを使用する必要があります。BeginEvaluate または Init に保存されたユーザ データは、常に Evaluate コールバックからアクセスすることができます。ただし、カスタム ノードのスレッド モードがシングルスレッドでない限り、Context::PutUserData プロパティを Evaluate コールバックから使用することはできません。
複数のスレッドを使用する場合に重要となる点は、1 つまたは複数のスレッドでユーザ データを修正する必要があるときに発生する競合の回避です。 ユーザ データへの書き込みアクセスは、スレッド ロックと同期する必要があります。 不注意で複数のスレッドからのユーザ データに重複した修正を加えてしまうと、あらゆる種類の問題が発生することが考えられます。 ロックは基本的な同期メカニズムであり、複数のスレッドによる共有データへのアクセスを可能にします。スレッドにデータ アクセスのロックがある場合、同じデータにアクセスする必要がある他のスレッドもそのロックが解放されるまで停止されます。
ロックはデータ共有を安全に行うためのメカニズムとなりますが、その一方で、カスタム ICENode の実行速度が遅くなる可能性もあります。一度に 1 つのスレッドしか、そのユーザ データにアクセスできなくなるためです。 ロックの問題を解決するには、スレッド セーフ バッファを使用します。 このテクニックでは、Softimage によって作成された各評価スレッドのユーザ データのコピーを含むバッファを作成します。各スレッドはそれぞれのデータを使用して動作するので、コピー データへのアクセスで競合が発生することはありません。 スレッド セーフ バッファは、BeginEvaluate コールバックで作成する必要があります。バッファのサイズは、ICEContextNode::GetEvaluationThreadCount によって返される評価スレッドの数で設定しなければなりません。 場合によっては、特定の評価でスレッドの数が増加することがあるので、作成したセーフスレッドバッファが常に新しい数のスレッドに拡張可能であるようにしてください。Init コールバックを使用してスレッド セーフ バッファを作成することはできず、Evaluate コールバックでロックを使用してデータ アクセスの競合を回避する必要があります。
CValueArray を使用して C ポインタを保存することは避けてください。タイプ siPrtType の CValue オブジェクトは CValueArray でサポートされていません。
以下の例では、スレッド セーフ バッファのテクニックを使用して BeginEvaluate からユーザ データを設定し、取得する方法を示しています。
// For storing CSampleData user data objects #include <vector> // Simple user data struct struct CSampleData { CSampleData() : nLen(0), pBuffer(NULL) {} ~CSampleData() { if (pBuffer) delete [] pBuffer; } LONG nLen; XSI::MATH::CVector3f* pBuffer; }; XSIPLUGINCALLBACK CStatus TestCustomICENode_BeginEvaluate( ICENodeContext& in_ctxt ) { CValue userData = in_ctxt.GetUserData( ); ULONG nThreadCount = in_ctxt.GetEvaluationThreadCount( ); std::vector<CSampleData>* pPerThreadData = NULL; if ( userData.IsEmpty() ) { pPerThreadData = new std::vector<CSampleData>; in_ctxt.PutUserData( (CValue::siPtrType)pPerThreadData ); } else { // Reuse the user data buffer if already created. pPerThreadData = (std::vector<CSampleData>*)(CValue::siPtrType)in_ctxt.GetUserData( ); } if ( pPerThreadData && pPerThreadData->size() < nThreadCount) { // Extend buffer if needed for(ULONG i = (ULONG)pPerThreadData->size(); i < nThreadCount; i++) { // Create a CSampleData object for each thread pPerThreadData->push_back( CSampleData() ); } } return CStatus::OK; } XSIPLUGINCALLBACK CStatus TestCustomICENode_Evaluate( ICENodeContext& in_ctxt ) { // Get the user data that we allocated in BeginEvaluate std::vector<CSampleData>* pPerThreadData = (std::vector<CSampleData>*)(CValue::siPtrType)in_ctxt.GetUserData( ); // The user data array is indexed by the current thread ID ULONG nThreadID = in_ctxt.GetCurrentThreadIndex( ); CSampleData& userData = (*pPerThreadData)[ nThreadID ]; Application().LogMessage( L"User data thread("+CString(nThreadID)+L"): " + CString( userData.nLen ) ); // Processing code goes here ... return CStatus::OK; } XSIPLUGINCALLBACK CStatus TestCustomICENode_EndEvaluate( ICENodeContext& in_ctxt ) { // Release memory allocated in BeginEvaluate CValue userData = in_ctxt.GetUserData( ); if ( userData.IsEmpty() ) { return CStatus::OK; } std::vector<CSampleData>* pPerThreadData = (std::vector<CSampleData>*)(CValue::siPtrType)in_ctxt.GetUserData( ); delete pPerThreadData; // Clear user data in_ctxt.PutUserData( CValue() ); return CStatus::OK; }