2016-05-26 11 views
0

私はクラスGameStateを次のように持っています。このゲームのシリアル化の無限ループをlibGdxで修正する方法はありますか?

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

私のFleetクラスは、非常に単純化されたフォーマットです。 公共クラスフリート { プライベートアレイ発送; プライベートプレーヤーの所有者。

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

簡体Playerクラス。私はJSON形式に私のゲームを保存するためにlibGdx Json.toJson(state);を使用

public class Player 
{ 
    private Array<Fleet> fleets; 

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

私は自分の情報を直接参照に保存しましたが、これは若干の問題を引き起こしました。最初に、GameStateのデータへの各参照は、シリアル化されると、Jsonリーダーによってそれ自体のインスタンスとして読み取られることです。だから、もし私がGameState xをシリアル化したら、それを逆シリアル化して、ArrayFleetをGameStateに入れてplanetsに移動し、playersに移動します。 fleetsに格納されているインスタンスへの元の参照の1つが見つかるたびに、それをそれ自身のインスタンスとして扱います。

これは、新しいゲームを読み込む前に、2つの参照が同じメモリを指していることを意味しますが、保存して再読み込みした後は、別々のメモリを指します。また、FleetShipのリストを保持しており、各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); 
    } 
} 

フリートクラス

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

これは、ディープコピーとシャローコピーの古典的な問題です。この種の状況を処理するためのさまざまなテクニックがありますが、ゲームの場合、これを処理する簡単な方法は、各オブジェクト(またはArtemisやAshleyのようなECSフレームワークを使用している場合はゲームエンティティ)に一意の識別子を割り当てることです。

オブジェクトをシリアル化するときに、他のオブジェクトをネストするのではなく、IDのリストをシリアル化するだけです。デシリアライズするときは、すべてをデシリアライズし、実際のオブジェクト参照にIDを展開する必要があります。

ここでは、提供したコードでこれを行う方法の簡単な例を示します。

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

これは、このソリューションが実行しているすべてのことは、コードの基本的な問題にパッチを当てているということです。つまり、双方向の関係を保ってコードを緊密に結合しています。

この問題を解決する方法はいくつかあります。

あなたはリレーションシップを一方向にすることができます(Playerは多くのFleetsを所有していますが、FleetsはPlayerに戻って参照しません)。これは、あなたのクラスをモデル化するために知っている典型的なOOPテクニックに従うのに役立ちます。それはまた、どのプレイヤーが艦隊を所有しているかを調べることはコストがかかることを意味する。あなたは、グラフではなく、所有権の観点から関係を考えています。これにより柔軟性も制限されますが、十分な場合もあります。

すべてのオブジェクト参照に対してインダイレクションを使用でき、基本オブジェクトにIDだけを格納できます。次に、オブジェクトにマップされたすべてのエンティティIDを格納する検索サービス(HashMapを使用)を使用します。オブジェクトが必要なときはいつでも、IDをサービスに渡すだけです。

LibGdxのjsonライブラリでサポートされているカスタムシリアル化とデシリアライズを使用できます。 idsと浅い参照を使いたいので、リンクされたオブジェクトを保存/復元するための特別なメカニズムが必要です。しかし、余分なスナップショットクラスは削除されます。

+0

答えをありがとう。私はこのようなことをすることに決めました。関連するIDを、各プレーヤーが所有する艦隊のようなもののIDを保持する 'DeserializationData'クラスに格納します。私はJson.Serializableを使ってシリアライズとデシリアライズをカスタマイズしました。 –

関連する問題