Thread Safe Plug-Ins
See Also: Main Plug-In Classes.
Overview
Plug-ins that relate to the renderer have specific methods that need to be thread safe. This is because the renderer launches several threads of execution when rendering an image. Therefore the same method could get called from several threads at the same time. If one of the methods is writing a value that the other is reading or writing this can interfere with valid values being accessed.
Plug-in materials, texture maps and atmospheric effects are the plug-in types that have methods that need to be = 4) BSPSPopupOnMouseOver(event);;">reentrant. Any method of these plug-ins that is called while rendering, like Texmap::EvalColor(), Mtl::Shade(), Atmospheric::Shade(), etc. must be written in a way that is thread safe.
The methods listed above can be re-entered from another thread while the renderer is processing. For example, while Texmap::EvalColor() was executing it could get called again, totally asynchronously. At a certain time this method could be changing a variable, and another thread could be changing that same variable at the same time. Or it could be setting the value while another thread is attempting to read it. This can interfere with valid values being set or read.
The main rule for class variables is if a method changes a variable it won't be thread safe unless it takes specific steps to ensure it is. Any variables in the class must not be written to without taking steps. This is because both threads can see the same variable and could write to it at the same time.
There are conditions where a plug-in does NOT have to do anything to be thread safe. If the method just reads variables, it will be thread safe. Any local variables (those on the stack) can be read and written without concern. Any global variables can also be read without concern. If a method does all of its work on the stack then it will be thread safe.
How To Ensure a Function is Thread Save
The code fragments below are from the 3ds max fog atmospheric effect. The code for this plug-in can be found in the SDK in \MAXSDK\SAMPLES\HOWTO\MISC\FOG.CPP. The synchronization object used to make the method thread safe here is called CRITICAL_SECTION. Developers can look in the Win32 API for documentation for this and other synchronization objects.
First a developer declares a CRITICAL_SECTION. This is a data type that is declared, initialized, and deleted when one is finished with it. When the class is created, like in the class constructor this object is initialized. Below are a pair of code fragment from the Fog plug-in where the CRITICAL_SECTION is declared and initialized:
This creates the CRITICAL_SECTION object.
class FogAtmos : public StdFog {
public:
CRITICAL_SECTION csect;
...
}
Here it is initialized in the constructor.
FogAtmos::FogAtmos() {
...
InitializeCriticalSection(&csect);
...
}
In code where data that is accessible to several threads is to be modified, you call a Win32 API named EnterCriticalSection() and pass the CRITICAL_SECTION object.
For example, thread A enters the code and EnterCriticalSection() is called. This sets a state in the CRITICAL_SECTION data structure. Now thread B enters the same section of code. It also calls EnterCriticalSection() and passes in the CRITICAL_SECTION object. The CRITICAL_SECTION object knows that another thread is already in that code. Therefore it will sit and wait until thread A finishes and calls LeaveCriticalSection(). When thread A calls LeaveCriticalSection() this will trigger thread B that it may return from EnterCriticalSection(). At this point it can then enter the code. Thus bracketing code with EnterCriticalSection() and LeaveCriticalSection() will make the code thread safe.
Below is a fragment from a method of the fog plug-in that is made thread safe using this technique.
void FogAtmos::UpdateCaches(TimeValue t)
{
EnterCriticalSection(&csect);
if (!valid.InInterval(t)) {
valid = FOREVER;
pblock->GetValue(PB_COLOR,t,fogColor,valid);
...
}
LeaveCriticalSection(&csect);
}
Note that at the beginning of the method EnterCriticalSection() is called. Just before it exits LeaveCriticalSection() is called.
When a plug-in is done with the CRITICAL_SECTION object, it can call DeleteCriticalSection(). Here this is done in the destructor for the Fog plug-in:
~FogAtmos() {
DeleteCriticalSection(&csect);
}
Developers should also be aware of the following two points:
1 Critical sections, or any other synchronization objects, should only surround the part of the code that requires synchronization. Otherwise, if the critical section surrounds a large block of computationally-expensive code unnecessarily, performance will suffer on multiprocessor machines.
As an example, if an algorithm needs to write to a class variable, do a long computation, then write to another class variable, it would be bad to structure the code like this:
Enter Critical Section
write to var1
do long computation...
write to var2
Leave Critical Section
This structure stops any other processor from accessing this routine until the long computation is done. It would be better to structure the code like this:
Enter Critical Section
write to var1
Leave Critical Section
do long computation...
Enter Critical Section
write to var2
Leave Critical Section
2 Synchronization problems often don't show up until you are running on a multi-processor system. All plug-in developers should test their code on multi-processor systems as much as possible. Intermittent crashes or erratic behavior is often a symptom of non-reentrancy.
Summary
3ds max is a multi-threaded application and plug-ins that run inside 3ds max where multiple threads are used need to be thread safe. This section has presented the data structures and calls from the Windows Win32 API used to create thread safe code.