[Solved] NOT Complex Problem with Inheritance - [Answer= Mismanaged Reference]

I have been trying to chase down a bug, and I fear I am a bit in over my head in dealing with inheritance, an array, and storing these derived classes in a single container (of type: base class),

-------------------
The Bug
-------------------
Whenever I spawn a ContainerObject (a class derived from GameWorldObject), it calls the SetupNewObject() method so everything is fine. I check the ContainerObjectData (class derived from ObjectData)& all items I placed within the chest are correct.

Whenever I leave & return to the level, and it calls LoadObjectData, the ContainerObject & its ContainerObjectData has only a few of the items or none of them.

The strangest part to this bug is that if I put multiple items with the same name in slots next to each other, it will only load a single item.

Blueprint, Blueprint, Apple, Blueprint will result in Blueprint, Apple, None, None.

Blueprint, Blueprint, Blueprint will result in Blueprint, None, None, None.

Or sometimes it will randomly decide to only have the very first item, and none others.

Blueprint, None, None, None.

------------------------
The Cause???
------------------------

I am almost certain the reason this is happening is because I am doing something wrong with inheritance and typecasting / storing in the base class dictionary.

I don’t know how else to store every derived GameWorldObject / ObjectData in a single container, other than having a container with a base class.

Am I just doing the entire structure wrong? Is this just a matter of not handling the Item[ ] array correctly somewhere?

------------------------
The Details
------------------------

I have two major base classes, which all game entities derive from.

The MonoBehaviour Component

//Unity Engine
public class GameWorldObject : MonoBehaviour

And the Serializable Data Object

//Simulation Data

[Serializable]
public class ObjectData
{
    public string myCurrentLocation = "Blank";
    public myVector3 myPosition = new myVector3(); //Object's world transform.position
    public string myName = "Blank"; //Name of Prefab to load
    public string objectGUID; //UNIQUE ID
    public ObjectData()
    {
    }
    public ObjectData(ObjectData objectData)
    {
        myCurrentLocation = objectData.myCurrentLocation;
        myPosition = objectData.myPosition;
        myName = objectData.myName;
        objectGUID = objectData.objectGUID;
    }
}

I store all the simulation Data in a serializable Dictionary. The Dictionary Key is objectGUID.

public Dictionary<string, ObjectData> allMyObjectData = new Dictionary<string, ObjectData>(); //A list of all the Location's objects.

I store references to all GameObjects instantiated via their objectGUID, in another Dictionary. This container is to hold all unity gameobjects which exist.

public static Dictionary<string, GameWorldObject> allActiveGameWorldObjects = new Dictionary<string, GameWorldObject>(); //Collection of all GameWorldObjects

Now, I have many derived classes for both Data (Simulation) & GameWorldObject (Engine)

public class PickableItem : GameWorldObject
public class ContainerObject : GameWorldObject
[Serializable]
public class PickableItemData : ObjectData
[Serializable]
public class ContainerObjectData : ObjectData
{
    public Item[] Storage;

    public bool isPlayerEquipped = false;
    public bool isOpened = false;
   public bool isBundle = true;
    public ContainerObjectData()
    {
    }
    public ContainerObjectData(ObjectData objectData)
    {
        myCurrentLocation = objectData.myCurrentLocation;
        myPosition = objectData.myPosition;
        myName = objectData.myName;
        objectGUID = objectData.objectGUID;
    }
    public ContainerObjectData(ContainerObjectData containerData)
    {
        //ObjectData
        myCurrentLocation = containerData.myCurrentLocation;
        myPosition = containerData.myPosition;
        myName = containerData.myName;
        objectGUID = containerData.objectGUID;

        //ContainerObjectData
        isPlayerEquipped = containerData.isPlayerEquipped;
        isOpened = containerData.isOpened;
        isBundle = containerData.isBundle;
        Storage = containerData.Storage;
    }
}

I haven’t ran into any problems yet for any object type, except for my ContainerObjects. Specifically, their Storage. In the above, line 4 & line 31.
** ** public Item[] Storage; Storage = containerData.Storage;** **

The Item class is also Serializable Data (Part of the Simulation, not Unity Engine).

[Serializable]
public class Item
{
    //Any changes should be reflected in PickableItem.cs and the copy Constructor, and Debug_ViewableItem
    public string ItemName = "Blank";
    public int ItemStackMax = 0;
    public int CurrentItemStack = 0;

    public Item()
    {
        ItemName = "Blank";
        ItemStackMax = 0;
        CurrentItemStack = 0;
    }
    public Item(Item newItem)
    {
        ItemName = newItem.ItemName;
        ItemStackMax = newItem.ItemStackMax;
        CurrentItemStack = newItem.CurrentItemStack;
    }
}

My Serialized, Simulation Data is entirely separate from Unity. The entire world is generated in code, and then whenever I need to Render (Instantiate) a Level, I simply call one method.

This method instantiates all ObjectData (Simulation) as GameWorldObjects (Unity GameObject).

Upon instantiation of a GameWorldObject (Unity), it calls a method either based on if it is a New version from partial data or an older version Loaded from data.

I save all data to file & store the data in a dictionary with type base classes (ObjectData / GameWorldObject) even when they’re derived.

PickableItem : GameWorldObject is stored in Dictionary<string, GameWorldObject>().
ContainerObjectData : ObjectData is stored in Dictionary<string, ObjectData>().

Since a derived class ContainerObjectData has myObjectData and myContainerObjectData, I use a getter to get the proper data. This way it always returns the class type that is actually used. (Any derived GameWorldObject (Unity) uses its own derived ObjectData (Simulation).

public class ContainerObject : GameWorldObject
{
    public ContainerObjectData myContainerObjectData;
    //GameWorldObject variable: public ObjectData myObjectData
    public override ObjectData getObjectData()
    {
        return myContainerObjectData;
    }
    /*
    public virtual ObjectData getObjectData()
    {
        return myObjectData;
    }
    */

If I’m creating a new ContainerObject (Unity), then I have no problem. I create a new Storage (Item[ ] Array) and populate it with empty item data.

However it’s when I am Loading Data that I have the bug.

public override void LoadObjectData(ObjectData newObjectData)
    {
        Debug.Log("Container: LoadObjectData(ObjectData " + newObjectData.myName + ")");
        //Load ObjectData
        myContainerObjectData = (ContainerObjectData)newObjectData;
        SetAcceptItemDictionary();
        /*
        private void SetAcceptItemDictionary()
        {
            if (AcceptedItemsDictionary.Count == 0)
            {
                foreach (SpecialContainerInfo scInfo in AcceptedItems)
                {
                    AcceptedItemsDictionary.Add(scInfo.acceptedItem, scInfo.newItemMaximums);
                }
            }
        }
        */
        Debug.Log("LoadObjectData: Container Storage = " + myContainerObjectData.Storage.Length);
        //Load HarvestableItemData
        if (myContainerObjectData.isOpened)
        {
            myAnimator.Play("Opened");
        }
  
        Vector3 myPosition = new Vector3(myContainerObjectData.myPosition.x, myContainerObjectData.myPosition.y, myContainerObjectData.myPosition.z);
        transform.position = myPosition;
        name = myContainerObjectData.myName;
        RegisterGameWorldObject();
        /*
        public void RegisterGameWorldObject()
        {
            //Register with Location
            ServerWorld.allLocations[getObjectData().myCurrentLocation].myGameWorldObjectKeys.Add(getObjectData().objectGUID);
            //Register with MasterWorld
            MasterWorld.allActiveGameWorldObjects.Add(getObjectData().objectGUID, this);
        }
        */
    }

Looks like a mismanagement of references, possibly brought on by things being done not in the order you’re expecting. Just a guess, mind.

1 Like

Thanks. This helped me to focus where the problem actually was.

When I was decoupling my simulation data from Unity, I began to use the term ‘new’ when passing ObjectData.

Then I was changing the GameWorldObject’s myObjectData, but not the simulation data in the dictionary allMyObjectData.

I removed the new usage, so myObjectData and allMyObjectData[objectGUID] should share the exact same reference.

from

 public void SetupNewObject(ContainerObjectData newContainerObjectData)
    {
        //Setup ContainerObjectData
        myContainerObjectData = new ContainerObjectData(newContainerObjectData);

to

 public void SetupNewObject(ContainerObjectData newContainerObjectData)
    {
        //Setup ContainerObjectData
        myContainerObjectData = newContainerObjectData;

And when I need to take a base class ObjectData passed as a parameter, and turn it into a derived class (ContainerObjectData)? I just remove the old reference and add the new one.

        myContainerObjectData = new ContainerObjectData(newObjectData);
        MasterWorld.theWorldData.allLocationData[newObjectData.myCurrentLocation].allMyObjectData.Remove(newObjectData.objectGUID);
        MasterWorld.theWorldData.allLocationData[myContainerObjectData.myCurrentLocation].allMyObjectData.Add(myContainerObjectData.objectGUID, myContainerObjectData);

I haven’t tested it yet, but this should finally be it. Makes sense. It broke right after I recently recreated the entire system.

Nice, I hope so. I was just speaking from experience after scanning your explanations. I find this is an area where we tend to come undone.

1 Like

Nope :rage:

But I am at least getting closer.

Is there a way to keep track of the exact pointer of an object? I am not very experienced with Visual Studio or tracking references in memory when they’re hidden from the Unity Editor (ex. static data).

I put in 3 items, and only the first item actually changes in the simulation, and only the first time I interact with it (then it stops updating the allMyObjectData) even though the GameWorldObject myObjectData changes correctly. So they still have to be two different references?

Hmm, I think I finally understand the purpose of Setters, not just Getters?

I’m thinking this might be solved by using Setters to change anything in Data. This way there is never any way for the reference to change after it’s created? Unless I specifically declare ‘new’?

[Serializable]
public class ObjectData
{
    public string myCurrentLocation = "Blank";
    public myVector3 myPosition = new myVector3(); //Object's world transform.position
    public string myName = "Blank"; //Name of Prefab to load
    public string objectGUID; //UNIQUE ID

    public ObjectData() //New Object
    {
    }
    public ObjectData(ObjectData objectData) //New Object from objectData
    {
        SetObjectData(objectData);
    }
    public void SetObjectData(ObjectData objectData) //Change ObjectData without losing Reference
    {
        myCurrentLocation = objectData.myCurrentLocation;
        myPosition = objectData.myPosition;
        myName = objectData.myName;
        objectGUID = objectData.objectGUID;
    }
}

SUCCESS!!!
Parrrrtaaaaaaaaaaaayyyyyyyyy!!!

So the problem was I changed the reference whenever I would place items in the container.

That is why the first would always work but none after (first item would be placed before reference changed).

When I created the Setter for ObjectData, I went through and saw this:

 public void ClientReceive_UpdateContainerObject(byte[] objectData, float timeSent)
    {
        ContainerObjectData myObjectData = (ContainerObjectData)MasterWorld.ByteArrayToObject(objectData);
        myContainer.myContainerObjectData = myObjectData;

Changed that to

            myContainer.myContainerObjectData.SetContainerObjectData(myObjectData);

Thanks Hippo! Finally resolved. Damn reference!