Provides an overview of the fundamental API changes introduced with Cinema 4D 2024.0 API.
One of the major changes of Cinema 4D 2024 release is the general improvement of scene execution speed achieved trough careful modifications of central parts of the classic API. It is important that plugins follow these changes faithfully as plugins substantially determine the scene execution speed of a loaded document. Since scene execution happens largely in the so called classic API, most changes can be found in the cinema.framework
. The most important abstract changes are:
FieldObject
and EffectorData
beyond pure constness changes.For a more detailed overview, please refer to the list below and the example plugin. As a general set of best practice rules:
NodeData
priority as they will be the most costly tasks.sdk_support(at)maxon(dot)net
, the SDK group is happy to help you along your migration journey.We unfortunately cannot make any development cost predictions for multi-version code base solutions, as these never have been supported by the SDK group as stated in the Development Environments manual. In the custom circumstances of the 2024.0
API migrations we encourage developers who use such solutions and run into troubles, to reach out to us for help.
The change documentation is accompanied by a new section in the SDK, \plugins\example.migration_2024
, containing a NodeData
plugin migration example (Fig. 1) in oboundingbox.cpp
and oboundingbox_legacy.cpp
, as well as other examples in change_examples.cpp
.
sdk.zip
instead.SDK Plugins | 2024.0 API Migration Example | Demonstrates the migration of a |
Changes | Constness of Overridable Methods | Demonstrates how adapt to the constness changes of overridable methods. |
Node Initialization | Demonstrates the purpose of the new isCloneInit argument of NodeData::Init . | |
Node Data Access System | Demonstrates the the usage of the new node data access dependency system. | |
Node Data Container Access | Demonstrates how to access the data container of scene elements. | |
Node Branching with GetBranchInfo | Demonstrates the slightly changed syntax of accessing node branches. | |
Copy On Write Scene Data | Demonstrates the copy-on-write changes made to VariableTag and BaseSelect . | |
Custom Data Type Access | Demonstrates how to access custom data type fields in BaseContainer and GeData instances. | |
Instantiating DescID Identifiers | Demonstrates how to construct compile-time and runtime constant DescID identifiers. | |
Accessing Observables | Demonstrates the changes made to the allocation and access of observables. | |
Gradient and Field Sampling | Demonstrates the changes made to Gradient and FieldObject sampling. | |
Storing Data with AttributeTuple | Demonstrates the new and performant type maxon::AttributeTuple to store and index data with MAXON_ATTRIBUTE keys which are known at compile-time. | |
Casting Between Data Types | Outlines the slightly changed style recommendations for casting between data types. | |
Support for Custom Qualifiers | Demonstrates how to define custom function and variable qualifiers in the 2024 API. |
Demonstrates how adapt to the constness changes of overridable methods.
The overarching theme of the 2024.0 API changes is the more precise distinction between mutable and immutable data. This is done so that data can be reused and so that scene execution can be more effectively parallelized. The foundation of all this is the correct application of const
-ness to methods in the Cinema 4D API. This has happened for many non-overrideable types where adaptions in third party code are often not necessary or only yield a performance benefit. But legacy plugin code that implements overridable types such as ObjectData
or TagData
must be adapted. Multiple NodeData
methods and the methods of derived types have been made cosnt
to allow for such more efficient scene execution. An example for such change is NodeData::GetDParameter
where both the method itself as well as the node passed to it have become const
:
virtual Bool NodeData::GetDParameter(const GeListNode* node, const DescID& id, GeData& t_data, DESCFLAGS_GET& flags) const |
const
methods of NodeData
derived plugin hooks signal that neither the plugin hook instance, e.g., MyObjectData
, nor the scene element representing them, e.g., a BaseObject
of type Omyobject
, should be modified. These methods are usually executed in parallel.In general, one should strive for embracing these restrictions, as circumventing them can be expensive both in development and runtime costs and can potentially lead to crashes when not carried out properly. The example shown below is taken from the example.migration_2024 plugin Oboundingbox
. The legacy version of that plugin counted the access to one of its parameters in its NodeData::GetDParameter
method by incrementing a class bound counter field _get_display_mode_count
. The 2024.0 code shown below demonstrates different approaches for translating such feature requirement to a method which is now const
.
Demonstrates the purpose of the new isCloneInit
argument of NodeData::Init
.
NodeData::CopyTo
.With the introduction of the Asset Browser, scene elements often must be reinitialized to drive the Asset API preset system. isCloneInit
indicates now such cloned node allocations which are usually deallocated right after their allocation. It is not necessary to initialize the data container of a node in these cases, as Cinema 4D will copy the data from a source node right after such isCloneInit
call. Using this argument and NodeData::CopyTo
, plugin authors can now also avoid doing expensive initialization computations more than once for the same data.
The example below is taken from the example.migration_2024 plugin Oboundingbox
. The ObjectData
plugin skips initializing its data container when isCloneInit
is true
.
Demonstrates the the usage of the new node data access dependency system.
The classic API provides now an all new data dependency system for scene elements. It allows for the more effective parallelized execution of certain stages of the scene execution such as generator or deformation cache building. While the system does realize a long desired dependency system between classic API scene elements, it is not a user oriented system at the moment. Its only purpose is the optimized parallelization of scene execution. The frontend of the system can be found in BaseList2D
:
BaseList2D::GetAccessedObjects
: Returns the data access for a node instance alone.BaseList2D::GetAccessedObjectsRec
: Returns the data access for a node instance and all its descendants and/or siblings.BaseList2D::GetAccessedObjectsOfHierarchy
: Returns the data access for a node instance and all its descendants.BaseList2D::GetAccessedObjectsOfChildren
: Returns the data access for all the children of a node instance and all their descendants.The backend of the system can be found in NodeData
and c4d_accessedobjects.h
:
NodeData::GetAccessedObjects
: Override this method to express custom data access information for a node type.EffectorData::GetAccessedObjects
: Override this method to express custom data access information for an effector type.AccessedObjectsCallback
: Used to signal data access information to the core of Cinema 4D.AccessedObjectsCallback::MayAccess
: Signals read and write access information for a given node to the core of Cinema 4D.METHOD_ID
: Expresses data access events such as the building of caches or the sampling of fields.ACCESSED_OBJECTS_MASK
: Expresses the type of data that is being accessed in a node, as for example caches, transforms, or the data container.The example below is taken from the example.migration_2024 plugin Oboundingbox
. The ObjectData
plugin returns a bounding box geometry for its first child object as its generator cache. In its NodeData::GetAccessedObjects
method, the plugin then expresses its data dependencies for building that generator cache.
Demonstrates how to access the data container of scene elements.
Reworked was also the data container access for scene elements, now differentiating clearly differentiating between read and write access. It is recommended to use the reference methods over the pointer methods.
const BaseContainer& BaseList2D::GetDataInstanceRef
: Returns a reference to the data container of the node for read access.BaseContainer& BaseList2D::GetDataInstanceRef
: Returns a reference to the data container of the node for read and write access.const BaseContainer* BaseList2D::GetDataInstance
: Returns a pointer to the data container of the node for read access.BaseContainer* BaseList2D::GetDataInstance
: Returns a pointer to the data container of the node for read and write access.Containers should be copied with the BaseContainer
copy constructors or one of its copy methods. BaseContainer BaseList2D::GetData
will be deprecated, as its copying behavior has often been invoked unintentionally.
Demonstrates the slightly changed syntax of accessing node branches.
GeListNode::GetBranchInfo
now operates with the concept of a maxon::ValueReceiver
and therefore also returns a maxon::Result
. This eliminates the need of predicting the number of branches that must be accessed and allows for terminating searches early.
Demonstrates the copy-on-write changes made to VariableTag
and BaseSelect
.
The types VariableTag
and BaseTag
now store their internal data as copy-on-write (COW) references. This has far-reaching consequences because VariableTag
is the storage form of most fundamental scene data such as points, polygons, normals, tangents, and vertex data. Creating for example two Cube object instances with the exact same settings, will have the result that the Tpoint
, Tpolygon
, and `Tuvw
tags in their generator caches reference the same data in memory (Fig. 2). Effectively, there is only the geometry data for one cube object being stored memory, without any explicit actions from the user such as an Instance object.
Similar optimizations can also happen for editable geometry. Internally, these optimizations rely on the already existing memoization optimization of the modeling core of Cinema 4D. The referencing of data is being updated on the following events:
The example shown below highlights the differences between read and write access for VariableTag
and BaseTag
instances.
Demonstrates how to access custom data type fields in BaseContainer
and GeData
instances.
Access to custom data type fields has been streamlined with read-only access methods and templated methods which directly return the data without any casting.
GeData::GetCustomDataType
: Provides read-only access to the stored custom data type value using templated access.GeData::GetCustomDataTypeI
: Provides read-only access to the stored custom data type value using legacy syntax and casting.GeData::GetCustomDataTypeWritable
: Provides read-write access to the stored custom data type value using templated access.GeData::GetCustomDataTypeWritableI
: Provides read-write access to the stored custom data type value using legacy syntax and casting.BaseContainer::GetCustomDataType
: Provides read-only access to the stored custom data type value using templated access.BaseContainer::GetCustomDataTypeI
: Provides read-only access to the stored custom data type value using legacy syntax and casting.BaseContainer::GetCustomDataTypeWritableObsolete
: Provides read-write access to the stored custom data type value using templated access.Demonstrates how to construct compile-time and runtime constant DescID
identifiers.
The type DescID
now distinguishes between compile-time and runtime constant instances of itself for performance reasons.
ConstDescID
: Instantiates a DescID
where all level identifiers are compile-time constants.CreateDescID
: Instantiates a DescID
where one or many level identifiers are only known at runtime.There are also constructors and a ::Create
method on DescID
which are wrapped by these macros, but they should not be called manually. Most DescID
constructor calls in old code should be replaced by ConstDescID
, since most parameter IDs are usually compile-time constants.
DescID::DescID(Int32 id1)
. Legacy code such as DescID(ID_MY_STUFF)
MUST be replaced with ConstDescID(DescLevel(ID_BASELIST_NAME))
. Ignoring this currently leads to cryptic compilation errors such as error C2065: XID_BASELIST_NAME undeclared identifier
.Demonstrates the changes made to the allocation and access of observables.
Observables are now only being allocated on demand to avoid allocating event-handlers (observables) without any subscribers (observers) being present.
MAXON_OBSERVABLE
is a macro that causes the Source Processor to create a function on the reference of the interface the macro is placed on. These functions are therefore not part of the document space of Doxygen and cannot be documented. Each observable access function has a singular bool
argument called create
. Passing true
will force a non-existing observable to be created, passing false
will return an empty reference and effectively terminate chained calls such as .AddObserver
, .Notify
, or .RemoveObserver
.The decision what to pass in each case for create
should be guided by the goal of minimizing idle event-handlers. As a rule of thumb, adding observers requires the observable to be created, but removing observers from an observable or notifying an observable can be terminated when the observable does not yet exist in the first place. There could, however, be cases where it for example only makes sense to add an observer when the observable does already exist or analogously to allocate a non-existing observable to notify it without any observers yet being present.
Demonstrates the changes made to Gradient
and FieldObject
sampling.
Gradient
and FieldObject
sampling has been changed so that all sampling related methods are now const
to ensure performant sampling. This is achieved in both cases by helper data structures returned by the respective initialization methods, GradientRenderData
for gradient sampling and maxon::GenericData
for field sampling. Similar changes have been applied to EffectorData
sampling, but operating plugin hooks is not part of the public API and therefore not here documented. Please reach out to the SDK group via sdk_support(at)maxon(dot)net
in case you must sample your own EffectorData
instances and run into troubles doing so.
Gradient
sampling:
FieldObject
sampling:
Demonstrates the new and performant type maxon::AttributeTuple
to store and index data with MAXON_ATTRIBUTE
keys which are known at compile-time.
Instances of maxon::DataDictionary
can become a bottleneck when being accessed thousands of times in quick succession. The type maxon::AttributeTuple
provides a lightweight alternative for cases where the data schema is known at compile-time which acts like a named tuple and shares the the performance characteristics of native C-data-structures.
Outlines the slightly changed style recommendations for casting between data types.
With the API overhaul, we removed a substantial amount of C-style casts from our code base because such casts are dangerous due to their unbound nature. C++ style casting is and will not become mandatory in Cinema 4D projects in the near future. But we encourage all third party developers more than ever to follow our example and remove them from their projects. A minor change has been made to our style guide, which now does recommend conversion constructors over C-style casts for fundamental types.
Demonstrates how to define custom function and variable qualifiers in the 2024 API.
CONST_2024
symbol. Being provided is only the registration of the symbol not a define. But one can use that symbol to define a macro as shown below. Maxon still does not support multi version solutions and we decided to add this feature as a compromise due to the special circumstances of the 2024 release. Note that you will have to backport changes of the source processor to older versions for this syntax to work in older APIs. The relevant changes can be found in sdk/frameworks/settings/sourceprocessor
in the files sourceprocessor.py
and lexer.py
. The change of the source processor is primarily intended as a fix to prevent similar problems in future versions of the API, as now qualifiers can be retroactively added without having to modify the source processor.Migrating to the 2024 API might require third parties to use code such as shown below when they use multi version solutions:
Although this is perfectly well-formed C++, compiling such code will fail on the source processor with an error message such as:
The culprit is the custom qualifier SDK_CONST
, as the source processor only allowed for a predefined pool of such function and variable qualifiers in the past. With 2024.0 the source processor allows to define custom qualifiers on a framework and file level.
With // stylecheck.register-qualifiers = Qualifier; [Qualifier; Qualifier; ...]
we can register one to many qualifiers. Multiple stylecheck.register-qualifiers
comments in a file act additively. It is recommended to put the registration calls at the top of the file, but it is only mandatory to place them before the first usage (not definition) of a custom qualifier symbol. So, this works too:
The parsing horizon of the source processor is a singular file, so all registrations made as comments in code only apply to the file they are made in. When you want to add modifiers on a broader level, you can add them to the project definition of a framework or module. Note that here only a singular statement is being supported and that their ins intentionally no broader scope than frameworks to register qualifiers.
The file solution/a.module/project/projectdefinition.txt
:
In the file solution/a.module/source/foo.h
we can now do this:
But in a file solution/b.module/source/other.h
this would not work because FOO
and BAR
have only been registered for the a.module
.