Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New persistence strategy #1559

Draft
wants to merge 41 commits into
base: master
Choose a base branch
from

Conversation

Elesbaan70
Copy link
Contributor

@Elesbaan70 Elesbaan70 commented May 7, 2022

Resolves #1502

This PR provides a new way to save data so that changes can be made to the model without breaking old savegames.

The complete task list for this project is at #1502.

Also see this wiki page: https://github.com/CitiesSkylinesMods/TMPE/wiki/Loading-and-Saving-Data

…ince non-ASCII characters are expected to be rare in TMPE data.
@Elesbaan70 Elesbaan70 added serialization load/store data in memory or on disk lifecycle related to the loading process like enabling/disabling/loading/reloading/load order/hot reload labels May 7, 2022
@Elesbaan70 Elesbaan70 self-assigned this May 7, 2022
@Elesbaan70 Elesbaan70 requested a review from krzychu124 May 7, 2022 19:23
@Elesbaan70 Elesbaan70 marked this pull request as ready for review May 31, 2022 23:13
@@ -153,11 +181,113 @@ public class SerializableDataExtension
}
}

private static void LoadDomCollection() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General question: Why XML, it might look like its a good fit, because named nodes, but that's where convenience ends. XML is hard to read for human, it is large, and it is not fast to write/parse and requires compression to be viable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Hard to read for human" is relative. It's certainly more readable than the binary format we were using before. Its readability has already been a valuable tool for me in working on #1392.

On your other points, the original choice was Json, but we were unable to find a trustworthy and full-featured Json serializer that was compatible with the particular combination of Unity and Mono versions we have to work with. Json.net is not compatible, and the Unity port of Json.net is abandoned and also nonfunctional.

The main advantage of XML is that we can use a known format and proven, stable code. If we rolled our own, we would be taking on a whole new level of maintenance responsibility.

One way we could reduce size is by using very short names, but this would pretty much destroy readability.

I'm open to other suggestions.......

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep XML, the work is done and i do not have strong objections to the format. XML seems to be the stuff used ubiquitously in business C# projects. How big serialized data for a large map can be? There are some limitations.

@@ -0,0 +1,409 @@
using CSUtil.Commons;
Copy link
Collaborator

@kvakvs kvakvs May 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is save/load easily testable? Something like:

  1. Create an object with randomized fields in allowed range (property based testing or a similar approach by coding random field values manually)
  2. Serialize
  3. Deserialize
  4. Compare
  5. For future versions try and keep an old serialized sample (one per old version) which could be tested that it still reads without errors

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will look into this. Probably best implemented in the unit test project.

using System.Text;
using System.Xml.Linq;

namespace TrafficManager.Persistence {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loading save file with features blocked, will it skip loading the incompatible sections?
When you save the game will the game lose that skipped data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no attempt to preserve unsupported data through a load and re-save. That data is completely skipped.

I would oppose adding this as a requirement. Putting it into practice in a reliable way is far more complicated than it might appear on the surface.

When saving XML data, the save code has the option of marking its data as requiring a specific feature identified in an enum. It gets added to a featuresRequired collection.

When featuresRequired comes back non-empty, the framework transfers the required feature with the highest numeric value (which would normally mean the newest feature) to the featuresForbidden collection, clears featuresRequired, and repeats the save.

The above step is repeated until the save code either (1) is no longer adding items to featuresRequired, or (2) indicates that it is no longer able to save the data. In the latter case, this means that some older versions will simply lose this data, due to either a fundamental incompatibility or an intentional choice on our part.

Outside of dev and alpha builds, we will normally choose not to write old data, since we do not support older versions. We could decide to keep writing old data for some period of time, in case an emergency rollback is necessary.

using System.Xml.Linq;

namespace TrafficManager.Persistence {
internal interface IPersistentObject : IComparable, IComparable<IPersistentObject> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of new code has no xmldoc comments. Is this some copy pasted pattern code?
Not everyone knows the meaning of this new added stuff, me included, a lot of magical new interfaces and fields and properties, no idea what they all do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will do this. (It's not copy/pasted code, it's all new.)

I just felt a little urgency to get people's eyes on this, because @krzychu124 is waiting on it for Airports DLC enhancements.

btw, I will probably be cleaning up this framework a little more and publishing it separately as an open-source library, which I'll then use in other mod work. Whether TMPE switches over to that or continues to use its own version, is a decision that can be made when the time comes.

@kvakvs
Copy link
Collaborator

kvakvs commented Jun 1, 2022

Can add tests to your checklist (at least run manual test once):

  • Old save in new serialization code (should read)
  • New save in old serialization code (should do what? ignore new and load old, or have no old and load blank settings)
  • New save in new serialization code

@Elesbaan70
Copy link
Contributor Author

Can add tests to your checklist (at least run manual test once):

Here's what I tested:

  1. Transition from binary to XML:
    1. Reading old data:
      1. Load old data in new version. No XML found, so it reads the binary data.
    2. Saving new data with backward compatibility:
      1. Save new (XML) and old (binary) data to same file (default behavior until we decide we don't need to write the binary data anymore).
      2. Load in old version. It completely ignores the XML data.
      3. Load in new version. It sees the XML data and ignores the binary data.
  2. Versioning in XML (currently only tested in Displaced lane support in traffic light simulation #1392 code):
    1. Reading old data:
      1. Load old data into new version. It detects the absence of the new feature, and reads the old way.
    2. Saving new data with backward compatibility:
      1. Save XML elements with and without the new feature.
      2. Load in old version. It skips elements with the new feature and reads elements without.
      3. Load in new version. It reads elements with the new feature and skips elements without.

@Elesbaan70
Copy link
Contributor Author

@kvakvs If you weren't already aware, the original issue that prompted this was, as indicated in #1502, the inability to revise the save format without breaking older builds. While we don't support old versions, this still creates a problem for anyone (including ourselves) who wants to run test builds.

But since we don't support old versions, the intention is that once a feature is released to stable, the legacy code that writes data the old way gets removed. (The code to read the old way has to stay pretty much forever, though I imagine we'll identify exceptions on a case-by-case basis.)

@Elesbaan70 Elesbaan70 marked this pull request as draft June 17, 2022 14:33
@Elesbaan70
Copy link
Contributor Author

I am converting this to draft, since TrafficManager.Util.Record was not addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lifecycle related to the loading process like enabling/disabling/loading/reloading/load order/hot reload serialization load/store data in memory or on disk
Projects
None yet
Development

Successfully merging this pull request may close these issues.

New persistence strategy for better cross-version compatibility
2 participants