Skip to content

Conversation

@Mosakaas
Copy link

@Mosakaas Mosakaas commented Jan 1, 2026

SaveFile has been reimplemented from the ground up to provide the user with an easy and optimized way to save and load their game data, whilst remaining completely extendible!

SaveFile is now build up of 3 parts: IStreamIO / IAsyncStreamIO, IStreamCompressor, and IStreamSerializer / IAsyncStreamSerializer. These handle saving-concerns separately and, through their use of streams, optimally. A SaveFile requires a combination of these types in one of its constructors, after which its behaviour remains consistent.

new SaveFile(
  SaveChunk, 
  new FileStreamIO(SaveFilePath), 
  new JsonStreamSerializer(JsonOptions), 
  new GZipStreamCompressor()
);

The above is a very common use case, and a static method is provided to make it easier to read:

SaveFile.CreateGZipJsonFile(SaveChunk, SaveFilePath, JsonOptions);

The beauty of an extendible system like this is that it allows creators to experiment with different ways of saving easily. They will be able to experiment with serialization methods or cloud providers easily, without incurring too much development overhead. Additionally, any extensions writen may be contributed back to SaveFileBuilder / the community.


Tests have been rewritten to use Xunit and do away with GodotTest. Since SaveFileBuilder can be its own standalone package, testing through GodotTest is an unnecessary step that complicates the testing process.

Mocking has been used to test SaveFile and its many possible configurations. Note that, should an interface implementation be tested, this should be done separately from any SaveFileTests. SaveFileTests only test the flow / interaction between its interfaces, not their implementations.


The ReadMe / documentation has been rewritten with a bigger focus on standalone applications. This makes the documentation more generally applicable: SaveFile could be easier implemented into standard C# applications without a need or understanding of the rest of the Chickensoft Ecosystem. However, a separate section in the documentation specifically details how to use it in Godot / with Chickensoft, so that this information is still readily available.

* Provide Stream Saver Interfaces

* Add IIOStreamProvider file implementation

* Add IAsyncIOStreamProvider http implementation

Currently missing some constructor parameters for HttpClient initialization.

* Add IStreamSerializer and IAsyncStreamSerializer implementation for Json

* Rewrite SaveFile to use interfaces.

Additionally, expose a static class SaveFile with the method CreateGZipJsonFile. This returns a SaveFile using File Input/Output, Json Serialization and GZip compression. This is likely how the SaveFile will mostly be used.

* parameter should be interface instead of class

* Add documentation

* Exhaustive overloads for SaveFile.CreateGZipJsonFile

Now you can call this method with a JsonSerializerContext or JsonTypeInfo parameter.

* Functionally tested HttpIO

The HttpIO is ready for the field! It now exposes the necessary headers and optional Request uris, some easy-to-use constructors and properly disposes its objects.

* Remove null-forgiving operators from deserialize extension methods

* Remove ConfigureAwait(false) from all async calls as it introduces save issues

* Fix ContentLength = 0 on Write

* Add namespaces for Compression, IO, and Serialization

* Fix IAsyncIOStreamProvider

We need to compress the memory stream and leave it open BEFORE writing it with our IAsyncIOStreamProvider.

Note that this isn't a completely correct name, since it doesn't provide a write / input stream, it accepts one. Will need to change this once a good name has been devised.

* Add HttpIO documentation

* Fix naming in static SaveFile

Gzip should be GZip
The dependency on Godot for running tests is unnecessary, as all of the functionality in this library is based on native C#. All of the tests have been rewritten to use xunit for quicker testing. Moq has been used for testing the SaveFile class and its many possible configurations based on the given interfaces.
IIOStreamProvider -> IStreamIO
IAsyncIOStreamProvider -> IAsyncStreamIO
ICompressionStreamProvider -> IStreamCompressor

These namechanges make it easier to form a connection between IStreamIO, IStreamCompresor, and IStreamSerializer as part of a bigger whole.
This makes the documentation more generally applicable: SaveFile could be easier implemented into standard C# applications without a need or understanding of the rest of the Chickensoft Ecosystem.
In reality, the order of saving goes io, compression, serialization. But from a user-perspective, the order of priority goes io, serialization, compression. In order to make the documentation come across a little more natural, the order of priority is applied in the documentation.
@Mosakaas
Copy link
Author

Mosakaas commented Jan 1, 2026

Important

Version number still needs to be updated. I would like help to know all the places the version number occurs, to make sure I update them all.

@Mosakaas
Copy link
Author

Mosakaas commented Jan 1, 2026

Important

I have not written any tests for any implementations of io, serialization or compression. However, I have tested all my implementations against the Chickensoft GameDemo and can assure they work.

Let me know if it is desirable I still write tests for my implementations or not.

@wlsnmrk
Copy link
Contributor

wlsnmrk commented Jan 1, 2026

[!Important]

Version number still needs to be updated. I would like help to know all the places the version number occurs, to make sure I update them all.

Good question! Versioning is handled by our release workflows. The version number in project files should always be left at 0.0.0-devbuild and not modified by hand.

@jolexxa jolexxa self-requested a review January 1, 2026 19:01
@jolexxa jolexxa added the enhancement New feature or request label Jan 1, 2026
@Mosakaas
Copy link
Author

Mosakaas commented Jan 1, 2026

The ci test fails because of the syntax nameof(SaveFile<>) in my code (at SaveFile.cs line 77).
This is supported syntax, so it should be supported in the tests.yaml I think.
If you have any idea why this might occur I'll leave it to you, otherwise I can take a look into fixing it.

In multiple places, "compressor" was written as "compresser".
@Mosakaas
Copy link
Author

Mosakaas commented Jan 3, 2026

The ci test fails because of the syntax nameof(SaveFile<>) in my code (at SaveFile.cs line 77). This is supported syntax, so it should be supported in the tests.yaml I think. If you have any idea why this might occur I'll leave it to you, otherwise I can take a look into fixing it.

So, MY understanding of the situation (which may be incorrect):
<LangVersion>preview</LangVersion> results in C# 14 for me, because I am using Visual Studio 2026, but it result in C# 13 for ubuntu-latest because their servers do not support C# 14 out of the box (again, I might be completely incorrect in how I understand this). We had some issues with pipelines and upgrading at work too.

We could look into fixing it, but for now I'll simply change my code a little. I suspect that, over time, ubuntu-latest will support this and everything will be fixed by itself, and since it's only 1 simple issue it's not a big deal for now.

Note

I also took a glance at fixing this in the pipeline through setup-dotnet, but that's only for specifying the sdk. I do not fully understand how it arrives at C# 14, and I decided this was not worth a bottleneck.

The ci pipeline does not support C# 14 out of the box. We could look into fixing this, but it might be fixed by itself in the future. For now, these small syntax changes are much easier to rely upon.
@jolexxa
Copy link
Member

jolexxa commented Jan 4, 2026

Yeah, .NET updates are slow to trickle through. I am watching this with keen interest but will hold off on reviewing until I can see what the PR for the game demo that uses this looks like. That will allow me to review more from a dog-fooding perspective and do it justice.

@Mosakaas
Copy link
Author

Mosakaas commented Jan 5, 2026

Silly me, now it builds, but it still expects Godot to run tests.

I'll update the workflow file later, but today I start with work again. So I expect my response to issues may slow down a little.

That should be the last one.
This is more true to the nature of these methods.
@Mosakaas
Copy link
Author

Mosakaas commented Jan 6, 2026

I've removed godot-testing from the workflow file and changed it to dotnet testing.

I also wanted to remove "msbuild-sdks": { "Godot.NET.Sdk": "4.5.1" } from the global.json, but it's still used in the release.yaml:

      - name: 🧮 Compute Next Version
        uses: chickensoft-games/next-godot-csproj-version@v1
        id: next-version
        with:
          project-version: ${{ steps.current-version.outputs.tag }}
          godot-version: global.json
          bump: ${{ inputs.bump }}

I am unsure how this functions, so let me know how I should change this code so that any references to godot may be omitted.

Didn't think about changing the workflow before: mistakes creep up.
@Mosakaas
Copy link
Author

Mosakaas commented Jan 6, 2026

Also... I don't know if changing the workflow from the PR actually works...? So better to just run it and see if it's using my updated workflow or not 😅

Edit: Jay! 🙌

@Mosakaas
Copy link
Author

Mosakaas commented Jan 7, 2026

@jolexxa I'm working on getting 100% code coverage now. Where and how should I export the results?

Right now I'm not using any external tools like coverlet, I'm just using Test > Analyze Code Coverage for All Tests in visual studio, which allows me to export to .xml, .coverage, .cobertura.xml and .coveragexml. In the project right now I see coverage.sh, which I assume is the preferred format generated by coverlet.

I've never used code coverage before so I'm still figuring out how it all works. If I need coverlet, I would appreciate some guidelines of how to use it and generate a coverage file.

PS:
My test files are gonna be big: I've been using Copilot to generate them and it leaves no case untested. I'm actually pleasantly surprised which how much better it has gotten at writing tests over the years! I am, of course, checking the code for each test to see if it makes sense, but so far no issues.

@jolexxa
Copy link
Member

jolexxa commented Jan 8, 2026

You can borrow a coverage.sh script from one of the other chickensoft projects which has an xunit test project, like LogicBlocks.

@Mosakaas
Copy link
Author

Finished generating and carefully reviewing all the test cases and I'm very satisfied! I will try to look into coverlet next once I have the time. If that results in a 100% coverage, then this PR should be ready to go!

@Mosakaas
Copy link
Author

@jolexxa or @wlsnmrk, if you would provide me some advice as to what to do with this snippet I would be most thankful:
#71

@Mosakaas
Copy link
Author

I still have some small improvements to make, like adding AOT support (should be easy), and here and there I've been working on XML support. The XmlSerializer is very poorly made however, so I'm still navigating a lot of mazes and gotchas for that. Either way, I'll add them after the release of this PR, cause it's already big enough.

Look this commit is not very structured but basically it gets the coverage up to 100% and that's pretty rad no?
Not so much fix as take the suggestion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants