The following code illustrates the recommended method to implement ReferenceTarget::Clone()
and ReferenceTarget::BaseClone()
in a plug-in when you only have strong references.
class MyDerivedPlugin
: public MyBasePlugin
{
const int MY_REFERENCE = 1;
ReferenceTarget* Clone(RemapDir& remap)
{
ReferenceTarget* result = new MyDerivedPlugin();
BaseClone(this, result, remap);
return result;
}
void BaseClone(ReferenceTarget* from, ReferenceTarget* to, RemapDir& remap)
{
if (!to || !from || from == to)
return;
MyBasePlugin::BaseClone(from, to, remap);
to->ReplaceReference(MY_REFERENCE, remap->CloneRef(from->GetReference(MY_REFERENCE)));
}
};
It is also possible to perform calls to ReferenceMaker::ReplaceReference()
in the Clone
method itself, but this can lead to complications if you ever want to derive a new plug-in from your old plug-in. Specifically, you cannot call base plug-in's Clone
method and would have to be sure to update all references managed by the derived plug-in correctly in your new derived Clone()
method.
Weakly or indirectly referenced objects should not be cloned, only the pointers should be copied. This means that you do not want to use CloneRefHierarchy()
or RemapDir::CloneRef()
on a weakly or indirectly referenced object.
Consider the scenario that if two nodes A and B are cloned at the same time, creating nodes C and D. Under node A is a weak reference to node B. After the clone, if you look at the weak reference in C, it should be pointing at D, not at B. To ensure this you have to use the method RemapDir::AddPostPatchProc()
passing a class derived from PostPatchProc
, to replace weak references that exist to objects which were part of the cloned hierarchy.
The following code demonstrates a method for implementing BaseClone()
in a plug-in derived from BaseClass
that that has no data other than references to copy.
// This class is used to patch up references at the end of a clone operation.
// An instance of this class is created and registered with the RemapDir if
// a target has not been cloned when the owner is cloned. The Proc
// will see if the target has been cloned at the end of the clone operation,
// and replace the target ref with the clone ref if so.
class PostPatchReference : public PostPatchProc
{
ReferenceMaker* owner;
ReferenceTarget* target;
public:
PostPatchReference(ReferenceMaker* owner, ReferenceTarget* target)
: owner(owner), target(target)
{ }
virtual int Proc(RemapDir&)
{
// Make sure we still hold a ref to the original target. In some cases we won't. For example,
// if owner is a controller on pos track, and target is part of object channel, and you shift-drag to
// clone, object ref is cloned by NodeSelectionProcessor::ChangeCloneType and it then remaps
// references, and then the PostPatchProcs are called. So by the time we are called, the original
// target has been replaced with its clone.
int refNum = owner->FindRef(target);
if (refNum >= 0)
{
ReferenceTarget *newTarget = remap.FindMapping(target); // see if target was cloned
if (newTarget != NULL && target != newTarget) // yep, update owner
owner->ReplaceReference(refNum, newTarget);
}
return TRUE;
}
};
virtual void MyPlugin::BaseClone(ReferenceTarget* from, ReferenceTarget* to, RemapDir& remap)
{
if (!from || !to || from == to)
return;
int numRefs = NumRefs();
for (int i=0; i < numRefs; ++i)
{
ReferenceTarget* fromTarget = from->GetReference(i);
if (from->IsRealDependency(fromTarget))
to->ReplaceReference(i, remap.CloneRef(fromTarget));
else
{
// see if target is already cloned. If so, point at it. If not, we don't want to
// force a clone, so we will register a PostPatchProc to check again at the end
// of the clone, and update our ref to the clone if present.
ReferenceTarget *newTarget = remap.FindMapping(fromTarget);
if (newTarget)
to->ReplaceReference(i, newTarget);
else
{
to->ReplaceReference(i, fromTarget);
remap.AddPostPatchProc(new PostPatchReference(to, fromTarget), true);
}
}
}
// Call the base class's implementation of BaseClone
BaseClass::BaseClone(from, to, remap);
}