Skip to content

Proposal: Decoupling SaveChunks #82

@CaseyHofland

Description

@CaseyHofland

It doesn't get better than this.

Edit: it got better than this.

SaveChunk is tightly coupled and can only contain one child per type. But it turns out that SaveChunks don't actually need to be coupled. All it needs in fact is 2 simple events and ways to invoke them: you heard that right! We can still save and load the data from one chunk onto another, but there is no reason for the SaveChunks to be coupled. I am sure that this will allow some clever people to do some very clever things with them!

// Expose events
public interface ISaveChunk<TData>
{
  event Action<TData>? OnSave;
  event Action<TData>? OnLoad;
}

// Note the new() restriction. This is intentional, as the new() restriction is also required for serialization scenarios.
// It also ensures that each OnSave callback may safely assume that the object is created from new().
// In this way, it works in cohesion with the unordered nature of events, wherein each callback assumes nothing.
public class SaveChunk<TData> : ISaveChunk<TData> where TData : new()
{
  public event Action<TData>? OnSave;
  public event Action<TData>? OnLoad;

  public TData GetSaveData()
  {
    var data = new TData();
    OnSave?.Invoke(data);
    return data;
  }

  public void LoadSaveData(TData data) => OnLoad?.Invoke(data);
}

Additionally, because you're settings relevant data from the class you're setting from, you can structure your data representation however you want. Right now GameData.Players is filled via index, but it could just as easily be done via string or Guid by using a Dictionary<,> Players.

public record GameData
{
  public List<PlayerData> Players;
}

public record PlayerData
{
  public string Name;
  public int Level;
}

[Meta(typeof(IAutoNode))]
public partial class Game : Node3D
{
  public SaveFile SaveFile = SaveFile.CreateGZipJsonFile(SaveFilePath, JsonOptions);
  public SaveChunk<GameData> GameChunk = new();

  // Provide the game chunk to all descendant nodes.
  ISaveChunk<GameData> IProvide<ISaveChunk<GameData>>.Value() => GameChunk;

  void OnSave()
  {
    var gameData = GameChunk.GetSaveData();
    SaveFile.Save(gameData);
  }

  void OnLoad()
  {
    var gameData = SaveFile.Load<GameData>();
    GameChunk.LoadSaveData(gameData);
  }
}

[Meta(typeof(IAutoNode))]
public partial class Player : CharacterBody3D
{
  [Dependency]
  public ISaveChunk<GameData> GameChunk => this.DependOn<ISaveChunk<GameData>>();

  public SaveChunk<GameData> PlayerChunk = new();

  public int index;

  public string Name;
  public int Age;

  public void Setup()
  {
    PlayerChunk.OnSave += playerData =>
    {
      playerData.Name = Name;
      playerData.Level = Age;
    };
    PlayerChunk.OnLoad += playerData =>
    {
      Name = playerData.Name;
      Age = playerData.Level;
    };
  }

  public void OnResolved()
  {
    GameChunk.OnSave += gameData => gameData.Players[index] = PlayerChunk.GetSaveData();
    GameChunk.OnLoad += gameData => PlayerChunk.LoadSaveData(gameData.Players[index]);
  }
}

Note how we didn't need to define an OnSave or OnLoad inside of the Game class. It is added inside the Player class, only once there is something to save or load.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions