Important Threading Information

Many parts of Cinema 4D are internally threaded. This means that whenever your plugin is called by Cinema 4D there might be restrictions on what is safe to do, e.g. bringing up GUI interfaces or accessing common data.

Note
Failing to observe threading limitations can lead to unpredictable, severe and hard-to-find bugs.

Drawing Pipeline Thread

All parts of the execution/drawing pipeline of Cinema 4D is threaded. This means that all calls to TagData::Draw(), ShaderData::Draw(), SceneHookData::Draw(), SceneHookData::Execute(), TagData::Execute(), ObjectData::GetVirtualObjects(), ObjectData::Draw() etc. are made from a thread.

Other threads (like the manager redraws) access this data at the same time. That is why no scene modifications of any kind must be made in those routines. For example inserting an object into the scene is enough to trigger a crash!

As an exception, modifications are allowed that change the object's parameters like position, or anything set through NodeData::SetDParameter(). However this should only be done in Execute() by tags (expressions) and objects on EXECUTIONPRIORITY_EXPRESSION. In later stages of a generator object (e.g. EXECUTIONPRIORITY_GENERATOR) for example, this will lead to caches being rebuild constantly or mismatching data in Attribute Manager.

A common problem is that you need to update/add materials in ObjectData::GetVirtualObjects(). That is the wrong place! Firstly, a redraw must never be triggered from within the redraw thread. Secondly, adding materials belongs (due to the scene access) into the user interaction part. For example, the materials needed for scene imports should be created already when the user imports the scene.

ObjectData::GetVirtualObjects() is of course allowed to do ANY modifications that do not modify the scene: as the object returned is not in the scene at that time it may be changed/created in any way necessary.

Forbidden Functions

For all threaded functions it is forbidden to:

  • Add an Event.
  • Make any changes to materials.
  • Change the structure of objects attached to the scene.
  • Change parameters of elements attached to the scene (allowed, but not recommended except for tags and objects following above rule (EXECUTIONPRIORITY_EXPRESSION)).
  • Call a Draw() function.
  • Perform any GUI functionality (e.g. displaying messages, opening dialogs etc.).
  • During drawing to do any file operations. (During execution it is allowed.)
  • Create undos.

Using Core Messages

If user interaction is still necessary within a threaded function, there is a workaround using messages. Examples of where this can be warranted is for demo limitation messages, or messages forced by an external application. It should usually be avoided. You should also check if NET is used, since otherwise you could block the entire rendering by waiting for the non-existing user to respond.

The workaround consists of having the threaded function send a message to the plugin so that the main thread can execute the GUI parts:

const Int32 DEMO_MESSAGE_ID = 1016549;
class DemoMessage : public MessageData
{
public:
virtual Bool CoreMessage(Int32 id, const BaseContainer& bc)
{
switch(id)
{
case DEMO_MESSAGE_ID:
MessageDialog("Demo message");
}
return true;
}
};
Bool RegisterDemoMessage()
{
return RegisterMessagePlugin(DEMO_MESSAGE_ID,
"Demo Message", 0,
NewObj(DemoMessage));
}
void DoDemoMessage()
{
SpecialEventAdd(DEMO_MESSAGE_ID, 0, 0);
}
void ThreadedFunction()
{
...
DoDemoMessage()
...
}

Rendering Thread

Additional restrictions apply for functions that are called during rendering, e.g. ShaderData::Output() and MaterialData::CalcSurface(), since they can be run on multiple processors. No OS calls are allowed in these function.

Scene Modifications

Before making modifications in the active scene, for example from a dialog, you always need to call StopAllThreads(). You have to do this even if you yourself are in the main thread, since there could be other threads that read from the scene.