On 13/02/2016 at 04:58, xxxxxxxx wrote:
Alrighty, here's the current code I have for handling my plugin's dirty checksum. It's much longer than I expected as there's some weird quirks with where and when the checksums actually increment.
- Deformers and Effectors do not affect the dirty flags on an object; they affect the dirty checksums of a cached instance of the object.
I need to get the modified objects via GetCache() or GetDeformCache() and check that returned object's dirty checksum. If the object creates a virtual hierarchy (Cloner, SubD, etc.) I have to do a recursive function to check the dirty checksums of each element.
The VirtualDirtySum() function below does this.
- Switching an object's parent in the OM does not increment dirty checksums. Moving an object's parent's matrix does not increment it either; DIRTY_MATRIX only increments changes with the local matrix.
To check for changes in the parent's hierarchy + matrixes I need to do a recursion upwards to get their dirty matrix checksums, as well as the GUID of their parents.
- My plugin can also read thinking particles from TP_PGroup. The whole TP system increments with every frame change, so to reduce dirty calls I only check for the particle system's dirty checksum if the group contains particles. If the group is empty, I skip the check.
- Right now I'm using a BaseArray to store the checksums. I thought of just adding everything into one UInt64 variable, but I fear there might be some rare cases where I couldn't detect changes (e.g. checksums of the inputs increment but a generator object has fewer objects in the next frame, so the total checksum is the same.)
I'll do a summation instead of an array if I can do enough tests to show it's safe.
- Render Instances and the Sphere object's Render Perfect mode do not return any cached geo, so
several of my checks are invalid for those cases. I fear that if I override those objects to generate geo during render time, it could cause unexpected changes in how the scene looks. For now I'll just ignore them as special cases; if a user wants to use those objects as input for my plugin, they'll have to turn off instancing.
- There might still be some scenarios that I missed. I'll post updates if I find any more problems.
#include "c4d.h"
#include "c4d_particles.h" _// TP system_
#include "customgui_inexclude.h" _// in/exclude methods_ **
_//----------------------------------------------------------------------------------------
///Gets TP Master System for the document.
/// @param[in] doc : BaseDocument the object is in
/// @return : TP_MasterSystem of the document
/// @note : Only one instance should exist for a document
//----------------------------------------------------------------------------------------_
TP_MasterSystem* GetTpMasterSystem ( const BaseDocument* doc )** {
if ( !doc ) {
return nullptr;
}
BaseSceneHook* hook = doc->FindSceneHook( ID_THINKINGPARTICLES );
if ( !hook || hook->GetType() != ID_THINKINGPARTICLES ) {
return nullptr;
}
return static_cast< TP_MasterSystem* >( hook );
} **
_//----------------------------------------------------------------------------------------
///Loops through input object's virtual hierarchy, gets total dirty checksum of its elements
/// @param[out] dirtySum : dirty checksum. make sure it's initialized before calling
/// @param[in] obj : input object to get virtual hierarchy from
/// @param[in] INEX_FLAGS : inex flags tied to object
/// @param[in] ISTIP : flag if object is tip of virtual hierarchy's branch
/// @param[in] ISCACHE : flag if recursion occurs
/// @return : none
//----------------------------------------------------------------------------------------_
void VirtualDirtySum( UInt64& dirtySum,
BaseObject* obj, const Int32& INEX_FLAGS, Bool ISTIP = false, const Bool ISCACHE = false )** {
BaseObject* cachedObj = obj->GetDeformCache();
//------------------------------------------------------------------------------------------------------------------------------------------------------------
if ( !ISCACHE ) { _// item passes first time, pre-emptively check if it has virtual children_
BaseObject *testParent = cachedObj;
if ( !testParent ) { _// no deform cache? check for regular cache_
testParent = obj->GetCache();
}
if ( testParent ) { _// deform cache or regular cache? check if cache has children_
ISTIP = ( testParent->GetDown() == nullptr );
}
else { _// no cache at all? object force tip status
ISTIP = true;_
}
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
if ( cachedObj ) { _// deformed cache exists? apply recursion_
VirtualDirtySum( dirtySum, cachedObj, INEX_FLAGS, ISTIP, true );
}
else {
cachedObj = obj->GetCache();
if ( cachedObj ) {
VirtualDirtySum( dirtySum, cachedObj, INEX_FLAGS, ISTIP, true ); _// regular cache exists? apply recursion_
}
else { _// not cache anymore? get dirty sum_
if ( ISTIP || INEX_FLAGS & UT_READ_FULL_CHAIN ) { _// if flagged, check if object is tip of its hierarchy. skip if it's not._
if ( !obj->GetBit( BIT_CONTROLOBJECT ) ) { _// if under generator, check if non-input. skip if used._
dirtySum += obj->GetDirty( DIRTYFLAGS_ALL );
}
}
}
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
if ( ISCACHE ) { _// object is in virtual hierarchy? apply recursion_
for ( cachedObj = obj->GetDown(); cachedObj; cachedObj = cachedObj->GetNext() ) { _// get next object in virtual hierarchy_
ISTIP = ( cachedObj->GetDown() == nullptr );
VirtualDirtySum( dirtySum, cachedObj, INEX_FLAGS, ISTIP, true );
}
}
}
** _//----------------------------------------------------------------------------------------
///Iterates through inexclude list of input objects.
///First entry of output array is the plugin object.
///Successive array entries are from the input inexclude list.
/// @param[out] inexDirtySum_old : array containing dirty checksums of input objects. Global variable from class
/// @param[in] op : object generated by plugin
/// @param[in] doc : document the plugin is in
/// @return : true if checksum changed. false if remains the same
//----------------------------------------------------------------------------------------_
Bool IsInputDirty ( maxon::BaseArray< UInt64 >& inexDirtySum_old,
BaseObject* op, BaseDocument* doc )** {
Bool isInputDirty = false;
TP_MasterSystem* tpMasterSystem = GetTpMasterSystem( doc );
const InExcludeData* inex = ( InExcludeData* )op->GetDataInstance()->GetCustomDataType( INEX_INPUT, CUSTOMDATATYPE_INEXCLUDE_LIST );
const Int32 inexCt = inex->GetObjectCount();
BaseList2D* inexObj;
maxon::BaseArray< UInt64 > inexDirtySum;
inexDirtySum.Reset();
UInt64 dirtySumTemp = 0,
parentId = 0;
BaseObject* parent,
* child;
//------------------------------------------------------------------------------------------------------------------------------------------------------------
if ( op->GetDataInstance()->GetBool( USE_GLOBAL, false ) ) { _// if plugin object set to global mode, matrixes are checked_
parent = op->GetUp();
if ( parent ) {
parentId = parent->GetGUID();
}
inexDirtySum.Append( op->GetDirty( DIRTYFLAGS_MATRIX ) + parentId ); _// plugin object's dirty matrix checksum plus parent's GUID_
for ( child = op->GetUp(); child; child = child->GetUp() ) { _// checksums of object's parent chain_
parent = child->GetUp();
if ( parent ) {
parentId = parent->GetGUID();
}
else {
parentId = 0;
}
inexDirtySum.Append( child->GetDirty( DIRTYFLAGS_MATRIX ) + parentId ); _// for each parent up the chain, get dirty matrix flags_
}
}
else { _// if plugin set to local mode, only parent switching is checked_
parent = op->GetUp();
while ( parent ) { _// recurse for parent's GUID_
inexDirtySum.Append( parent->GetGUID() );
parent = parent->GetUp();
}
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
for ( Int32 i = 0; i < inexCt; ++i ) {
inexObj = inex->ObjectFromIndex( doc, i );
if ( inexObj ) {
if ( inexObj->GetType() == ID_TP_PGROUP ) { _//input object is tp_pGroup_
if ( tpMasterSystem ) {
if ( tpMasterSystem->GetGroupParticleCount( static_cast< TP_PGroup* >( inexObj ), 0 ) ) { _// tp_pGroup has particles? read tpMasterSystem's dirty checksum_
inexDirtySum.Append( tpMasterSystem->GetDirty() + inexObj->GetDirty( DIRTYFLAGS_ALL ) );
}
else {
inexDirtySum.Append( inexObj->GetDirty( DIRTYFLAGS_ALL ) ); _// no particles? just get flags from tp_pGroup_
}
}
}
else {
BaseObject* obj = static_cast< BaseObject* >( inexObj ); _// input object is baseobject_
BaseObject* parent,
* child;
UInt64 parentId = 0;
UInt64 dirtySumTemp = 0;
parent = obj->GetUp();
if ( parent ) {
parentId = parent->GetGUID();
}
VirtualDirtySum( dirtySumTemp, obj, inex->GetFlags( i ) ); _// dirty checksum of object's virtual hierarchy_
inexDirtySum.Append( obj->GetDirty( DIRTYFLAGS_ALL ) + dirtySumTemp + parentId ); _// add parent's GUID, in case parent is changed_
for ( child = obj->GetUp(); child; child = child->GetUp() ) { _// checksums of object's parent chain_
parent = child->GetUp();
if ( parent ) {
parentId = parent->GetGUID();
}
else {
parentId = 0;
}
inexDirtySum.Append( child->GetDirty( DIRTYFLAGS_MATRIX ) + parentId ); _// for each parent up the chain, get dirty matrix flags_
}
}
}
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
if ( inexDirtySum.GetCount() == inexDirtySum_old.GetCount() ) { _// item count the same, check for checksum mismatches_
for ( Int32 i = 0; i < inexDirtySum.GetCount(); ++i ) {
if ( inexDirtySum[i] != inexDirtySum_old[i] ) {
isInputDirty = true;
break;
}
}
}
else { _// item count different, automatically assume dirty_
isInputDirty = true;
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------
if ( isInputDirty ) { _// transfer array_
inexDirtySum_old.Reset();
inexDirtySum_old.CopyFrom( inexDirtySum );
}
inexDirtySum.Reset();
//------------------------------------------------------------------------------------------------------------------------------------------------------------
_// colorizes object in viewport when dirty checksum changes. remove after all testing is okay._
ObjectColorProperties prop;
op->GetColorProperties( &prop );
if ( isInputDirty ) {
prop.color = Vector( 0.9, 0.2, 0.1 );
}
else {
prop.color = Vector( 1.0 );
}
if ( prop.usecolor != ID_BASEOBJECT_USECOLOR_ALWAYS ) {
prop.usecolor = ID_BASEOBJECT_USECOLOR_ALWAYS;
}
op->SetColorProperties( &prop );
//------------------------------------------------------------------------------------------------------------------------------------------------------------
return isInputDirty;
}
** _//----------------------------------------------------------------------------------------
///virtual Member from ObjectData class
/// @param[in] op : object generated by plugin
/// @param[in] doc : document the plugin is in
/// @return : none
/// @notes : _inexDirtySum_old is a private global variable. stores array of dirty checksums
//----------------------------------------------------------------------------------------_
void UberTracer02::CheckDirty ( BaseObject* op, BaseDocument* doc )** {
if ( IsInputDirty( _inexDirtySum_old, op, doc ) ) { _// check input inex list for dirty checksum, plus plugin object itself_
op->SetDirty( DIRTYFLAGS_DATA );
}
}