Custom ICENodes support user data which can be added with Context::PutUserData and retrieved with Context::GetUserData. Any data type supported by CValue can be used as user data type. User data is typically set from the BeginEvaluate callback, and any memory allocated in BeginEvaluate must be released in the EndEvaluate callback. You can also set the user data from the Init callback when the custom node gets created, in which case the Term callback must be used for releasing any memory allocated in Init. The user data stored in BeginEvaluate or Init is always accessible from the Evaluate callback. However, unless the custom node threading mode is single-threading, the Context::PutUserData property cannot be used from the Evaluate callback.
One important issue when using multiple threads is to avoid conflicts when one or more threads need to modify the user data. Write access to the user data must be synchronized with thread locks. If you're not careful, overlapping modifications to user data from multiple threads may cause all sort of problems. Locking is a fundamental synchronization mechanism that allows multiple threads to access shared data: when a thread holds a lock to access the data, other threads that need to access the same data are halted until the lock is released.
While using locks is a safe mechanism for sharing data, it may slow down the execution of your custom ICENode since only one thread at a time can access the user data. Locks can be overcome by using a thread safe buffer. This technique consists of creating a buffer containing a copy of the user data for each evaluation thread created by Softimage, and since each thread works with their own data copy data access conflicts will not happen. Thread safe buffers must be created in the BeginEvaluate callback. The size of the buffer must be set with the number of evaluation threads returned by ICEContextNode::GetEvaluationThreadCount. In some cases the number of threads may increase for a given evaluation, therefore you should always make sure your safe thread buffer can be extended to the new number of threads. The Init callback cannot be used for creating thread-safe buffers and locks must be used in the Evaluate callback to avoid data access conflicts.
Avoid using CValueArray to store C pointers, the CValue objects of type siPrtType are not supported by CValueArray.
The following example demonstrates how to set and get user data from BeginEvaluate using the technique of thread safe buffers:
// 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; }
Except where otherwise noted, this work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License