-
Notifications
You must be signed in to change notification settings - Fork 4
Description
Hi, been loving basically all of the libraries. Had a few teething issues with the SaveFileBuilder and thought I'd go over my thoughts and see what you think and if there's a PR to be raised out of it or not. Maybe just a documentation change in the README or changes to SaveChunk.
Tight coupling
In the example code in the README we have the parent node somewhat tightly coupled to its children's save data.
var gameData = new GameData() {
MapData = chunk.GetChunkSaveData<MapData>(),
PlayerData = chunk.GetChunkSaveData<PlayerData>(),
PlayerCameraData = chunk.GetChunkSaveData<PlayerCameraData>()
};In the above example we need to know that there is a MapData chunk and a PlayerData chunk etc. We also need to know about the GameData chunk in children:
[Dependency]
public ISaveChunk<GameData> GameChunk => this.DependOn<ISaveChunk<GameData>>();Whilst I think this pattern is useful I found myself wanting to decouple this relationship.
Also: In the above example, if I wanted 10 PlayerData objects then I need a list but there isn't a built in way to register each
PlayerNode'sPlayerDatawithout finding them and looping over them in the parent.
Workaround
In my own game I've ended up creating a class like this to facilitate the kind of relationship I wanted between the nodes that serve these chunks
public partial class SaveChunkBag
{
public ISaveChunk<ChunkBagSave> SaveChunk { get; private set; }
private readonly ConcurrentDictionary<string, ISaveChunk<SaveObject>> _children = new();
public SaveChunkBag()
{
SaveChunk = new SaveChunk<ChunkBagSave>(
onSave: GetSaveData,
onLoad: LoadSaveData
);
}
public void AddChunk(string name, ISaveChunk<SaveObject> chunk)
{
_children[name] = new SaveChunk<SaveObject>(
onSave: c => chunk.GetSaveData(),
onLoad: (c, newData) => chunk.LoadSaveData(newData)
);
}
public void AddChunk<T>(string name, Func<ISaveChunk<SaveObject>, T> onSave, Action<ISaveChunk<SaveObject>, T> onLoad)
where T : SaveObject
{
_children[name] = new SaveChunk<SaveObject>(
onSave: (c) => (T)onSave(c),
onLoad: (c, newData) => onLoad(c, (T)newData)
);
}
private ChunkBagSave GetSaveData(ISaveChunk<ChunkBagSave> _chunk)
{
var save = new ChunkBagSave();
foreach (var kvp in _children)
{
if (kvp.Value == null)
{
throw new KeyNotFoundException($"Chunk '{kvp.Key}' not found in SaveChunkList.");
}
save.Children[kvp.Key] = kvp.Value.GetSaveData();
}
return save;
}
private void LoadSaveData(ISaveChunk<ChunkBagSave> chunk, ChunkBagSave newChunkBag)
{
foreach (var child in newChunkBag.Children)
{
if (!_children.ContainsKey(child.Key))
{
throw new KeyNotFoundException($"Chunk '{child.Key}' not found in SaveChunkList.");
}
_children[child.Key].LoadSaveData(child.Value);
}
}
[Meta, Id("chunk_bag")]
public partial class ChunkBagSave
{
[Save("children")]
public Dictionary<string, SaveObject> Children { get; set; } = new();
}
}
// This is the base class for all save objects in the game.
// It allows for polymorphic serialization and deserialization of game objects.
[Meta]
public partial record SaveObject
{
}Usage Examples
I think these examples show a more decoupled structure that might be useful.
[Dependency]
private SaveChunkBag ChunkBag => this.DependOn<SaveChunkBag>();
private string NpcName;
public void OnResolved()
{
ChunkBag.AddChunk("npcs." + NpcName,
onSave: (chunk) =>
{
// Do something
},
onLoad: (chunk, data) =>
{
// Do something
}
);
}[Dependency]
private SaveChunkBag ChunkBag => this.DependOn<SaveChunkBag>();
SaveChunkBag IProvide<SaveChunkBag>.Value() => this.PlayerData.ChunkBag;
public void OnResolved()
{
ChunkBag.AddChunk("Player",
onSave: (chunk) => this.PlayerData,
onLoad: (chunk, data) =>
{
this.PlayerData = data;
}
);
}