FieldList, and how to properly use it



  • Hello,

    in my plugin, I support Fields. I have a FIELDLIST element in my .res file, and I can get the FieldList custom datatype, et cetera.

    But there are some things I really don't know how to handle:

    1. Dirtyness
    I need to track dirtyness of all Field objects in the list, so I can determine when to rebuild my plugin object's cache. FieldList::GetDirty() seems to catch only changes that were made to the list, not to any of the stuff in the list. I found that the IsDirty() functions often report dirtyness when actually nothing has changed, so I am using the GetDirty() function of each Field object, then store the dirty checksums and later compare if the checksums I get from the Field objects to see if they have changed.

    This seems to work ok. However, it's quit a lot of code required, just for tracking the dirtyness:

    // Check for dirty fields
    GeData geFieldData;
    if (op->GetParameter(MYOBJECT_FIELDS_LIST, geFieldData, DESCFLAGS_GET::NONE))
    {
    	// Get FieldList
    	CustomDataType* const fieldData = geFieldData.GetCustomDataType(CUSTOMDATATYPE_FIELDLIST);
    	FieldList* const fieldList  = static_cast<FieldList*>(fieldData);
    	if (fieldList && fieldList->HasContent())
    	{
    		// Dirty fieldList
    		_currentDirtyChecksum_fieldList = fieldList->GetDirty(doc);
    		needsEvaluation |= _currentDirtyChecksum_fieldList != _compareDirtyChecksum_fieldList;
    
    		// Dirty field objects
    		GeListHead *listHead = fieldList->GetLayersRoot();
    		if (listHead)
    		{
    			_currentDirtyChecksum_fieldLayers = 0;
    			_currentDirtyChecksum_fieldObjects = 0;
    			for (FieldLayer *layer = static_cast<FieldLayer*>(listHead->GetFirst()); layer; layer = IterateNextFieldLayer(layer))
    			{
    				// Layer node (in list)
    				_currentDirtyChecksum_fieldLayers += layer->GetDirty(DIRTYFLAGS::DATA);
    
    				GePrint(layer->GetName());
    
    				// Actual field object
    				const FieldLayerLink layerLink = layer->GetLinkedObject(doc);
    				BaseObject *fieldObject = static_cast<BaseObject*>(layerLink._object);
    				if (fieldObject)
    				{
    					_currentDirtyChecksum_fieldObjects += fieldObject->GetDirty(DIRTYFLAGS::CACHE|DIRTYFLAGS::DATA|DIRTYFLAGS::MATRIX);
    				}
    			}
    			needsEvaluation |= _currentDirtyChecksum_fieldLayers != _compareDirtyChecksum_fieldLayers;
    			needsEvaluation |= _currentDirtyChecksum_fieldObjects != _compareDirtyChecksum_fieldObjects;
    		}
    	}
    }
    

    _currentDirtyChecksum_fieldObjects, _currentDirtyChecksum_fieldLayers, _compareDirtyChecksum_fieldLayers, and _compareDirtyChecksum_fieldObjectsare members of my NodeData. The _compare*checksums are later updated with the values from the _current*checksums.

    2. FieldList contents
    There are the functions FieldList::HasContent() and FieldList::GetCount(). Why is this:

    • Removing Field objects from the FieldList correctly decreases the return value of GetCount(), and, after removing the last Field object from the list, makes HasContent()return false.
    • Deleting Field objects that are linked in the FieldList from the document does not decrease the the return value of GetCount() and HasContent() always returns true.

    That means, if the user deletes linked Field objects in the Object Manager, my plugin object is permanently broken. It behaves as if there still were Field objects to take into account. And the user has no chance of repairing it, because the FieldList appears empty to the user.

    I know this behaviour is consistent with the InExcludeList, which also maintains zombie BaseLinks. But I never understood the use of this. I also tried iterating the list and removing all zombie elements by deleting the list item if GetLinkedObject() does not return a valid pointer, but unfortunately that would also remove folders in the FieldList.

    MoGraph seems to handle it well. If I delete a Field object that is linked in an effector object, the effector will show full effect, even though there should be a zombie link in the FieldList. What does MoGraph do to handle this so robustly?

    It really is a pity that there's not a single example in the SDK (not in the GitHub repo either) that uses a FieldList.

    Thanks for any help!

    Cheers,
    Frank



  • Is anyone from the Tech Support looking into this?
    Or is any of this unclear, should I elaborate more?



  • Hi @fwilleke80 sorry I was busy yesterday, so for the first point there is nothing to help you more, as you may know, some mograph stuff (especially if you rely on matrix or shader) are constantly dirty... And if one of those is on the fieldlist, then all the fieldlist will be dirty. So your approach to monitoring each one is the most optimized.

    Regarding your second point, I've reached the development team. For me, the reason is for undo operation, but that's true its a bit unexpected, so let's see what the development team will say.
    But when a field object is deleted, the FieldLayer has his flags set to SKIP | HIDE | TEMPORARY. You can retrieve them via FieldLayer::GetLayerFlags.
    To Exclude folder you can simply check FieldLayer for its type FLfolder.

    Regarding example we do have mograph_fields.cpp which demonstrate:

    • A CommandData that samples a FieldObject
    • A CommandData that samples the field list of a plain effector.
    • A Custom FieldData implementation
    • A FieldLayerData implementation

    So you are right we don't have an example of how to handle field within an ObjectData. This is something we will add on our ToDo list.

    Cheers,
    Maxime.



  • Just et a reply from the developer and as suspected this is to properly support undo.

    Hi,

    This is the same system used in inexclude lists throughout Cinema. The object in the list isn’t the object itself, just a layer with a link to the object, if the real world object is deleted then the entry simply points to nothing. When you delete in the list though you are deleting the layers and adding undos to the object the list is on via the GUI system, the list additionally checks for any other uses of the object in scene and deletes it as well if none are found.

    Because of differences in how the object system and GUI system works, and that fields may be shared in multiple lists this approach allows undos, GUI display and evaluation to work correctly. These layers are then marked for omission in file operations.

    Hope it makes sense,
    Cheers,
    Maxime.



  • Thank you! I'll see if the flags solve my problem. Sounds like I can work with this.
    I'll mark this SOLVED for now!

    An example for a FieldList in an ObjectData would be most appreciated! :-)

    Cheers,
    Frank



  • Back from a short autumn vacation. Sorry, I have to mark this as UNSOLVED again.

    But when a field object is deleted, the FieldLayer has his flags set to SKIP | HIDE | TEMPORARY. You can retrieve them via FieldLayer::GetLayerFlags.

    After deleting a linked FieldObject, the respective FieldLayer in the FieldList is not set to SKIP or HIDE or TEMPORARY. It is still NONE.

    EDIT: Interesting. I wrote a little Python tag to print out the flags of all FieldLayers in the FieldList of a MoGraph Effector object, and in deed, the flags are set in the Effector's FieldList's FieldLayer. They are not set in my object.

    I guess the EffectorData usually takes care for flagging the dead FieldLayers. My object is not an effector. Is there any message I need to catch and handle?

    For now, I'll try and recognise dead and otherwise unusable FieldLayers like this. but it doesn't feel safe:

    inline Bool IsActiveFieldLayer(FieldLayer *fieldLayer, BaseDocument *doc)
    {
    	// Inactive or not sampling the value (we only need the ones that sample value)
    	const FIELDLAYER_CHANNELFLAG channelFlags = fieldLayer->GetChannelFlags();
    	if (!(channelFlags&FIELDLAYER_CHANNELFLAG::ENABLE) || !(channelFlags&FIELDLAYER_CHANNELFLAG::VALUE))
    		return false;
    
    	// Flagged as no-to-be-used (doesn't seem to work, flags are never set)
    	const FIELDLAYER_FLAG layerFlags = fieldLayer->GetLayerFlags();
    	if (layerFlags&FIELDLAYER_FLAG::SKIP ||
    	    layerFlags&FIELDLAYER_FLAG::ERRORSKIP ||
    	    layerFlags&FIELDLAYER_FLAG::HIDE ||
    	    layerFlags&FIELDLAYER_FLAG::TEMPORARY)
    		return false;
    
    	// Dead link but not a folder (once the flags actually work, the first line can be removed)
    	if (!fieldLayer->GetLinkedObject(doc)._object &&
    	     fieldLayer->GetType() != FLfolder)
    		return false;
    
    	// We can consider the FieldLayer active and usable
    	return true;
    }
    

    Cheers,
    Frank



  • Hi @fwilleke80 looking at the code this is something done in the CheckDirty and FieldData::SetLinkedObjectin all our FieldData.
    The logic is similar by checking if the link is still valid.

    So there is no message for that, and this should be handled automatically if in your ObjectData::CheckDirty your properly forward the call to the falloff. Find an example in Deformer update and Fields.

    Note that this method is not magic and rely on the CheckDirty to be called so I would say even our solution is not the most robust.

    Cheers,
    Maxime.



  • Thank you! :-)