ユーザ データ

 
 
 

[Custom]: ICENodes は、Context::PutUserData を使用して追加でき、Context::GetUserData を使用して取得できるユーザ データをサポートしています。 CValue でサポートされるすべてのデータ型は、ユーザ データ型として使用できます。 ユーザ データは、通常、BeginEvaluate コールバックから設定されるため、BeginEvaluate で割り当てられるメモリはすべて EndEvaluate コールバックを対象に、スケルトン コードを生成します。 また、カスタム ノードが作成される際に Init コールバックからユーザ データを設定することもできます。この場合、Term コールバックを使用して、Init で割り当てられるメモリを解放する必要があります。 BeginEvaluate または Init に保存されるユーザ データは、常に Evaluate コールバックによってパケットが並列処理されるマルチスレッド コンテキストで行われます。 ただし、カスタム ノードのスレッド モードがシングル スレッドでない限り、Context::PutUserData プロパティを Evaluate コールバックから使用することはできません。

複数のスレッドを使用する場合に重要となる点は、1 つまたは複数のスレッドでユーザ データを修正する必要があるときに発生する競合の回避です。 ユーザ データへの書き込みアクセスは、スレッド ロックと同期する必要があります。 不注意で複数のスレッドからのユーザ データに重複した修正を加えてしまうと、あらゆる種類の問題が発生することが考えられます。 ロックは基本的な同期メカニズムであり、複数のスレッドによる共有データへのアクセスを可能にします。スレッドにデータ アクセスのロックがある場合、同じデータにアクセスする必要がある他のスレッドもそのロックが解放されるまで停止されます。

ロックはデータ共有を安全に行うためのメカニズムとなりますが、その一方で、カスタム ICENode の実行速度が遅くなる可能性もあります。一度に 1 つのスレッドしか、そのユーザ データにアクセスできなくなるためです。 ロックの問題を解決するには、スレッド セーフ バッファを使用します。 このテクニックでは、Softimage によって作成された各評価スレッドのユーザ データのコピーを含むバッファを作成します。各スレッドはそれぞれのデータを使用して動作するので、コピー データへのアクセスで競合が発生することはありません。 スレッド セーフ バッファは、BeginEvaluate コールバックに作成する必要があります。 バッファのサイズは、ICEContextNode::GetEvaluationThreadCount によって返される評価スレッドの数で設定しなければなりません。 場合によっては、特定の評価でスレッドの数が増加することがあるので、作成したセーフ スレッド バッファが常に新しい数のスレッドに拡張可能であるようにしてください。 Init コールバックはスレッドセーフ バッファの作成に使用できないため、ロックを Evaluate コールバックで使用してデータ アクセスの競合を回避する必要があります。

注:

CValueArray を使用して C ポインタを保存しないでください。CValue オブジェクト(siPrtType タイプ)は 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;
}