1. WHAT CHANGED (diff of the two builds)
Bodies are byte-for-byte identical. The CPU Performance Patch differs only in the tail:
C#:
//vanilla (DAZCharacterSelector.cs:3760)
public void SetActiveClothingItem(DAZClothingItem item, bool active, bool fromRestore = false)
{
... identical 36 lines ...
SyncAnatomy();
}
//YOUR (DAZCharacterSelector.cs:4877)
public void SetActiveClothingItem(DAZClothingItem item, bool active, bool fromRestore = false, bool skipSyncAnatomy = false)
{
... identical 36 lines ...
if (!skipSyncAnatomy)
{
SyncAnatomy();
}
}
vanilla ResetClothing (DAZCharacterSelector.cs:3156)
4 call sites, no flag, and NO trailing SyncAnatomy()
-> SyncAnatomy() is reached once per clothing item
YOUR ResetClothing (DAZCharacterSelector.cs:3145)
the same 4 call sites with skipSyncAnatomy: true,
followed by a single SyncAnatomy() at line 3172
-> SyncAnatomy() is reached once per ResetClothing
So the optimization is sound and I would like to keep it. The problem is only WHERE the branch lives.
--------------------------------------------------------------------------------
2. PROPOSED FIX
Split the method. Move the shared body into a new method that does not sync anatomy, keep the public
signature exactly as it was, and let ResetClothing call the no-sync variant.
C#:
// new, additive; no existing signature changes
public void SetActiveClothingItemNoSync(DAZClothingItem item, bool active, bool fromRestore = false)
{
... the 36 identical lines, WITHOUT the trailing SyncAnatomy() ...
}
// public signature restored, byte-compatible with vanilla
public void SetActiveClothingItem(DAZClothingItem item, bool active, bool fromRestore = false)
{
SetActiveClothingItemNoSync(item, active, fromRestore);
SyncAnatomy();
}
// ResetClothing keeps the N -> 1 optimization, calling the no-sync variant per item
protected void ResetClothing(bool clearAll = false)
{
Init();
foreach (var item in _femaleClothingItems)
SetActiveClothingItemNoSync(item, clearAll ? false : item.startActive);
foreach (var item in _maleClothingItems)
SetActiveClothingItemNoSync(item, clearAll ? false : item.startActive);
SyncAnatomy(); // already present at DAZCharacterSelector.cs:3172
}
This is purely additive plus a signature restoration:
- one new public method (cannot break anything),
- the original 3-argument signature back (fixes every existing plugin),
- the optimization unchanged: 1 SyncAnatomy() per ResetClothing, as today.
- vanilla behaviour of the public method is unchanged for all existing callers.
APPLY THE SAME SPLIT TO SetActiveHairItem. It has the identical 4-argument change and the identical
8 skipSyncAnatomy: true call sites in ResetHair (DAZCharacterSelector.cs:4929, 3757-3773). Fixing
only clothing would leave the twin landmine armed for the next release.
--------------------------------------------------------------------------------
3. OPTIONAL: the flag is not propagated into the recursive call
Minor, and not required for compatibility - your optimization is currently not airtight. Inside the
body, the exclusiveRegion knock-out (DAZCharacterSelector.cs:4910) calls itself without forwarding
the flag:
SetActiveClothingItem(clothingItems
, active: false); // skipSyncAnatomy defaults to false
So during ResetClothing, if the item being activated belongs to an exclusive region that already has
an active item, this recursive call still runs a full SyncAnatomy() - which is precisely the cost
skipSyncAnatomy was added to avoid. In the split version, have the no-sync variant recurse into
itself. Final state is identical (SyncAnatomy re-evaluates visible body parts, and the single
trailing call in ResetClothing happens last), so the last-sync-wins result is the same.
--------------------------------------------------------------------------------