Add game file persistence functionality

This commit is contained in:
NGnius (Graham) 2020-04-28 21:56:34 -04:00
parent 83427b806e
commit 07ba6f2dc4
10 changed files with 441 additions and 0 deletions

View file

@ -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));

View file

@ -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")
}
}
}

View 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);
}
}

View file

@ -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);
}
}
}

View 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");
}
}
}

View 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]);
}
}
}
}

View 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;
}
}
}

View file

@ -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"));

View file

@ -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");
}
}

View 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>())),
};
}
}
}