C7-Game / Prototype

An early-stage, open-source 4X strategy game
https://c7-game.github.io/
MIT License
34 stars 9 forks source link

Save Without References #425

Closed pcen closed 4 days ago

pcen commented 1 year ago

builds on #423

Discussions

There are numerous gameplay features that can be implemented, but are blocked or otherwise unnecessarily difficult to implement due to the lack of a workable save file format. Included in this set of features are https://github.com/C7-Game/Prototype/issues/102, https://github.com/C7-Game/Prototype/issues/103, and https://github.com/C7-Game/Prototype/issues/148, which are the remaining gameplay features for Carthage. These features all requiring adding new information to the save file in order to implement. Implementing these features is difficult because the current save file format is unwieldy for the following reasons:

Another future potential downsides of the naive JSON serialisation approach is for save editors and modding. For save editors, the GameData class is a huge blob of mutable state, and any editor function must ensure that the changes it applies will permeate the entire GameData class. This already must be the case for things that can happen in game (ie. when a new city is built, we already must ensure it is added to the Tile, the Player's cities, etc.) but to add new features to the editor would also need to modify all of GameData correctly (the implementation overhead is O(the amount of state GameData holds)).

I've followed the principles @QuintillusCFC laid out here: https://github.com/C7-Game/Prototype/discussions/377. Save files are directly deserialised into SaveGame which in turn creates a GameData. GameData is first converted into a SaveGame and then serialised directly to the save file - there are no C# references in serialisation or deserialisation.

  1. e pluribus unum: the SaveGame class has the same structure as the JSON file it serialises to, and no classes contained in SaveGame references any other classes in SaveGame - it is a tree of objects. Any cyclic references are stored as an ID
  2. easy to understand for human readers: everything in the c7-static-map-save.json is human readable, references are incrementing integers prefixed with human readable strings.
  3. polymorphic classes - not solved in this PR, but since SaveGame is still being serialised to JSON any solution that could be applied to the current save format can also be implemented for SaveGame

This save format also addresses another issue that requires manual work whenever new fields are added to GameData - the save needs to be regenerated from a .sav file, and any existing c7 save files cannot be salvaged due to the serialised C# references, and the fact that there is no good way to handle newly added or removed fields when using dotnet or newtonsoft json. With a value-based SaveGame format, the backwards compatibility is much easier to solve for existing save games. Since everything in the SaveGame format is "plain old data", if a new field is added, old save file formats can still be parsed, and then upgraded to the newer save format by an UpgradeSaveGameFromVersionAToVersionB (this process can be chained indefinitely for a c7 save that is n versions older than the current, by sequentially running the SaveGame through n UpgradeSaveGame... functions), while the actual GameData definition is not tied to any backward compatibility concerns.

QuintillusCFC commented 5 months ago

Thoughts (which may become obsolete as I learn more):

Going to look at a bit more once my battery isn't near-empty, and see if it can be merged into the Godot 4 branch.

pcen commented 5 months ago

@QuintillusCFC I checked the other PR I have that branches off of this branch, and it looks like I did not address the TODO in CreateGame.cs, so it should be a separate issue.