繁体   English   中英

如何在libGdx中修复此游戏序列化无限循环?

[英]How can I fix this game serialization infinite loop in libGdx?

我的类GameState如下:

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

我的Fleet课程的非常简化的格式是: 公共舱Fleet {私人Array船; 私人玩家拥有者;

    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 ,然后反序列化它,它将在GameState中读取我的Fleet Array并移动到planets ,然后移动到players 每当它找到对存储在fleets的实例的原始引用之一时,就会将其视为自己的实例。

这意味着在加载新游戏之前,这两个引用将指向同一块内存,但是在保存并重新加载后,它们将指向单独的内存。 另外,由于Fleet保留了Ship的列表,并且每个Ship都以字段的形式包含对其父fleet的引用,因此, Json序列化器和反序列化器将因此陷入无限循环。

我试图做这个 ,因为它似乎是最简单的方法,但内森在接受的答案指出,并不是最有效的方法。

简而言之,如何重现此问题?

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错误得到无限循环。

对于深层副本还是浅层副本,这是一个经典问题。 有很多不同的技术可以处理这种情况,但是对于游戏而言,一种简单的处理方法是为每个对象(或游戏实体,如果您使用的是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;
        }
    }
}

话虽这么说,所有这些解决方案正在做的是用您的代码修补一个基本问题。 也就是说,您正在通过具有双向关系来紧密耦合代码。

您可以通过多种方式解决此问题。

您可以使关系成为单向的(一个玩家拥有许多舰队,但是舰队没有对玩家的引用)。 这将帮助您遵循已知的典型OOP技术来对类进行建模。 这也意味着查找哪个玩家拥有舰队可能是昂贵的。 您可能会以所有权树而不是图形的方式来思考关系。 这也可能会限制灵活性,但可能就足够了。

您可以对所有对象引用使用间接寻址,并将ID存储在基本对象中。 然后,您将具有一个查找服务(使用HashMap),该服务存储映射到该对象的所有实体ID。 每当需要对象时,只需将id传递到服务中即可。

我认为您可以使用自定义序列化和反序列化,这是LibGdx的json库支持的。 您需要使用id和浅引用,因此需要一些特殊的机制来保存/恢复链接的对象。 但这会删除多余的快照类。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM