Add game file persistence functionality
This commit is contained in:
parent
83427b806e
commit
07ba6f2dc4
10 changed files with 441 additions and 0 deletions
|
@ -48,6 +48,7 @@ namespace GamecraftModdingAPI
|
|||
// init utility
|
||||
Logging.MetaDebugLog($"Initializing Utility");
|
||||
Utility.GameState.Init();
|
||||
Utility.VersionTracking.Init();
|
||||
// create default event emitters
|
||||
Logging.MetaDebugLog($"Initializing Events");
|
||||
EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.ApplicationInitialized, "GamecraftModdingAPIApplicationInitializedEventEmitter", false));
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
|
||||
using RobocraftX.Common;
|
||||
using Svelto.DataStructures;
|
||||
using Svelto.ECS;
|
||||
using Svelto.ECS.Serialization;
|
||||
|
||||
using Harmony;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.Persistence
|
||||
{
|
||||
[HarmonyPatch]
|
||||
class DeserializeFromDiskEntitiesEnginePatch
|
||||
{
|
||||
internal static EntitiesDB entitiesDB = null;
|
||||
|
||||
private static readonly byte[] frameStart = Encoding.UTF8.GetBytes("\0\0\0GamecraftModdingAPI\0\0\0");
|
||||
|
||||
public static void Prefix(ref ISerializationData ____serializationData, ref FasterList<byte> ____bytesStream, ref IEntitySerialization ____entitySerializer, bool ____spawnBlocksOnly)
|
||||
{
|
||||
if (____spawnBlocksOnly) return; // only run after second deserialization call (when all vanilla stuff is already deserialized)
|
||||
uint originalPos = ____serializationData.dataPos;
|
||||
Logging.MetaDebugLog($"dataPos: {originalPos}");
|
||||
BinaryBufferReader bbr = new BinaryBufferReader(____bytesStream.ToArrayFast(out uint count), ____serializationData.dataPos);
|
||||
byte[] frameBuffer = new byte[frameStart.Length];
|
||||
Logging.MetaDebugLog($"serial data count: {____serializationData.data.count} capacity: {____serializationData.data.capacity}");
|
||||
int i = 0;
|
||||
// match frame start
|
||||
while (frameBuffer != frameStart && bbr.Position < count-frameStart.Length)
|
||||
{
|
||||
i = 0;
|
||||
frameBuffer[0] = bbr.ReadByte();
|
||||
while (frameBuffer[i] == frameStart[i] && bbr.Position < count - frameStart.Length + i)
|
||||
{
|
||||
i++;
|
||||
if (i == frameStart.Length) break;
|
||||
frameBuffer[i] = bbr.ReadByte();
|
||||
}
|
||||
if (i == frameStart.Length) break;
|
||||
}
|
||||
// abort if at end of file
|
||||
if (bbr.Position >= count - frameStart.Length)
|
||||
{
|
||||
Logging.MetaLog("Skipping deserialization (no frame found)");
|
||||
return;
|
||||
}
|
||||
//____serializationData.dataPos = bbr.Position;
|
||||
Logging.MetaDebugLog($"dataPos (after frame): {bbr.Position}");
|
||||
uint customComponentsCount = bbr.ReadUint();
|
||||
for (uint c = 0; c < customComponentsCount; c++)
|
||||
{
|
||||
// determine component from info
|
||||
uint nameLength = bbr.ReadUint();
|
||||
byte[] nameBytes = new byte[nameLength];
|
||||
bbr.ReadBytes(nameBytes, nameLength);
|
||||
string name = Encoding.UTF8.GetString(nameBytes);
|
||||
Logging.MetaDebugLog($"Component name: {name} (len: {nameLength})");
|
||||
uint componentEnd = bbr.ReadUint();
|
||||
____serializationData.dataPos = bbr.Position;
|
||||
if (SerializerManager.ExistsSerializer(name))
|
||||
{
|
||||
// deserialize component
|
||||
IEntitySerializer serial = SerializerManager.GetSerializer(name);
|
||||
if (!serial.Deserialize(ref ____serializationData, ____entitySerializer))
|
||||
{
|
||||
Logging.MetaDebugLog("Component deserialization failed!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.MetaDebugLog("Skipping component deserialization: not found!");
|
||||
}
|
||||
bbr = new BinaryBufferReader(____bytesStream.ToArrayFast(out count), componentEnd);
|
||||
}
|
||||
____serializationData.dataPos = originalPos; // change back to original end point (just in case)
|
||||
Logging.MetaDebugLog("Deserialization complete");
|
||||
}
|
||||
|
||||
public static MethodBase TargetMethod()
|
||||
{
|
||||
return AccessTools.Method("RobocraftX.SaveAndLoad.DeserializeFromDiskEntitiesEngine:LoadingFinished");//AccessTools.TypeByName("RobocraftX.SaveAndLoad.DeserializeFromDiskEntities")
|
||||
}
|
||||
}
|
||||
}
|
18
GamecraftModdingAPI/Persistence/IEntitySerializer.cs
Normal file
18
GamecraftModdingAPI/Persistence/IEntitySerializer.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
using Svelto.ECS;
|
||||
using Svelto.ECS.Serialization;
|
||||
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.Persistence
|
||||
{
|
||||
public interface IEntitySerializer : IDeserializationFactory, IQueryingEntitiesEngine
|
||||
{
|
||||
IEntityFactory EntityFactory { set; }
|
||||
|
||||
bool Serialize(ref ISerializationData serializationData, EntitiesDB entitiesDB, IEntitySerialization entitySerializer);
|
||||
|
||||
bool Deserialize(ref ISerializationData serializationData, IEntitySerialization entitySerializer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
using RobocraftX.SaveAndLoad;
|
||||
using Svelto.ECS;
|
||||
|
||||
using Harmony;
|
||||
|
||||
namespace GamecraftModdingAPI.Persistence
|
||||
{
|
||||
[HarmonyPatch(typeof(SaveAndLoadCompositionRoot), "Compose")]
|
||||
class SaveAndLoadCompositionRootPatch
|
||||
{
|
||||
public static void Prefix(EnginesRoot enginesRoot)
|
||||
{
|
||||
SerializerManager.RegisterSerializers(enginesRoot);
|
||||
}
|
||||
}
|
||||
}
|
81
GamecraftModdingAPI/Persistence/SaveGameEnginePatch.cs
Normal file
81
GamecraftModdingAPI/Persistence/SaveGameEnginePatch.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
|
||||
using RobocraftX.Common;
|
||||
using RobocraftX.SaveAndLoad;
|
||||
using Svelto.DataStructures;
|
||||
using Svelto.ECS;
|
||||
using Svelto.ECS.Serialization;
|
||||
|
||||
using GamecraftModdingAPI.Utility;
|
||||
using Harmony;
|
||||
|
||||
namespace GamecraftModdingAPI.Persistence
|
||||
{
|
||||
[HarmonyPatch]
|
||||
class SaveGameEnginePatch
|
||||
{
|
||||
private static readonly byte[] frameStart = Encoding.UTF8.GetBytes("\0\0\0GamecraftModdingAPI\0\0\0");
|
||||
|
||||
public static void Postfix(ref ISerializationData serializationData, EntitiesDB entitiesDB, IEntitySerialization entitySerializer)
|
||||
{
|
||||
Logging.MetaDebugLog("Running Postfix on SerializeGameToBuffer: serializing custom components...");
|
||||
if (SerializerManager.GetSerializersCount() == 0)
|
||||
{
|
||||
Logging.MetaDebugLog("Skipping component serialization: no serializers registered!");
|
||||
return;
|
||||
}
|
||||
serializationData.data.ExpandBy((uint)frameStart.Length);
|
||||
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out uint buffLen), serializationData.dataPos);
|
||||
uint originalPos = serializationData.dataPos;
|
||||
Logging.MetaDebugLog($"dataPos: {originalPos}");
|
||||
// Add frame start so it's easier to find GamecraftModdingAPI-serialized components
|
||||
for (int i = 0; i < frameStart.Length; i++)
|
||||
{
|
||||
bbw.Write(frameStart[i]);
|
||||
}
|
||||
Logging.MetaDebugLog($"dataPos (after frame start): {bbw.Position}");
|
||||
serializationData.data.ExpandBy(4u);
|
||||
bbw.Write((uint)SerializerManager.GetSerializersCount());
|
||||
string[] serializerKeys = SerializerManager.GetSerializerNames();
|
||||
for (uint c = 0; c < serializerKeys.Length; c++)
|
||||
{
|
||||
Logging.MetaDebugLog($"dataPos (loop start): {bbw.Position}");
|
||||
// write component info
|
||||
serializationData.data.ExpandBy(4u + (uint)serializerKeys[c].Length);
|
||||
bbw.Write((uint)serializerKeys[c].Length);
|
||||
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}");
|
||||
byte[] nameBytes = Encoding.UTF8.GetBytes(serializerKeys[c]);
|
||||
for (int i = 0; i < nameBytes.Length; i++)
|
||||
{
|
||||
bbw.Write(nameBytes[i]);
|
||||
}
|
||||
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}");
|
||||
serializationData.data.ExpandBy(4u);
|
||||
serializationData.dataPos = bbw.Position + 4u;
|
||||
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}");
|
||||
Logging.MetaDebugLog($"dataPos (appears to be): {serializationData.dataPos}");
|
||||
// serialize component
|
||||
IEntitySerializer serializer = SerializerManager.GetSerializer(serializerKeys[c]);
|
||||
if (!serializer.Serialize(ref serializationData, entitiesDB, entitySerializer))
|
||||
{
|
||||
Logging.MetaDebugLog("Component serialization failed!");
|
||||
}
|
||||
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}");
|
||||
bbw.Write((uint)serializationData.dataPos);
|
||||
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}");
|
||||
bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out buffLen), serializationData.dataPos);
|
||||
Logging.MetaDebugLog($"dataPos (loop end): {bbw.Position}");
|
||||
}
|
||||
serializationData.data.Trim();
|
||||
Logging.MetaDebugLog($"dataPos (end): {bbw.Position}");
|
||||
Logging.MetaDebugLog("Serialization complete");
|
||||
}
|
||||
|
||||
public static MethodBase TargetMethod()
|
||||
{
|
||||
return typeof(SaveGameEngine).GetMethod("SerializeGameToBuffer");
|
||||
}
|
||||
}
|
||||
}
|
72
GamecraftModdingAPI/Persistence/SerializerManager.cs
Normal file
72
GamecraftModdingAPI/Persistence/SerializerManager.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Svelto.ECS;
|
||||
using Svelto.ECS.Serialization;
|
||||
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.Persistence
|
||||
{
|
||||
public static class SerializerManager
|
||||
{
|
||||
private static Dictionary<string, IEntitySerializer> _serializers = new Dictionary<string, IEntitySerializer>();
|
||||
|
||||
private static Dictionary<string, Action<IEntitySerialization>> _registrations = new Dictionary<string, Action<IEntitySerialization>>();
|
||||
|
||||
private static EnginesRoot _lastEnginesRoot;
|
||||
|
||||
public static void AddSerializer<T>(IEntitySerializer serializer) where T : ISerializableEntityDescriptor, new()
|
||||
{
|
||||
string name = typeof(T).FullName;
|
||||
_serializers[name] = serializer;
|
||||
_registrations[name] = (IEntitySerialization ies) => { ies.RegisterSerializationFactory<T>(serializer); };
|
||||
if (_lastEnginesRoot != null)
|
||||
{
|
||||
serializer.EntityFactory = _lastEnginesRoot.GenerateEntityFactory();
|
||||
_registrations[name].Invoke(_lastEnginesRoot.GenerateEntitySerializer());
|
||||
_lastEnginesRoot.AddEngine(serializer);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ExistsSerializer(string name)
|
||||
{
|
||||
return _serializers.ContainsKey(name);
|
||||
}
|
||||
|
||||
public static bool ExistsSerializer<T>(IEntitySerializer serializer) where T : ISerializableEntityDescriptor, new()
|
||||
{
|
||||
return ExistsSerializer(typeof(T).FullName);
|
||||
}
|
||||
|
||||
public static IEntitySerializer GetSerializer(string name)
|
||||
{
|
||||
return _serializers[name];
|
||||
}
|
||||
|
||||
public static string[] GetSerializerNames()
|
||||
{
|
||||
return _serializers.Keys.ToArray();
|
||||
}
|
||||
|
||||
public static int GetSerializersCount()
|
||||
{
|
||||
return _serializers.Count;
|
||||
}
|
||||
|
||||
public static void RegisterSerializers(EnginesRoot enginesRoot)
|
||||
{
|
||||
_lastEnginesRoot = enginesRoot;
|
||||
IEntityFactory factory = enginesRoot.GenerateEntityFactory();
|
||||
IEntitySerialization ies = enginesRoot.GenerateEntitySerializer();
|
||||
foreach (string key in _serializers.Keys)
|
||||
{
|
||||
Logging.MetaDebugLog($"Registering IEntitySerializer for {key}");
|
||||
_serializers[key].EntityFactory = factory;
|
||||
_registrations[key].Invoke(ies);
|
||||
enginesRoot.AddEngine(_serializers[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
63
GamecraftModdingAPI/Persistence/SimpleEntitySerializer.cs
Normal file
63
GamecraftModdingAPI/Persistence/SimpleEntitySerializer.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
|
||||
using Svelto.ECS;
|
||||
using Svelto.ECS.Serialization;
|
||||
|
||||
using RobocraftX.Common;
|
||||
|
||||
namespace GamecraftModdingAPI.Persistence
|
||||
{
|
||||
public class SimpleEntitySerializer<Descriptor> : IEntitySerializer where Descriptor : ISerializableEntityDescriptor, new()
|
||||
{
|
||||
public delegate EGID[] GetEntitiesToSerialize(EntitiesDB entitiesDB);
|
||||
|
||||
private GetEntitiesToSerialize getEntitiesToSerialize;
|
||||
|
||||
protected int serializationType;
|
||||
|
||||
public IEntityFactory EntityFactory { set; protected get; }
|
||||
|
||||
public EntitiesDB entitiesDB { set; protected get; }
|
||||
|
||||
public EntityComponentInitializer BuildDeserializedEntity(EGID egid, ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor, int serializationType, IEntitySerialization entitySerialization)
|
||||
{
|
||||
EntityComponentInitializer esi = EntityFactory.BuildEntity<Descriptor>(egid);
|
||||
entitySerialization.DeserializeEntityComponents(serializationData, entityDescriptor, ref esi, serializationType);
|
||||
return esi;
|
||||
}
|
||||
|
||||
public bool Deserialize(ref ISerializationData serializationData, IEntitySerialization entitySerializer)
|
||||
{
|
||||
BinaryBufferReader bbr = new BinaryBufferReader(serializationData.data.ToArrayFast(out uint count), serializationData.dataPos);
|
||||
uint entityCount = bbr.ReadUint();
|
||||
serializationData.dataPos = bbr.Position;
|
||||
for (uint i = 0; i < entityCount; i++)
|
||||
{
|
||||
entitySerializer.DeserializeEntity(serializationData, serializationType);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Ready() { }
|
||||
|
||||
public bool Serialize(ref ISerializationData serializationData, EntitiesDB entitiesDB, IEntitySerialization entitySerializer)
|
||||
{
|
||||
serializationData.data.ExpandBy(4u);
|
||||
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out uint count), serializationData.dataPos);
|
||||
EGID[] toSerialize = getEntitiesToSerialize(entitiesDB);
|
||||
bbw.Write((uint)toSerialize.Length);
|
||||
serializationData.dataPos = bbw.Position;
|
||||
for (uint i = 0; i < toSerialize.Length; i++)
|
||||
{
|
||||
entitySerializer.SerializeEntity(toSerialize[i], serializationData, serializationType);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public SimpleEntitySerializer(GetEntitiesToSerialize getEntitiesToSerialize)
|
||||
{
|
||||
this.getEntitiesToSerialize = getEntitiesToSerialize;
|
||||
serializationType = (int)SerializationType.Storage;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,6 +44,7 @@ namespace GamecraftModdingAPI.Tests
|
|||
FileLog.Reset();
|
||||
HarmonyInstance.DEBUG = true;
|
||||
GamecraftModdingAPI.Main.Init();
|
||||
Logging.MetaDebugLog($"Version group id {(uint)ApiExclusiveGroups.versionGroup}");
|
||||
// in case Steam is not installed/running
|
||||
// this will crash the game slightly later during startup
|
||||
//SteamInitPatch.ForcePassSteamCheck = true;
|
||||
|
@ -55,6 +56,8 @@ namespace GamecraftModdingAPI.Tests
|
|||
Logging.MetaDebugLog("Audio Mixers: "+string.Join(",", AudioTools.GetMixers()));
|
||||
//AudioTools.SetVolume(0.0f, "Music"); // The game now sets this from settings again after this is called :(
|
||||
|
||||
Utility.VersionTracking.Enable();
|
||||
|
||||
// debug/test handlers
|
||||
EventManager.AddEventHandler(new SimpleEventHandlerEngine(() => { Logging.Log("App Inited event!"); }, () => { },
|
||||
EventType.ApplicationInitialized, "appinit API debug"));
|
||||
|
|
|
@ -13,5 +13,7 @@ namespace GamecraftModdingAPI.Utility
|
|||
public static readonly ExclusiveGroup eventsExclusiveGroup = new ExclusiveGroup();
|
||||
|
||||
public static uint eventID;
|
||||
|
||||
public static readonly ExclusiveGroup versionGroup = new ExclusiveGroup("GamecraftModdingAPIVersion");
|
||||
}
|
||||
}
|
||||
|
|
96
GamecraftModdingAPI/Utility/VersionTracking.cs
Normal file
96
GamecraftModdingAPI/Utility/VersionTracking.cs
Normal file
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
using RobocraftX.Common;
|
||||
using Svelto.ECS;
|
||||
using Svelto.ECS.Serialization;
|
||||
|
||||
using GamecraftModdingAPI.Persistence;
|
||||
using GamecraftModdingAPI.Events;
|
||||
|
||||
namespace GamecraftModdingAPI.Utility
|
||||
{
|
||||
public static class VersionTracking
|
||||
{
|
||||
private static readonly VersionTrackingEngine versionEngine = new VersionTrackingEngine();
|
||||
|
||||
private static bool isEnabled = false;
|
||||
|
||||
public static uint GetVersion()
|
||||
{
|
||||
if (!isEnabled) return 0u;
|
||||
return versionEngine.GetGameVersion();
|
||||
}
|
||||
|
||||
public static void Enable()
|
||||
{
|
||||
EventManager.AddEventEmitter(versionEngine);
|
||||
isEnabled = true;
|
||||
}
|
||||
|
||||
public static void Disable()
|
||||
{
|
||||
EventManager.AddEventEmitter(versionEngine);
|
||||
isEnabled = false;
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
SerializerManager.AddSerializer<ModVersionDescriptor>(new SimpleEntitySerializer<ModVersionDescriptor>(
|
||||
(_) => { return new EGID[1] { new EGID(0u, ApiExclusiveGroups.versionGroup) }; }
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
internal class VersionTrackingEngine : IEventEmitterEngine
|
||||
{
|
||||
public string Name { get; } = "GamecraftModdingAPIVersionTrackingGameEngine";
|
||||
|
||||
public EntitiesDB entitiesDB { set; private get; }
|
||||
|
||||
public int type => -1;
|
||||
|
||||
public bool isRemovable => false;
|
||||
|
||||
public IEntityFactory Factory { set; private get; }
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public void Ready()
|
||||
{
|
||||
EGID egid = new EGID(0u, ApiExclusiveGroups.versionGroup);
|
||||
if (!entitiesDB.Exists<ModVersionStruct>(egid))
|
||||
{
|
||||
Version currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
int v = (currentVersion.Major * 1000) + (currentVersion.Minor);
|
||||
Factory.BuildEntity<ModVersionDescriptor>(egid).Init<ModVersionStruct>(new ModVersionStruct
|
||||
{
|
||||
version = (uint)v
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetGameVersion()
|
||||
{
|
||||
return entitiesDB.QueryUniqueEntity<ModVersionStruct>(ApiExclusiveGroups.versionGroup).version;
|
||||
}
|
||||
|
||||
public void Emit() { }
|
||||
}
|
||||
|
||||
public struct ModVersionStruct : IEntityComponent
|
||||
{
|
||||
public uint version;
|
||||
}
|
||||
|
||||
public class ModVersionDescriptor: SerializableEntityDescriptor<ModVersionDescriptor._ModVersionDescriptor>
|
||||
{
|
||||
[HashName("GamecraftModdingAPIVersionV0")]
|
||||
public class _ModVersionDescriptor : IEntityDescriptor
|
||||
{
|
||||
public IComponentBuilder[] componentsToBuild { get; } = new IComponentBuilder[]{
|
||||
new SerializableComponentBuilder<SerializationType, ModVersionStruct>(((int)SerializationType.Network, new DefaultSerializer<ModVersionStruct>()), ((int)SerializationType.Storage, new DefaultSerializer<ModVersionStruct>())),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue