Multiple instances of ObjectData plugin?

Hi all, my first post here. Thank you for the opportunity.

I'm developing a C++ ObjectData plugin. During testing it seems that straight from the start multiple instances of the plugin are created. Why is that and can it be avoided?

I've cobbled together a bare bones plugin (called Minst, plugin ID=1111111) which displays the same behaviour as my actual plugin.

Here's the output to the Console:

Screen Shot 2022-11-14 at 14.50.26.png

I believe the Console shows that multiple instances of Minst are created by calling Init() more than once. (My more complex plugin also calls GetDDescription() more than once, so this multiple calling of the same functions can become quite involved very quickly. That's why I would very much like to know the reasoning behind it.)

The Console also shows that GetVirtualObjects() is only called for the first instance of the plugin, not the other two, which seems a good thing. (However, it also shows that GetVirtualObjects() is called twice in a row, one right after the other, which seems a bit superfluous.)

Here 's the code:

main.h

#ifndef MAIN_H
#define MAIN_H

Bool RegisterMinst();

#endif

main.cpp

#include "c4d.h"
#include "main.h"

Bool PluginStart()
{
    return RegisterMinst() != 0;
}

void PluginEnd()
{
}

Bool PluginMessage(Int32 id, void* data)
{
    switch (id)
    {
        case C4DPL_INIT_SYS:
        {
            return g_resource.Init() != 0; 
        }

        case C4DMSG_PRIORITY:
        {
            return true;
        }

        default:
            return false;
    }
}

minst.cpp

#include "c4d.h"
#include "main.h"

#define ID_MINST  1111111  // Temporary plugin id. Replace this with a proper ID.

class Minst : public ObjectData
{
INSTANCEOF(Minst, ObjectData)

public:
    Bool Init(GeListNode* node) final;

    BaseObject* GetVirtualObjects(BaseObject* op, HierarchyHelp* hh) final;

    Bool Message(GeListNode* node, Int32 type, void* t_data) final;

    static NodeData* Alloc() { return NewObjClear(Minst); }
};

Bool Minst::Init(GeListNode* node)
{
    maxon::String message = FormatString("Init: Plugin [email protected], Plugin [email protected]", (void*)this, node->GetType());
    ApplicationOutput(message);

    return true;
}

Bool Minst::Message(GeListNode* node, Int32 type, void* t_data)
{
    return true;
}

BaseObject* Minst::GetVirtualObjects(BaseObject* op, HierarchyHelp* hh)
{
    maxon::String message = FormatString("GetVirtualObjects: Plugin [email protected], Plugin [email protected]", (void*)this, op->GetType());
    ApplicationOutput(message);

    return nullptr;
}

Bool RegisterMinst()
{
    ApplicationOutput("RegisterMinst");
    
    return RegisterObjectPlugin(ID_MINST, "Minst"_s, OBJECT_GENERATOR, Minst::Alloc, ""_s, nullptr, 0);
}

Can someone please shed some light on this?

Thank you.

Hello @wnoyce,

Thank you for reaching out to us and welcome to the Plugin Café.

Why are methods being called multiple times?

What you are writing there is called an (object) generator in the terminology of Cinema 4D. The idea is to drive some form of dynamic geometry. All plugin hooks that implement classic API scene elements such as objects, tags, materials, shaders, etc. derive from NodeData. A plugin comes in two layers, the interface layer and the plugin layer.

  • Interface Layer: All "tangible" scene elements (objects, tags, ...) in a document are derived from C4DAtom and are at least represented by a BaseList2D or more specific types such as BaseObject, BaseTag, etc. The entities are the front end of the (public) Cinema 4D API. An object in the Object Manager is a BaseObject, a material in the Material Manger a BaseMaterial, and when a BaseDocument (which is itself a BaseList2D) is traversed, it will yield scene elements with these types.
  • Plugin Layer: This is the backend of Cinema 4D, what you are implementing with your plugin. An ObjectData plugin is the backend to drive a BaseObject, a TagData the backend for a BaseTag, etc. Cinema 4D uses the plugin layer to drive its classic API scene graph. The plugin layer can fully access the interface layer, but the interface layer only has limited access to the plugin layer. There are also restrictions in place regarding modifying the interface layer from the plugin layer, because the plugin layer is mostly executed in parallel.

It is important to understand that you more or less are only providing a 'cooking recipe' for an object instance, not the object instance itself. Cinema 4D will reallocate, reinitialize, and rebuild things all the time when managing a scene.

  • ObjectData::GetVirtualObjects() will be called every time Cinema 4D must generate the cache for the associated BaseObject. This will happen for example when the user changes a parameter, but also on other occasions.
  • NodeData::GetDDescription will be called whenever Cinema 4D must evalute the description of the node (which can be quite often).
  • NodeData::Init will also be called more the once, because Cinema 4D is reallocating nodes quite often in the background. As pointed out in the docs:

The parameters of a node must be initialized in this method. NodeData::Init is being called frequently due to nodes being reallocated in the background by Cinema 4D. One should therefore be careful with carrying out computationally complex tasks in this function.

So, it is normal that these methods are being called more than once. It might not always be obvious for an outside observer why a node is being reallocated and reinitialized; an example would be Asset API preset assets, which for example are reallocating nodes multiple times when invoked.

Why is my GetVirtualObjects only called twice for three object instances?

You return the null pointer in your GetVirtualObjects method, indicating a memory error to Cinema 4D. This can cause Cinema 4D to cease to call this plugin hook when executing the passes on a scene. So, it seems like GVO is being called twice for the first instance of your plugin object and then zero times for the remaining two.

Also, the pointer to the plugin hook is a poor way of identifying plugin instances, as the plugin hook is just the driving interface, and not the actual entity. You get passed in the actual entity in most methods (Init, Message: node, GVO: op) and can also always retrieve it yourself with NodeData::Get, which will yield the GeListNode which is (currently) representing your plugin instance.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

@ferdinand Thank you very much for your elaborate answer!

Explaining the difference between the interface layer and the plugin layer was very enlightening. I now understand that calling functions more than once is all part of the package and that this does not refer to the actual plugin instance. Thank you again.