2016-05-26 2 views
0

У меня есть класс GameState следующим образом;Как я могу исправить бесконечный цикл сериализации игры в libGdx?

public class GameState 
{ 
    private Array<Fleet> fleets; 
    private Array<Planet> planets; 
    private Array<Player> players; 
    //Constructors, methods, yada yada 
} 

Очень упрощенный формат моего класса Fleet; общественный класс Fleet { частные корабли; частный владелец;

public Fleet(Player owner) 
    { 
     this.owner = owner; 
     this.ships = new Array<Ship>(); 
    } 
    //Methods 
} 

Упрощенный Player класс;

public class Player 
{ 
    private Array<Fleet> fleets; 

    public Player() 
    { 
     fleets = new Array<Fleet>(); 
    } 
} 

Я использую libGdx Json.toJson(state);, чтобы сохранить свою игру в формате JSON.

Я сохранил информацию в прямых ссылках, но это вызвало некоторые незначительные проблемы. Во-первых, каждая ссылка на данные в GameState, когда она сериализована, считывается читателем Json как ее собственный экземпляр. Итак, если бы я сериализовал GameState x, то десериализовал его, он прочитал бы мой Array из Fleet в GameState и переместился на planets, затем переместился на players. Всякий раз, когда он нашел одну из исходных ссылок на экземпляр, хранящийся в fleets, он рассматривал бы его как свой собственный экземпляр.

Это означает, что перед загрузкой новой игры две ссылки указывают на одну и ту же часть памяти, но после сохранения и перезагрузки они будут указывать на отдельные фрагменты памяти. Кроме того, поскольку Fleet хранит список Ship в нем, и каждый Ship содержит ссылку в форме своего родительского поля fleet, сериализатор Json и десериализатор будут выбрасываться в бесконечный цикл из-за этого.

Я пытался сделать this, как это казалось в простой подход, но, как Натан отметил в принятом ответе, не самый эффективный подход.

Как воссоздать эту проблему короче?

GameState класс

public class GameState 
{ 
    public Array<Player> players; 

    public GameState() 
    { 
     this.players = new Array<Player>(); 
     players.add(new Player()); 
     players.add(new Player()); 
    } 
} 

класс игрока

public class Player 
{ 
    public Array<Fleet> fleets; 

    public Player() 
    { 
     fleets = new Array<Fleet>(); 
    } 

    public void addFleet(Fleet fleet) 
    { 
     fleets.add(fleet); 
    } 
} 

Fleet класс

public class Fleet() 
{ 
    public Player owner; 

    public Fleet(Player owner) 
    { 
     this.owner = owner; 
     this.owner.fleets.add(this); 
    } 
} 

MainGame класс

public class MainGame extends Game 
{ 
    @Override 
    public void create() 
    { 
     GameState state = new GameState(); 
     state.fleets.add(new Fleet(state.players.get(0))); 
     state.fleets.add(new Fleet(state.players.get(1))); 

     Json json = new Json(); 
     String infiniteLoopOrStackOverflowErrorHappensHere = json.toJson(state); 
     state = json.fromJson(infiniteLoopOrStackOverflowErrorHappensHere); 
    } 
} 

Вы должны получить бесконечный цикл из этого или ошибку StackOverflow.

ответ

2

Это классическая проблема с глубокими копиями против мелких копий. Существует множество различных методов обработки такого рода ситуаций, но для игры простой способ справиться с этим - назначить уникальные идентификаторы каждому объекту (или игровому объекту, если вы используете инфраструктуру ECS, такую ​​как Artemis или Ashley).

Когда вы сериализуете объекты, вместо того, чтобы вставлять другие объекты, просто сериализуйте список идентификаторов. Когда вы выполните десериализацию, вам нужно будет десериализовать все, а затем расширить идентификаторы в фактические ссылки на объекты.

Что я сказал ниже, это простой пример того, как это сделать с кодом, который вы предоставили.

public class MainGame extends ApplicationAdapter { 

    @Override 
    public void create() { 
     final Player player0 = new Player(); 
     final Player player1 = new Player(); 
     final Fleet fleet0 = new Fleet(player0); 
     player0.fleets.add(fleet0); 
     final Fleet fleet1 = new Fleet(player1); 
     player1.fleets.add(fleet1); 

     GameState state = new GameState(); 
     state.players.add(player0); 
     state.players.add(player1); 
     state.fleets.add(fleet0); 
     state.fleets.add(fleet1); 


     final Json json = new Json(); 
     final String infiniteLoopOrStackOverflowErrorHappensHere = json.toJson(state.toGameSaveState()); 
     state = json.fromJson(GameSaveState.class, infiniteLoopOrStackOverflowErrorHappensHere).toGameState(); 
    } 
} 

public abstract class BaseEntity { 
    private static long idCounter = 0; 

    public final long id; 

    BaseEntity() { 
     this(idCounter++); 
    } 

    BaseEntity(final long id) { 
     this.id = id; 
    } 
} 

public abstract class BaseSnapshot { 
    public final long id; 

    BaseSnapshot(final long id) { 
     this.id = id; 
    } 
} 

public class Fleet extends BaseEntity { 
    public Player owner; 

    Fleet(final long id) { 
     super(id); 
    } 

    public Fleet(final Player owner) { 
     this.owner = owner; 
     //this.owner.fleets.add(this); --> Removed because this is a side-effect! 
    } 

    public FleetSnapshot toSnapshot() { 
     return new FleetSnapshot(id, owner.id); 
    } 


    public static class FleetSnapshot extends BaseSnapshot { 
     public final long ownerId; 

     //Required for serialization 
     FleetSnapshot() { 
      super(-1); 
      ownerId = -1; 
     } 

     public FleetSnapshot(final long id, final long ownerId) { 
      super(id); 
      this.ownerId = ownerId; 
     } 

     public Fleet toFleet(final Map<Long, BaseEntity> entitiesById) { 
      final Fleet fleet = (Fleet)entitiesById.get(id); 
      fleet.owner = (Player)entitiesById.get(ownerId); 
      return fleet; 
     } 
    } 
} 

public class GameSaveState { 
    public final Array<PlayerSnapshot> playerSnapshots; 
    public final Array<FleetSnapshot> fleetSnapshots; 

    //required for serialization 
    GameSaveState() { 
     playerSnapshots = null; 
     fleetSnapshots = null; 
    } 

    public GameSaveState(final Array<PlayerSnapshot> playerSnapshots, final Array<FleetSnapshot> fleetSnapshots) { 
     this.playerSnapshots = playerSnapshots; 
     this.fleetSnapshots = fleetSnapshots; 
    } 

    public GameState toGameState() { 
     final Map<Long, BaseEntity> entitiesById = constructEntitiesByIdMap(); 

     final GameState restoredState = new GameState(); 
     restoredState.players = restorePlayerEntities(entitiesById); 
     restoredState.fleets = restoreFleetEntities(entitiesById); 
     return restoredState; 
    } 

    private Map<Long, BaseEntity> constructEntitiesByIdMap() { 
     final Map<Long, BaseEntity> entitiesById = new HashMap<Long, BaseEntity>(); 

     for (final PlayerSnapshot playerSnapshot : playerSnapshots) { 
      final Player player = new Player(playerSnapshot.id); 
      entitiesById.put(player.id, player); 
     } 

     for (final FleetSnapshot fleetSnapshot : fleetSnapshots) { 
      final Fleet fleet = new Fleet(fleetSnapshot.id); 
      entitiesById.put(fleet.id, fleet); 
     } 

     return entitiesById; 
    } 

    private Array<Player> restorePlayerEntities(final Map<Long, BaseEntity> entitiesById) { 
     final Array<Player> restoredPlayers = new Array<Player>(playerSnapshots.size); 
     for (final PlayerSnapshot playerSnapshot : playerSnapshots) { 
      restoredPlayers.add(playerSnapshot.toPlayer(entitiesById)); 
     } 
     return restoredPlayers; 
    } 

    private Array<Fleet> restoreFleetEntities(final Map<Long, BaseEntity> entitiesById) { 
     final Array<Fleet> restoredFleets = new Array<Fleet>(fleetSnapshots.size); 
     for (final FleetSnapshot fleetSnapshot : fleetSnapshots) { 
      restoredFleets.add(fleetSnapshot.toFleet(entitiesById)); 
     } 
     return restoredFleets; 
    } 
} 

public class GameState { 
    public Array<Player> players = new Array<Player>(); 
    public Array<Fleet> fleets = new Array<Fleet>(); 

    public GameSaveState toGameSaveState() { 
     final Array<PlayerSnapshot> playerSnapshots = new Array<PlayerSnapshot>(players.size); 
     final Array<FleetSnapshot> fleetSnapshots = new Array<FleetSnapshot>(fleets.size); 

     for (final Player player : players) { 
      playerSnapshots.add(player.toSnapshot()); 
     } 

     for (final Fleet fleet : fleets) { 
      fleetSnapshots.add(fleet.toSnapshot()); 
     } 

     return new GameSaveState(playerSnapshots, fleetSnapshots); 
    } 
} 

public class Player extends BaseEntity { 
    public Array<Fleet> fleets = new Array<Fleet>(); 

    public Player() {} 

    Player (final long id) { 
     super(id); 
    } 

    public PlayerSnapshot toSnapshot() { 
     final Array<Long> fleetIds = new Array<Long>(fleets.size); 
     for(final Fleet fleet : fleets) { 
      fleetIds.add(fleet.id); 
     } 

     return new PlayerSnapshot(id, fleetIds); 
    } 


    public static class PlayerSnapshot extends BaseSnapshot { 
     public final Array<Long> fleetIds; 

     //Required for serialization 
     PlayerSnapshot() { 
      super(-1); 
      fleetIds = null; 
     } 

     public PlayerSnapshot(final long id, final Array<Long> fleetIds) { 
      super(id); 
      this.fleetIds = fleetIds; 
     } 

     public Player toPlayer(final Map<Long, BaseEntity> entitiesById) { 
      final Player restoredPlayer = (Player)entitiesById.get(id); 
      for (final long fleetId : fleetIds) { 
       restoredPlayer.fleets.add((Fleet)entitiesById.get(fleetId)); 
      } 
      return restoredPlayer; 
     } 
    } 
} 

Это, как говорится, все это решение исправляет фундаментальную проблему с кодом, который у вас есть. То есть, вы делаете свой код плотно связанным, имея двунаправленные отношения.

Существуют различные способы решения этой проблемы.

Вы можете сделать отношения однонаправленными (игроку принадлежит много флотов, но у флотов нет ссылки на игрока). Это поможет вам следовать типичным методам ООП, которые вы знаете для моделирования ваших классов. Это также означает, что поиск того, кто владеет флотом, может быть дорогостоящим. Вы бы думали о взаимоотношениях с точки зрения дерева собственности, а не графа. Это также может ограничивать гибкость, но этого может быть достаточно.

Вы можете использовать косвенность для всех ссылок на объекты и просто сохранить идентификаторы в базовом объекте. Затем у вас будет служба поиска (с использованием HashMap), которая хранит все идентификаторы объектов, сопоставленные с объектом. Всякий раз, когда вы хотите объект, вы просто передаете идентификатор в службу.

Вы можете использовать пользовательскую сериализацию и десериализацию, которая поддерживается библиотекой json библиотеки LibGdx. Вы хотите использовать идентификаторы и мелкие ссылки, поэтому вам понадобится специальный механизм для сохранения/восстановления связанных объектов. Но это позволит обрезать дополнительные классы снимков.

+0

Спасибо за ответ. Я решил сделать что-то вроде этого, сохранив соответствующий идентификатор в классе «DeserializationData», который содержит идентификаторы таких вещей, как флоты, которыми владеет каждый игрок. Я настроил сериализацию и десериализацию с помощью Json.Serializable. –

Смежные вопросы