Как копировать объекты при сохранении согласованных ссылок?

Я хочу внедрить систему контрольных точек для игры, над которой я работаю, и для этого мне нужно скопировать все изменяемые объекты в пределах уровня, чтобы создать независимую копию уровня. В упрощенном примере мои классы выглядят примерно так:

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

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

Но есть проблема: когда я хочу дублировать уровень и его объекты, ссылки становятся непоследовательными. Например, я мог бы глубоко скопировать мой экземпляр уровня и полностью скопировать все объекты gameObjects. Но когда я это делаю, ссылка уровня в GameObject больше не указывает на "правильный" (новый) уровень. В этом случае я мог бы просто вызвать метод для каждого объекта и reset его уровень. Но это будет становиться все более сложным с увеличением количества типов объектов, поскольку объекты могут иметь или не иметь собственных наборов объектов, которые они создали, а затем эти ссылки также должны быть обновлены и так далее.

Есть ли что-то вроде шаблона дизайна для такого рода ситуаций или что-то специфическое для Java, что значительно облегчает эту проблему?

Edit

У меня просто появилась идея: как насчет создания какого-то класса-оболочки для каждого GameObject, который выглядит так:

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

Это будет дано как ссылка на все объекты, которые нуждаются в ссылках. И каждый GameObject получит уникальный идентификатор, и уровень будет иметь карту, связывающую каждый идентификатор с фактическим GameObject (что-то вроде GameObject Level.getObject(long id)). Хотя это, вероятно, будет работать, я все еще чувствую, что должно быть лучшее решение.

Изменить: дальнейшее уточнение

Похоже, я не делал свою проблему достаточно ясной, поэтому я упрощу и обобщу ее еще немного:

Я получил два примера объектов (objectA и objectB): objectA содержит ссылку на objectB и objectB содержит ссылку на objectA.

Что-то вроде этого:

public class MyObject
{
   private MyObject partner;

   ...

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

Я хочу скопировать оба. С мелкой копией я получаю два новых объекта: newObjectA и newObjectB. newObjectA сохраненная ссылка (которая должна указывать на newObjectB), тем не менее, указывает на исходный objectB и наоборот.

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

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

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

Теперь я могу "просто" запустить все мои объекты и сопоставить их старые объекты с новыми объектами, но это кажется медленным и слишком сложным решением для меня. Каков самый простой и эффективный способ решить эту проблему?

Примечание. Я рассматривал возможность публикации этого файла на gamedev.stackexchange.com, но я нашел, что это скорее проблема программирования и дизайна, чем проблема, связанная с развитием игры.

Ответ 1

Если вы копируете только объект Level и хотите, чтобы ссылки внутри его списка указывали на новую копию, вы не могли бы просто пропустить их в методе копирования.

Что-то вроде этого:

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:

У вас может быть контейнер для 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;
    }
}

Теперь у вас будет ссылка на GameObjectSet как в классе Level, так и в классе GameObject.

Ответ 2

Классическим решением для контрольной точки является Memento pattern.

Тем не менее, если вы когда-либо захотите перенести контрольно-пропускные пункты, то Java Serialization может быть тем, что вы ищете, поскольку она сохраняет идентификаторы объектов в дополнение к состояниям объектов (в том же потоке, то есть). Это будет примерно так:

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();
}