Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to copy objects while maintaining consistent references?

Tags:

java

oop

copy

I want to implement a checkpoint system for a game I am working on and for that I need to copy all mutable objects within a level in order to create an independent copy of the level. In a simplified example my classes look something like this:

public class GameObject
{
    ...
    private Level level;
    ...
}

public class Level
{
    ...
    private List<GameObject> gameObjects;
    ...
}

But there is a problem: When I want to duplicate a level and its objects the references become inconsistent. For example, I could deep-copy my Level-instance and deep-copy all gameObjects. But when I do that, the Level-reference in GameObject is no longer pointing at the "right" (new) level. In this case I could just call a method for each object and reset its level. But that would get increasingly complex with more and more types of objects involded, as objects might or might not have their own sets of objects they created and then those references have to be updated as well and so on.

Is there anything like a design-pattern for this kind of situation, or anything Java-specific that makes this problem much easier?

Edit

I just had an idea: How about creating some kind of wrapper class for each GameObject that looks like this:

public class GameObjectWrapper
{
    ...
    private long gameObjectID;
    private Level level;
    ...
}

This would be given as a reference to all objects which need references. And each GameObject would get a unique id and level would have a map linking each id to an actual GameObject (something like GameObject Level.getObject(long id)). While this would probably work, I still feel like there must be a better solution.

Edit: Further clarification

It seems like I did not make my issue clear enough, so I will simplify and generalize it a bit more:

I got two example objects (objectA and objectB): objectA contains a reference to objectB and objectB contains a reference to objectA.

Something like this:

public class MyObject
{
   private MyObject partner;

   ...

   public MyObject shallowCopy()
   {
        return new MyObject(partner);
   }
}

I want to copy both. With shallow copy, I get two new objects: newObjectA and newObjectB. newObjectA's stored reference (which is supposed to point to newObjectB), however, still points to the original objectB and vice-versa.

MyObject objectA = new MyObject(), objectB = new MyObject();

objectA.setPartner(objectB);
objectB.setPartner(objectA);

MyObject newObjectA = objectA.copy(); //newObjectA's partner now is objectB (instead of newObjectB)
MyObject newObjectB = objectB.copy(); //newObjectB's partner now is objectA (instead of newObjectA)

Now, I could "just" run through all my objects and map their old objects to new objects, but that seems slow and too complicated a solution to me. What is the simplest and most efficient way to resolve this?

Note: I considered posting this to gamedev.stackexchange.com, but I found this to be more of a programming and design specific problem than a game development specific problem.

like image 707
flotothemoon Avatar asked Jun 06 '15 10:06

flotothemoon


People also ask

How can we copy object without mutating?

Similar to adding elements to arrays without mutating them, You can use the spread operator to copy the existing object into a new one, with an additional value. If a value already exists at the specified key, it will be overwritten.

Why are objects copied by references?

Objects are assigned and copied by reference. In other words, a variable stores not the “object value”, but a “reference” (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object itself.

How do you copy references in Python?

Python always works by reference, unless you explicitly ask for a copy (a slice of a built-in list is deemed to "ask for a copy" -- but a slice of a numpy array also works by reference). However, exactly because of that, alist=anotherlist; alist.


2 Answers

If you're only copying the Level object and want the references inside the list of it to point to the new copy, couldn't you just loop through them in the copy method.

Something like this:

class Level {

    List<GameObject> gameObjects;

    Level copy() {
        Level level = new Level();
        level.gameObjects = new ArrayList<>(gameObjects.size());
        for (GameObject gameObject : gameObjects) {
            level.gameObjects.add(gameObject.copyWithReference(level));
        }
        return level;
    }
}

class GameObject {

    Level level;

    GameObject copyWithReference(Level level) {
        GameObject gameObject = new GameObject();
        gameObject.level = level;
        return gameObject;
    }
}

Edit:

You could have a container for the gameObjects:

class GameObjectSet {

    Level level;
    List<GameObject> gameObjects;

    GameObjectSet copy(Level newLevel) {
        GameObjectSet gameObjectSet = new GameObjectSet();
        gameObjectSet.level = newLevel;
        // copy all gameObjects and make them point to new gameObjectSet
        return gameObjectSet;
    }
}

Now you would have a reference to a GameObjectSet in both in Level class and in GameObject class.

like image 110
Bubletan Avatar answered Oct 24 '22 15:10

Bubletan


The classic solution to checkpointing is the Memento pattern.

That said, if you ever want to persist checkpoints, Java Serialization may be what you are looking for, as it retains object identities in addition to object states (within the same stream, that is). This would be something like:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
    oos.writeObject(level);
}

byte[] checkpoint = baos.getBytes();

try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(checkpoint)) {
    Level level = (Level) ois.readObject();
    assert level == level.getGameObjects().get(0).getLevel();
}
like image 28
meriton Avatar answered Oct 24 '22 16:10

meriton