Touch(), cache handling and dirty counts

On 05/05/2018 at 06:46, xxxxxxxx wrote:

User Information:
Cinema 4D Version:   R19 
Platform:   Windows  ;   
Language(s) :     C++  ;

I am having trouble to determine when an input object of my generator changed compared to the
last time that I handled and generated new geometry for it.

When the generator is first invoked, there's no cached state and thus it generates new geometry
for every input object. It saves the dirty count of the input object (DIRTYFLAGS_CACHE | DIRTYFLAGS_DATA)
in a hashmap. After the geometry was generated from the PolygonObject's found in the input object's
cache, it calls Touch() on the object.

However, Touch() increases the DIRTYFLAGS_CACHE dirty count (eg. for Array and Cloner objects,
not for Cube primitives for some reason).

Now the dirty count that I stored for the object is incorrect as it has been modified by Touch().
I need to call Touch() after I computed the geometry however because Touch() will free the cache
of the input object, making it inaccessible for computing the new geometry.
Do I need to compute the dirty count a second time after calling Touch() and store that dirty count

Now assume the above works properly and I determine that I don't need to recompute geometry
from the input object. I simply call Touch() again in order to hide the input object in the viewport
and reuse the previously generated cache.

Does that mean that the input object's GetVirtualObjects() was called but the result is completely
ignored and deleted again by my Touch() call? Wouldn't that be super unnecessary?

Also, that Touch() call would again increase the dirty count.

Are there some resources that explain how to manage this properly?

On a side-note, I can't use the BaseObject DependenceList stuff because I want to treat all of the
inputs separately. So if one input object changed, I may still re-use the previously generated cache
of another input object.

For the sake of completeness, in case it is of any relevance, this is how I retrieve all the input
objects to my generator:

  inline void CollectGenerators(BaseObject* obj, maxon::BaseArray<BaseObject*>& objects) {  
  if (obj->GetDeformCache() || obj->GetCache() || obj->GetType() == Opolygon) {  
  else {  
    for (auto&& child : Utils::IterChildren(obj)) {  
      if (!child->GetBit(BIT_CONTROLOBJECT)) {  
        CollectGenerators(child, objects);  
    // Then in GetVirtualObjects() :  
  maxon::BaseArray<BaseObject*> generators;  
  for (auto&& child : Utils::IterChildren(op)) {  
    Utils::CollectGenerators(child, generators);  
   for (auto&& generator : generators) {  
       // TODO: Determine if the cache of the generator has changed -> Recompute the geometry only in that case  
         maxon::BaseArray<PolygonObject*> geos;  
         // Here I collect all PolygonObjects using the method described in the  
         // BaseObject::GetCache() documentation (checking for BIT_CONTROLOBJECT).    
       // Build new geometry from PolygonObjects  

Thanks in advance,


On 07/05/2018 at 01:58, xxxxxxxx wrote:

Small update: I noticed that calling Touch() on the input object will cause it to NOT generate a new
cache the next time GVO is called. This can be tested easily with a Python Generator:

import c4d  
def main() :  
  array = op.GetDown()  
  print(array, array.GetCache())  
  return c4d.BaseObject(c4d.Ocube)

(<c4d.BaseObject object called 'Array/Array' with ID 5150 at 0x000001F73CB71C50>, <c4d.BaseObject object called 'Array/Null' with ID 5140 at 0x000001F73CB71AD0>)  
(<c4d.BaseObject object called 'Array/Array' with ID 5150 at 0x000001F73CB71F50>, None)  
(<c4d.BaseObject object called 'Array/Array' with ID 5150 at 0x000001F73CB71AD0>, None)

This resolves one of my previous questions:

Originally posted by xxxxxxxx

Does that mean that the input object's GetVirtualObjects() was called but the result is completely
ignored and deleted again by my Touch() call? Wouldn't that be super unnecessary?

However, what if I actually need the cache but the input object has none because I called Touch()
on it in the previous GVO() pass?

On 07/05/2018 at 02:07, xxxxxxxx wrote:

Another test from the C++ plugin

      auto c = op->GetDown();  
    if (c) {  
      GePrint(c->GetName() + ": Has Cache? " + String(c->GetCache() ? "yes" : "no"));  
      auto cc = c->GetDown();  
      if (cc) GePrint("    " + cc->GetName() + ", BIT_CONTROLOBJECT? " + String::IntToString(cc->GetBit(BIT_CONTROLOBJECT)));  
    return BaseObject::Alloc(Onull);

After I move the Array object under the ObjectData plugin, I get

Array: Has Cache? yes  
Array: Has Cache? no  
Array: Has Cache? no  
Array: Has Cache? no  
Array: Has Cache? no  
Array: Has Cache? no  
Array: Has Cache? no  

Which is the same behaviour as in the Pyhon Generator. I just wanted to make sure that it's not
some kind of issue with the Python Generator, so I tested it in the C++ plugin, too.

Shouldn't the BIT_CONTROLOBJECT be set on the "Car" polygon object? How else would I know
that the object is already being used by the Array object?



On 07/05/2018 at 02:35, xxxxxxxx wrote:

Hi Niklas, thanks for writing us.

There's a in-depth description on the topic in the BaseObject manual at this link which I think it could be worth reading with regard on how/when to use BaseObject::Touch() .

Let me know if any further help is needed.

Best, Riccardo

On 07/05/2018 at 03:36, xxxxxxxx wrote:

Thanks Riccardo, that looks like what I was searching for. My bad for not finding it. :-)
I'll give all the info in that page a go and come back with any unanswered questions.


On 07/05/2018 at 09:14, xxxxxxxx wrote:

Hello Riccardo,

Unfortunately I am not getting very far. I fear my case is a little more complex than what is usually
implemented for generator plugins.

  • GetAndCheckHierarchyClone() seems to always return a clone and tell me that the object is
    dirty. Not very helpful unfortunately. Also, I would like to avoid the "cloning" part and read from
    the cache directly as it will be much more efficient.

  • Using IsDirty() seems to be very useful and does not require me to store dirty counts, great!
    But can I use it for any objects that are not direct children of my generator?

For example, the Array object only takes its first child as its input. Any other of its child
objects are ignored and left visible in the Viewport. I want to have my generator take this
object into account instead (similar to how the Subdivision Surface can handle this case, see
the image below)

Also, there appear to be cases where IsDirty() returns true but the object has no cache
that I can read from. I described this situation before when I wasn't using IsDirty(). How
would I be able to access the cache when the generator is dirty but it doesn't have a cache?

I noticed that the Subdivision Surface seems to be capable of handling the input objects similar to
the way that I would like to handle them. It sees the objects generated by the array, but also the
second child of the array that is not taken into account by the Array object itself.

Any chance to get some insight on how the Subdivision Surface does all of this? Specifically

  • How does it find all the PolygonObjects to take into account from its child objects?
  • How and when does it call Touch() on these input objects?
  • How does it check if an input object changed?

Thanks in advance,