Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep copy of a List

This should be a fairly simple problem to solve however I've tried several ways of doing it but the results are always the same.

I'm trying to copy a list containing GameObjects into another list. The problem is it seems I'm copying the references since any changes done to the GameObjects of the original list, also affect the ones on the new list, something that I don't want to happen. From what I've read I'm doing a shallow copy instead of a deep copy, so I tried to use the following code to clone each object:

public static class ObjectCopier
    {
        /// <summary>
        /// Perform a deep Copy of the object.
        /// </summary>
        /// <typeparam name="T">The type of object being copied.</typeparam>
        /// <param name="source">The object instance to copy.</param>
        /// <returns>The copied object.</returns>
        public static GameObject Clone<GameObject>(GameObject source)
        {

            if (!typeof(GameObject).IsSerializable)
            {
                throw new ArgumentException("The type must be serializable ", "source: " + source);
            }

            // Don't serialize a null object, simply return the default for that object
            /*if (Object.ReferenceEquals(source, null))
            {
                return default(GameObject);
            }*/

            IFormatter formatter = new BinaryFormatter();
            Stream stream = new MemoryStream();
            using (stream)
            {
                formatter.Serialize(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return (GameObject)formatter.Deserialize(stream);
            }
        }
    }

I get the following error:

ArgumentException: The type must be serializable Parameter name: source: SP0 (UnityEngine.GameObject) ObjectCopier.Clone[GameObject] (UnityEngine.GameObject source) (at Assets/Scripts/ScenarioManager.cs:121)

The function posted above is called here:

    void SaveScenario(){
        foreach(GameObject obj in sleManager.listOfSourcePoints){
            tempObj = ObjectCopier.Clone(obj);

            listOfScenarioSourcePoints.Add(tempObj);
            Debug.Log("Saved Scenario Source List Point");
        }
        foreach(GameObject obj in sleManager.listOfDestPoints){
            tempObj = ObjectCopier.Clone(obj);
            listOfScenarioDestPoints.Add(tempObj);
            Debug.Log("Saved Scenario Dest List Point");
        }
    }

    void LoadScenario(){
        sleManager.listOfSourcePoints.Clear();
        sleManager.listOfDestPoints.Clear ();
        foreach(GameObject obj in listOfScenarioSourcePoints){
            tempObj = ObjectCopier.Clone(obj);
            sleManager.listOfSourcePoints.Add(tempObj);
            Debug.Log("Loaded Scenario Source List Point");
        }
        foreach(GameObject obj in listOfScenarioDestPoints){
            tempObj = ObjectCopier.Clone(obj);
            sleManager.listOfDestPoints.Add(tempObj);
            Debug.Log("Loaded Scenario Dest List Point");
        }
    }

Now, the original list is created here:

if (child.name == "DestinationPoints")
{
     parentDestinationPoints = child.gameObject;
     foreach (Transform grandChildDP in parentDestinationPoints.transform)
     {
          //Debug.Log("Added DP object named: " + grandChildDP.name);
          tempObj = grandChildDP.gameObject;
          listOfDestPoints.Add(tempObj);
          tempObj.AddComponent<DestinationControl>();
          tempObj.transform.renderer.material.color = Color.white;
     }
}

// Hide all SourcePoints in the scene
if (child.name == "SourcePoints")
{
    parentSourcePoints = child.gameObject;
    foreach (Transform grandChildSP in parentSourcePoints.transform)
    {
         tempObj = grandChildSP.gameObject;
         listOfSourcePoints.Add(tempObj);
         tempObj.transform.renderer.enabled = false;
    }
}

This "tempObj" has the [SerializeField] property so I must be missing something here. Any help would be greatly appreciated.

EDIT: Forgot to mention, this app is in Unity3D.

like image 469
user3019217 Avatar asked Nov 02 '22 03:11

user3019217


1 Answers

I think you need to mark your class GameObject that you are attempting to clone with the [Serializable] attribute. I would also make the clone method generic so that it is not limited to type:

/// <summary>
/// Creates a deep clone of an object using serialization.
/// </summary>
/// <typeparam name="T">The type to be cloned/copied.</typeparam>
/// <param name="o">The object to be cloned.</param>
public static T DeepClone<T>(this T o)
{
    using (MemoryStream stream = new MemoryStream())
    {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(stream, o);
        stream.Position = 0;
        return (T)formatter.Deserialize(stream);
    }
}

I hope this helps.


Edit. Perhaps a deep copy without serialization using some thing like

class A
{
    // copy constructor
    public A(A copy) {}
}

// A referenced class implementing 
class B : IDeepCopy
{
    object Copy() { return new B(); }
}

class C : IDeepCopy
{
    A A;
    B B;
    object Copy()
    {
        C copy = new C();

        // copy property by property in a appropriate way
        copy.A = new A(this.A);
        copy.B = this.B.Copy();
     }
}

I have also just found Copyable which uses reflection to provide deep copies of objects. Again, I hope this was of some use...

like image 155
MoonKnight Avatar answered Nov 13 '22 17:11

MoonKnight