Register to object change
-
Can an
ObjectData
plugin react to the change of an object it created? I'm working in C++ on 2023.2.0.Situation
My
ObjectData
plugin creates a null and an IK-Spline tag among many other objects. The tag is not attached to the null in question.Requirement
Whenever the user changes the size of the null I need to update the Handle Depth of one of the handles in the IK-Spline tag. Is it possible to register to the change of an object?
What I've tried
The
Message
method of myObjectData
does not receive a relevant message upon changing the size of the null.I inspected the
BaseObject
instance of my null and found theAddEventNofication
function. However this function is only documented as "Private" in the online documentation.Disclaimer
Please note that I've read up on creating additional objects in an ObjectData plugin and I'm already aware of the fallpits and risks.
-
Hello @CJtheTiger,
thank you for reaching out to us.
Is it possible to register to the change of an object? [...] The Message method of my ObjectData does not receive a relevant message upon changing the size of the null.
NodeData::Message only receives messages sent to that node, as it is just the counter part to
C4DAtom::Message
. So, if you implement aFoo
object, you will receive messages sent toFoo
instances. There is an exception to that rule,C4DAtom::MultiMessage
. When a caller decides to use it instead of::Message
, also the children and branches of the node upon which::MultiMessage
is being invoked will be informed. Multi message events are rare and nothing one can rely on as a principle.Node messages, i.e.,
C4DAtom::Message/NodeData::Message
, are intrinsically bound to a singular node. Broad scene or system wide messages are the so called core messages. It is however not possible to receive core messages in a node.[...] and found the AddEventNofication function. However this function is only documented as "Private" in the online documentation.
There are some examples on the forum here, but you first of all must register for each node yourself. So, you cannot say 'broadcast all Onull data container changes to me', and there is also no data container event (see NOTIFY_EVENT for details).
What can I do?
You are running here into the well known problem that the classic API has no meaningful dependency graph/information, and as a root cause for that also only a very coarse scene update event system.
- Simple Solution: Use the built-in dependency mechanism for object plugins, the flag OBJECT_INPUT. When you flag your plugin as such a direct child of your plugin node which has become data dirty will also cause your plugin's
GetVirtualObjects
(and similar methods) to be invoked. ButOBJECT_INPUT
is indeed limited to direct children, everything else will be ignored. So, it depends on where your null objects live. - Tag Solution : Another pattern to solve problems such as this are tags. The idea is to mark the thing you want to be notified about with a custom tag of yours. Because a tag qualifies for
::MultiMessage
messages sent to its hosting object, and because tags are executed on each scene pass, they can be a clever solution to the problem. The tags are then usually hidden and carry a base link to the node they should inform about changes of their host object. Either in their::Message
or::Execute
method they then carry out the information propagation. Problems can be here threading restrictions and feedback or delay effects. - Complex Solution: You can solve all these problems with a
MessageData
plugin. With it you can hook in the core message stream, and onEVMSG_CHANGE
events traverse/analyze a scene graph to decide if an event X has happened. Then you can inform specific nodes of that event you have detected. To inform your nodes, you should then useC4DAtom::Message
with the message IDMSG_BASECONTAINER
, sending the relevant data in aBaseContainer
. Efficiently doing all of this, especially gracefully traversing and analyzing the scene graph, can be tricky.
Cheers,
Ferdinand - Simple Solution: Use the built-in dependency mechanism for object plugins, the flag OBJECT_INPUT. When you flag your plugin as such a direct child of your plugin node which has become data dirty will also cause your plugin's
-
Hey @ferdinand,
thank you for your response! And a very involved one at that, as expected from you. Thanks!
Simple solution using
OBJECT_INPUT
I tried the solution using
OBJECT_INPUT
. To verify whether it works I created a breakpoint inGetVirtualObjects
and tried it both with and without theOBJECT_INPUT
flag. What I found was that the flag does not make a difference, the function will still be called oh so many times. Also you mentioned "direct children". I tested this both with children directly under my generator as well as with nested children of the generator. All of them triggerGVO
. And since I don't get information about which children exactly has changed and what property I'd have to re-evaluate all of them which doesn't sit quite right with me. They're only nulls but still.Complex solution using
MessageData
As for the complex solution (and this is very theoretical so feel free to skip it) I'd have to register a
MessageData
plugin and hook into the messaging pipeline, just to ignore all messages except this very specific one. And then I'd also have to identify whether the issuing object is indeed one of my desired nulls so I'd have to mark those in some way. Which then makes me think "mark it with a tag". And at that point I can just use a custom tag anyways.Tag Solution
Performance-wise this sounds the cleanest to me. It doesn't bloat the messaging pipeline, it won't require any fancy evaluation logic to see whether this is my desired null, it's very explicit in its definition. So this is the one I'll use.
But feel free to correct me in my theories about performance.
-
I would like to add the solution I chose to close this topic.
The Tag Solution did indeed work very well.
Execute
got called plenty of times and I was easily able to filter for the change of the object I was looking for.But what I actually did was create a whole new class deriving from
ObjectData
and listen to the messageMSG_DESCRIPTION_POSTSETPARAMETER
to perform my actions right then. In the first post I said that my plugin creates a null. I replaced all those nulls with my newObjectData
class. So my object hierarchy is similiar to this:MainPluginObject : ObjectData
-> Needs to be accessed byHelperObject
.HelperObject : ObjectData
-> AccessesMainPluginObject
in itsMessage
function.
Also note that thread awareness is required.
The reason I chose this structure this is the following:
I would like the user to
Current State to Object
myMainPluginObject
. Before that theHelperObject
might not be visible in the object manager depending on the values of some properties inMainPluginObject
. But onceCSTO
is called I want all objects to be shown in the object manager. If I had chosen the Tag Solution the user might delete the tag by accident and break everything. Now of course the user can still break things by deleting or moving theHelperObject
out of the hierarchy ofMainPluginObject
but this I already have to validate anyways. Having a similiar check in a tag just adds another point of error which I'd like to avoid.So I'll mark this as my answer after taking everything @ferdinand said into consideration.
Again, thank you very much for your assistance! You never let us down!
-
Hey @CJtheTiger,
sorry for my radio silence here and thank you for updating the thread. In short: you drew all the right conclusions for the general case.
A
MessageData
solution can be quite performant when you know exactly what you are doing and when you must monitor either every object (in the sense of geometry) in the scene graph or at least many of them. For something like monitoring materials, shaders, or tags, the solution is mandatory anyways.The tag solution on the other hand can become a bottle neck when the information flow becomes too expensive. Imagine such dependency propagation tag called
DataTag
, and you have 1000's of them in a scene. Each tag not only informs then one but multiple other nodes about changes of its host. Due to that there is also overlap, i.e., bothDataTag DA
andDataTag DB
inform a target objectT
about changes of their host objectsHA
andHB
. But forT
this might be the "same" event and it then might be doing work twice. So, when stuff is getting complicated, you. are often better off with aMessageData
solution, as you can consolidate things there.Cheers,
Ferdinand