As stated in issue #103, there is currently no easy way to customize cloning behavior other than taking over completely, either by overriding OnCopyTo (Components, Resources), or by implementing ICloneExplicit (other classes).
As soon as the above attributes are in place and working, replace some of the internal implementations of ICloneExplicit with using the attribute.
Some general thoughts on this before diving right in:
Status Report: Created a "cloning" branch for this issue and began rewriting the whole Cloning system using the concept described above. Satisfied all existing Unit Tests, wrote some more and satisfied them too.
Status Report: Wrote a lot of Unit Tests, added support for the new ICloneExplicit interface, added support for Skip- and Identity-Fields, removed a lot of old code and fixed pragma warnings.
I've also run some first performance measurements, yielding that the new system is approximately 1.42 times slower than the old one. This definitely needs to change. The goal is to get it working at the same speed.
Side Note: Investigate whether ReflectionHelper.CreateInstanceOf can be improved.
Status Report: Implemented surrogates and did some refactoring on ReflectionHelper. DelegateSurrogates are now actually much better than before, as they are treated like weak references, so object A doesn't pass on all its event subscriptions to its clone, if they happen to refer to objects outside the cloned object graph. Performance has been improved slightly.
Status Report: Implemented all old ICloneExplicit cases in first iteration. Did a lot of performance improvements - the complex object graph test case went down from 480ms to 130ms, which may actually be more than twice as fast as the old system. Which would be pretty awesome, if proven true in real-world use cases.
Status Report: Fixed a lot of bugs and wrote tests for them to make sure they don't come back. Not done yet, it's still in the "crash and burn" phase from a productive standpoint. At least Prefabs don't work yet, and there are probably more bugs that haven't been discovered.
On the plus side, most of these bugs stem from the fact that the old system was a hacky bunch of special case handling, and now those special cases are gone - but some parts of the code still rely on it. So yes, the Duality world is burning right now, but it's a cleansing fire.
( In case I just scared you away from pulling updates, it all happens in the cloning branch. The master branch remains unaffected. )
Status Report: Fixed some more bugs in the cloning system and modified the API and internal routines to allow reusing existing objects in a CopyDataTo operation. This is vital in order to apply Prefabs to existing objects, since existing object hierarchies and Components will need to be used without re-instantiation.
Also found an issue with copying data to existing GameObjets: Right now, due to the fully automated GameObject cloning, no events will be fired, but this is necessary in those cases. Need to fix this using a manual ICloneExplicit implementation in GameObjects. Make sure this implementation works seamlessly with AutoHandleObject calls and doesn't look too bad.
Status Report: Fixed the GameObject issue by providing an ICloneExplicit implementation. Unlike the one from the old system, it will also remove Components and child objects when necessary, so that's kind of a free fix +1 along the way. Also improved the general ICloneSetup and ICloneOperation API to be more convenient and straightforward. All Unit Tests end successful now.
Status Report: Resolved a Delegates ownership inversion and introduced "merge surrogates" to do it properly. When copying a delegate field from a source object to an existing target object, the target graph will keep all subscriptions it doesn't own and add target versions of all subscriptions owned by the source graph.
void Foo(Bar source, Bar target, Bar other)
source.SomeEvent += source.Child.ReceiveEventA;
source.SomeEvent += other.Child.ReceiveEventA;
source.SomeEvent += StaticReceiveEventA;
target.SomeEvent += target.Child.ReceiveEventB;
target.SomeEvent += other.Child.ReceiveEventB;
target.SomeEvent += StaticReceiveEventB;
In the above code snippet, firing the target event will trigger the following receivers:
The target event will not trigger the following receivers:
It may look complicated at first, but it makes sense when you think about it. Object usually subscribe and unsubscribe themselves to events. Of the events owner is copied, you wouldn't expect to be suddenly subscribed to the copy as well.
Status Report: Wrote a lot of Prefab unit tests, and some more to check for specific issues. Prefabs now allow GameObjects to carry additional Components and children again.
Status Report: Did a lot of testing, fixed bugs and wrote unit tests. Manually tested nested Prefabs and complex RigidBody setups being cloned, all successful without further ado. So far, no issues with UndoRedo occurred. Due to the positive test results, I'm considering to merge the cloning branch into the main branch soon, but I might wait a while until distributing new NuGet packages.
Status Report: Did a side-by-side performance comparison cloning a complex Scene in the old system vs. the new one. Am horrified by the results, because the new one takes 15x the time of the old one. This needs to change before considering a merge.
Status Report: Improved performance, so the new system is now at 2x the time of the old one in a real-world use case. Still not good enough. Need to improve this.
Status Report: Removed source-target mapping from CloneProvider for value types and cleaned up some code.
Status Report: Optimized ContentRef behavior without notable performance impact. Failed in various other ways to improve performance any further.
Status Report: Added manual cloning implementations for widely used Duality Components in order to improve performance. The CloneProvider now also uses precompiled functions to traverse an objects fields in the setup step. The new system still takes 1.5x the time, which is kind of okay, given the fact that it actually works in a lot more cases than the old one.
Can get down to about 1.2x when treating ContentRefs as plain old data, but that would break all of them when they refer to a temporary Resource within the cloned object graph, like the old system would have.
Comparing performance from the old system to the new one is kind of unfair though, because the old system didn't actually work in a lot of cases. I guess 1.5x is fine - it can still clone 25000 simple GameObjects and their 1-3 Components in under 250ms on my test system. That's actually cloning 1600 objects per frame and still having 60 FPS, which would be an insane thing to do anyway.Looking at more realistic use cases:
Also fixed some bugs and tested some more use cases. The new system seems to work fine so far. Getting ready to merge.
Status Report: Added more unit tests, streamlined copying value types internally. Down to 1.3x the time of the old system, which is pretty good given the fact that it actually needs to do more than twice the work in order to handle all cases correctly. Spawning objects from prefabs now takes 7x the time of manually instantiating it, instead of 9x like before.
Status Report: Added support for partial cloning, e.g. "Clever SavePoints".
Status Report: Merged cloning into the master branch. Observe how it behaves, then commit new packages.
New NuGet packages have been deployed. Closing this issue.