Browse Source

Add script serialization support

tags/v0.2
NGnius (Graham) 4 years ago
parent
commit
9bc04099a6
14 changed files with 647 additions and 35 deletions
  1. +204
    -0
      GamecraftScripting/Commands/ExecuteCommandEngine.cs
  2. +36
    -0
      GamecraftScripting/Commands/PythonRunnerCommandEngine.cs
  3. +36
    -0
      GamecraftScripting/Commands/SerializationCommandEngine.cs
  4. +0
    -33
      GamecraftScripting/GamecraftScripting.csproj
  5. +12
    -2
      GamecraftScripting/Plugin.cs
  6. +74
    -0
      GamecraftScripting/Serialization/DeserializeFromDiskEntitiesEnginePatch.cs
  7. +17
    -0
      GamecraftScripting/Serialization/SaveAndLoadCompositionRootPatch.cs
  8. +57
    -0
      GamecraftScripting/Serialization/SaveGameEnginePatch.cs
  9. +24
    -0
      GamecraftScripting/Serialization/ScriptBuilder.cs
  10. +25
    -0
      GamecraftScripting/Serialization/ScriptDeserializationFactory.cs
  11. +28
    -0
      GamecraftScripting/Serialization/ScriptEntityDescriptor.cs
  12. +61
    -0
      GamecraftScripting/Serialization/ScriptSerializer.cs
  13. +13
    -0
      GamecraftScripting/Serialization/ScriptStruct.cs
  14. +60
    -0
      GamecraftScripting/Serialization/SerializationDescriptorMapPatch.cs

+ 204
- 0
GamecraftScripting/Commands/ExecuteCommandEngine.cs View File

@@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;

using GamecraftModdingAPI.Commands;
using GamecraftModdingAPI.Utility;
using Svelto.ECS;

using Microsoft.Scripting.Hosting;

using GamecraftScripting.Environment;
using GamecraftScripting.Serialization;

namespace GamecraftScripting.Commands
{
public class ExecuteCommandEngine : ICustomCommandEngine
{
public const string URI_IMMEDIATE = "immediate://";
public const string URI_HTTP = "http://";
public const string URI_HTTPS = "https://";
public const string URI_INTERNAL = "internal://";
public const string URI_BUILTIN = "exmods://";
public const string URI_FILE = "file://";
public const string URI_ANSWER = "42://";

public string Description { get; } = "Execute some Python, intelligently guessing how to execute it";

public string Name { get; } = "ExecutePython";

public EntitiesDB entitiesDB { set; private get; }

public void Dispose()
{
CommandRegistrationHelper.Unregister(Name);
}

public void Ready()
{
// TODO command registration
CommandRegistrationHelper.Register<string>(Name, ExecutePythonCommand, Description);
// create insecure Python namespace
ScriptEngine pyEngine = PythonEnvironment.CreateEngine();
ScriptScope pyScope = PythonEnvironment.CreateScope(pyEngine);
PythonEnvironment.CurrentEngine = pyEngine;
PythonEnvironment.CurrentScope = pyScope;
}

private void ExecutePythonCommand(string name)
{
string uriName = urifyName(name.Trim());
if (uriName == null)
{
Logging.CommandLogError("Unable to guess script type. Please add a scheme to the start of the command argument to avoid this. \nValid schemes include: \n" + URI_IMMEDIATE.Replace('/', '\\') + " (for Python code) \n" + URI_FILE.Replace('/', '\\') + " (for Python file) ");
return;
}
string scriptContents = getScript(uriName);
if (scriptContents == null)
{
Logging.CommandLogError("Invalid script or URI scheme. Please replace the scheme at the start of the command argument. Valid schemes include: \n" + URI_IMMEDIATE.Replace('/', '\\') + "\\\\ (for Python code) \n" + URI_FILE.Replace('/', '\\') + "\\\\ (for Python file) ");
return;
}
Execute(uriName, scriptContents);
}

private string urifyName(string name)
{
if (name.Contains("://")) return name; // already is a uri, hopefully a valid uri
if (name.Contains(":\\\\")) return name.Replace(":\\\\", "://"); // :\\ is easier to input in CLI
if (!name.EndsWith(".py", StringComparison.InvariantCultureIgnoreCase))
return URI_IMMEDIATE + name; // probably not a script file, hopefully it's some code
name = name.TrimEnd('\\', '/'); // I still hate that Windows filenames use the escape character as a separator
// search scripts included in game save
ScriptStruct[] integratedScripts = entitiesDB.QueryEntities<ScriptStruct>(ScriptBuilder.ScriptGroup).ToFastAccess(out uint integratedScriptsCount);
for (int i = 0; i < integratedScriptsCount; i++)
{
if (integratedScripts[i].name == name)
{
// matching script found in game save
return URI_INTERNAL + name;
}
}
// search scripts on disk
try
{
FileInfo file = new FileInfo(name);
if (file.Exists)
{
return URI_FILE + name;
}
}
catch (Exception e) when (
e is System.Security.SecurityException
|| e is ArgumentException
|| e is UnauthorizedAccessException
|| e is PathTooLongException
|| e is NotSupportedException)
{ }
return null;
}

private string getScheme(string uriName)
{
string[] splitName = uriName.Split(new string[] { @"://" }, StringSplitOptions.None);
if (splitName.Length == 2)
{
return splitName[0] + "://";
}
return null;
}

private string getPath(string uriName)
{
string[] splitName = uriName.Split(new string[] { @"://" }, StringSplitOptions.None);
if (splitName.Length == 2)
{
return splitName[1];
}
return null;
}

private string getScript(string uriName)
{
string uri_scheme = getScheme(uriName);
if (uri_scheme == null) return null;
string uri_path = getPath(uriName);
if (uri_path == null) return null;
switch (uri_scheme)
{
case URI_IMMEDIATE:
return uri_path;
case URI_HTTP: // download from web
case URI_HTTPS:
#if DEBUG
Logging.CommandLogWarning("Executing code from the Internet is dangerous! \nWeb scripts are only available in DEVELOPMENT versions of GamecraftScripting.");
try
{
WebRequest req = WebRequest.Create(uriName);
req.Method = "GET";
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
byte[] body = new byte[int.Parse(resp.GetResponseHeader("Content-Length"))];
resp.GetResponseStream().Read(body, 0, body.Length);
return Encoding.ASCII.GetString(body);
}
catch (Exception)
{
return null;
}
#else
Logging.CommandLogError("Executing code from the Internet is dangerous! \nWeb scripts are only available in DEVELOPMENT versions of GamecraftScripting.");
return null;
#endif
case URI_INTERNAL: // found in game file (already deserialized to memory)
ScriptStruct[] internalScripts = entitiesDB.QueryEntities<ScriptStruct>(ScriptBuilder.ScriptGroup).ToFastAccess(out uint scriptCount);
for (int i = 0; i < scriptCount; i++)
{
if (internalScripts[i].name == uri_path)
{
return internalScripts[i].script;
}
}
return null;
case URI_BUILTIN:
// TODO: for future implementation
return null;
case URI_FILE:
try
{
return File.ReadAllText(uri_path);
}
catch (Exception)
{
return null;
}
case URI_ANSWER:
return "print 42";
default:
return null;
}
}

private void Execute(string uriName, string script, bool logError = true)
{
ScriptSource src = PythonEnvironment.CurrentEngine.CreateScriptSourceFromString(script);
try
{
src.Execute(PythonEnvironment.CurrentScope);
}
catch (Exception e)
{
if (logError)
{
Logging.CommandLogError($"Python error in '{uriName}': \n{e.Message}");
}
else
{
throw e;
}

}
}
}
}

+ 36
- 0
GamecraftScripting/Commands/PythonRunnerCommandEngine.cs View File

@@ -9,6 +9,7 @@ using Svelto.ECS;
using Microsoft.Scripting.Hosting;

using GamecraftScripting.Environment;
using GamecraftScripting.Serialization;

namespace GamecraftScripting.Commands
{
@@ -24,12 +25,16 @@ namespace GamecraftScripting.Commands
{
CommandRegistrationHelper.Unregister("RunPythonScript");
CommandRegistrationHelper.Unregister("RunPython");
CommandRegistrationHelper.Unregister("RunGamePython");
CommandRegistrationHelper.Unregister("ListGameScripts");
}

public void Ready()
{
CommandRegistrationHelper.Register<string>("RunPythonScript", RunPythonScript, "Run a python script stored on your computer");
CommandRegistrationHelper.Register<string>("RunPython", RunPythonString, "Run a python argument");
CommandRegistrationHelper.Register<string>("RunGamePython", RunIntegratedScript, "Run a python script stored in the game file");
CommandRegistrationHelper.Register("ListGameScripts", ListIntegratedScripts, "List python scripts stored in the game file");
// create insecure Python namespace
ScriptEngine pyEngine = PythonEnvironment.CreateEngine();
ScriptScope pyScope = PythonEnvironment.CreateScope(pyEngine);
@@ -38,6 +43,37 @@ namespace GamecraftScripting.Commands
//Logging.MetaLog(string.Join(", ", pyEngine.GetSearchPaths().ToArray()));
}

private void ListIntegratedScripts()
{
string result = "";
ScriptStruct[] scripts = entitiesDB.QueryEntities<ScriptStruct>(ScriptBuilder.ScriptGroup).ToFastAccess(out uint count);
for (uint i = 0u; i < count; i++)
{
result += scripts[i].name + " \n";
}
Logging.CommandLog($"Found {count} integrated script(s) \n{result}");
}

private void RunIntegratedScript(string scriptName)
{
if (scriptName == null) return;
if (scriptName.StartsWith("exmods://", StringComparison.InvariantCultureIgnoreCase))
{
// TODO: Lookup for built-in scripts
return;
}
ScriptStruct[] scripts = entitiesDB.QueryEntities<ScriptStruct>(ScriptBuilder.ScriptGroup).ToFastAccess(out uint count);
for (uint i = 0u; i < count; i++)
{
if (scripts[i].name == scriptName)
{
ExecuteScript(scripts[i].script);
return;
}
}
Logging.CommandLogError("Script not found");
}

private void RunPythonScript(string scriptPath)
{
string script = File.ReadAllText(scriptPath);


+ 36
- 0
GamecraftScripting/Commands/SerializationCommandEngine.cs View File

@@ -0,0 +1,36 @@
using System;
using System.IO;
using Svelto.ECS;
using GamecraftModdingAPI.Commands;
using GamecraftModdingAPI.Utility;

namespace GamecraftScripting.Commands
{
public class SerializationCommandEngine : ICustomCommandEngine
{
public string Description => "Save a script into a Gamecraft game file";

public string Name => "IntegrateScript";

public EntitiesDB entitiesDB { set; private get; }

internal static IEntityFactory entityFactory = null;

public void Dispose()
{
CommandRegistrationHelper.Unregister(Name);
}

public void Ready()
{
CommandRegistrationHelper.Register<string>(Name, persistScript, Description);
}

private void persistScript(string filepath)
{
if (entityFactory == null) return;
EGID scriptId = Serialization.ScriptBuilder.BuildScriptEntity(filepath, File.ReadAllText(filepath), entityFactory);
Logging.MetaLog($"Created persistent script with id {scriptId.entityID}");
}
}
}

+ 0
- 33
GamecraftScripting/GamecraftScripting.csproj View File

@@ -90,9 +90,6 @@
<Reference Include="Havok.Physics.Hybrid">
<HintPath>..\ref\Gamecraft_Data\Managed\Havok.Physics.Hybrid.dll</HintPath>
</Reference>
<Reference Include="HdgRemoteDebugRuntime">
<HintPath>..\ref\Gamecraft_Data\Managed\HdgRemoteDebugRuntime.dll</HintPath>
</Reference>
<Reference Include="IllusionInjector">
<HintPath>..\ref\Gamecraft_Data\Managed\IllusionInjector.dll</HintPath>
</Reference>
@@ -123,9 +120,6 @@
<Reference Include="Rewired_Windows">
<HintPath>..\ref\Gamecraft_Data\Managed\Rewired_Windows.dll</HintPath>
</Reference>
<Reference Include="Robocraft.MainGame.AutoEnterSimulation">
<HintPath>..\ref\Gamecraft_Data\Managed\Robocraft.MainGame.AutoEnterSimulation.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.AccountPreferences">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.AccountPreferences.dll</HintPath>
</Reference>
@@ -150,15 +144,9 @@
<Reference Include="RobocraftX.Crosshair">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Crosshair.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.EntityStreamUtility">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.EntityStreamUtility.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.FrontEnd">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.FrontEnd.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GameSignalHandling">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GameSignalHandling.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.DebugDisplay">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.DebugDisplay.dll</HintPath>
</Reference>
@@ -171,18 +159,12 @@
<Reference Include="RobocraftX.GUI.ScaleGhost">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.ScaleGhost.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.SignalLabel">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.SignalLabel.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUIs.WorkshopPrefabs">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUIs.WorkshopPrefabs.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Input">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Input.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Inventory">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Inventory.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.MachineEditor">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.MachineEditor.dll</HintPath>
</Reference>
@@ -216,9 +198,6 @@
<Reference Include="RobocraftX.Player">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Player.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Priority">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Priority.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Rendering">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Rendering.dll</HintPath>
</Reference>
@@ -288,12 +267,6 @@
<Reference Include="Unity.Entities.Hybrid">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Entities.Hybrid.dll</HintPath>
</Reference>
<Reference Include="Unity.Entities.Properties">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Entities.Properties.dll</HintPath>
</Reference>
<Reference Include="Unity.Entities.StaticTypeRegistry">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Entities.StaticTypeRegistry.dll</HintPath>
</Reference>
<Reference Include="Unity.Jobs">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Jobs.dll</HintPath>
</Reference>
@@ -324,9 +297,6 @@
<Reference Include="Unity.RenderPipelines.Core.ShaderLibrary">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.RenderPipelines.Core.ShaderLibrary.dll</HintPath>
</Reference>
<Reference Include="Unity.RenderPipelines.Lightweight.Runtime">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.RenderPipelines.Lightweight.Runtime.dll</HintPath>
</Reference>
<Reference Include="Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.dll</HintPath>
</Reference>
@@ -396,9 +366,6 @@
<Reference Include="UnityEngine.DSPGraphModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.DSPGraphModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.FileSystemHttpModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.FileSystemHttpModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.GameCenterModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.GameCenterModule.dll</HintPath>
</Reference>


+ 12
- 2
GamecraftScripting/Plugin.cs View File

@@ -9,6 +9,7 @@ using IllusionPlugin;
using UnityEngine;
using GamecraftModdingAPI;
using GamecraftModdingAPI.Commands;
using Harmony;

namespace GamecraftScripting
{
@@ -19,24 +20,33 @@ namespace GamecraftScripting
public string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;

public string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();

private HarmonyInstance harmony = null;

public void OnApplicationQuit()
{
var currentAssembly = Assembly.GetExecutingAssembly();
harmony.UnpatchAll(currentAssembly.GetName().Name);
harmony = null;
Main.Shutdown();
}

public void OnApplicationStart()
{
Main.Init();
var currentAssembly = Assembly.GetExecutingAssembly();
harmony = HarmonyInstance.Create(currentAssembly.GetName().Name);
harmony.PatchAll(currentAssembly);
// register development commands
#if DEBUG
//CommandManager.AddCommand(new SimpleCustomCommandEngine(() => { Application.Quit(); }, "Exit", "Exit the game immediately"));
#endif
// debugging commands
CommandManager.AddCommand(new Commands.DebugCommandEngine());
// functional commands
CommandManager.AddCommand(new Commands.PythonRunnerCommandEngine());
CommandManager.AddCommand(new Commands.ExecuteCommandEngine());
CommandManager.AddCommand(new Commands.SerializationCommandEngine());
}

public void OnFixedUpdate() { }


+ 74
- 0
GamecraftScripting/Serialization/DeserializeFromDiskEntitiesEnginePatch.cs View File

@@ -0,0 +1,74 @@
using System;
using System.Text;
using System.Reflection;
using Harmony;
using RobocraftX.Common;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.Serialization;

namespace GamecraftScripting.Serialization
{
[HarmonyPatch]
class DeserializeFromDiskEntitiesEnginePatch
{
internal static IEntityFactory entityFactory = null;

private static readonly byte[] frameStart = (new UTF8Encoding()).GetBytes("GamecraftScripting");

public static void Prefix(ref ISerializationData ____serializationData, ref FasterList<byte> ____bytesStream, ref IEntitySerialization ____entitySerializer)
{
____entitySerializer.RegisterSerializationFactory<ScriptEntityDescriptor>(new ScriptDeserializationFactory(entityFactory));
uint scriptDataStart = ____serializationData.dataPos;
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos: {scriptDataStart}");
BinaryBufferReader bbr = new BinaryBufferReader(____bytesStream.ToArrayFast(out uint count), ____serializationData.dataPos);
byte[] frameBuffer = new byte[frameStart.Length];
GamecraftModdingAPI.Utility.Logging.MetaLog($"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();
GamecraftModdingAPI.Utility.Logging.MetaLog($"Buffer byte 0: {(new UTF8Encoding()).GetString(new byte[]{frameBuffer[0]})}");
while (frameBuffer[i] == frameStart[i])
{
i++;
if (i == frameStart.Length) break;
frameBuffer[i] = bbr.ReadByte();
GamecraftModdingAPI.Utility.Logging.MetaLog($"Buffer byte {i}: {(new UTF8Encoding()).GetString(new byte[] { frameBuffer[i] })}");
}
if (i == frameStart.Length) break;
}
// abort if at end of file
if (bbr.Position >= count - frameStart.Length)
{
GamecraftModdingAPI.Utility.Logging.MetaLog("Skipping script deserialization (no frame found)");
return;
}
____serializationData.dataPos = bbr.Position;
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos (after frame): {____serializationData.dataPos}");
uint scriptCount = bbr.ReadUint();
GamecraftModdingAPI.Utility.Logging.MetaLog($"scriptCount: {scriptCount}");
uint potentialStart = bbr.ReadUint();
if (potentialStart == ____serializationData.dataPos - frameStart.Length) return;
____serializationData.dataPos += 4u;
for (uint j = 0; j < scriptCount; j++)
{
EGID newScriptId = new EGID(j, ScriptBuilder.ScriptGroup);
// why doesn't \/this\/ do anything?
____entitySerializer.DeserializeNewEntity(newScriptId, ____serializationData, SerializationType.Storage);
}
bbr = new BinaryBufferReader(____bytesStream.ToArrayFast(out count), ____serializationData.dataPos);
uint actualStart = bbr.ReadUint();
GamecraftModdingAPI.Utility.Logging.MetaLog($"actualStart: {actualStart}");
GamecraftModdingAPI.Utility.Logging.MetaLog($"Deserialized {scriptCount} scripts starting at {scriptDataStart} ({actualStart})");
____serializationData.dataPos = scriptDataStart; // change back to original end point (just in case)
}

public static MethodBase TargetMethod()
{
return AccessTools.Method("RobocraftX.SaveAndLoad.DeserializeFromDiskEntitiesEngine:LoadingFinished");//AccessTools.TypeByName("RobocraftX.SaveAndLoad.DeserializeFromDiskEntities")
}
}
}

+ 17
- 0
GamecraftScripting/Serialization/SaveAndLoadCompositionRootPatch.cs View File

@@ -0,0 +1,17 @@
using System;
using Harmony;
using RobocraftX.SaveAndLoad;
using Svelto.ECS;
namespace GamecraftScripting.Serialization
{
[HarmonyPatch(typeof(SaveAndLoadCompositionRoot), "Compose")]
class SaveAndLoadCompositionRootPatch
{
public static void Prefix(EnginesRoot enginesRoot)
{
IEntityFactory factory = enginesRoot.GenerateEntityFactory();
DeserializeFromDiskEntitiesEnginePatch.entityFactory = factory;
Commands.SerializationCommandEngine.entityFactory = factory;
}
}
}

+ 57
- 0
GamecraftScripting/Serialization/SaveGameEnginePatch.cs View File

@@ -0,0 +1,57 @@
using System;
using System.Text;
using System.Reflection;
using Harmony;
using RobocraftX.Common;
using RobocraftX.SaveAndLoad;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.Serialization;

using GamecraftModdingAPI.Utility;

namespace GamecraftScripting.Serialization
{
[HarmonyPatch]
class SaveGameEnginePatch
{
private static readonly byte[] frameStart = (new UTF8Encoding()).GetBytes("GamecraftScripting");

public static void Postfix(ref ISerializationData serializationData, EntitiesDB entitiesDB, IEntitySerialization entitySerializer)
{
Logging.MetaLog("Running Postfix on game save serializer");
serializationData.data.ExpandBy((uint)frameStart.Length);
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out uint buffLen), serializationData.dataPos);
ScriptStruct[] scripts = entitiesDB.QueryEntities<ScriptStruct>(ScriptBuilder.ScriptGroup).ToFastAccess(out uint count);
uint scriptDataStart = serializationData.dataPos;
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos: {scriptDataStart}");
for (int i = 0; i < frameStart.Length; i++)
{
bbw.Write(frameStart[i]);
}
serializationData.dataPos += (uint)frameStart.Length;
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos (after frame start): {serializationData.dataPos}");
serializationData.data.ExpandBy(4u);
Logging.MetaLog($"scriptCount: {count}");
bbw.Write(count);
serializationData.dataPos += 4u;
//foreach (byte b in BitConverter.GetBytes(count)) serializationData.data.Add(b);
for (uint i = 0; i < count; i++)
{
EGID scriptId = new EGID(i, ScriptBuilder.ScriptGroup);
entitySerializer.SerializeEntity(scriptId, serializationData, SerializationType.Storage);
}
bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out buffLen), serializationData.dataPos);
serializationData.data.ExpandBy(4u);
bbw.Write(scriptDataStart);
serializationData.dataPos += 4u;
//foreach (byte b in BitConverter.GetBytes(scriptDataStart)) serializationData.data.Add(b);
serializationData.data.Trim();
}

public static MethodBase TargetMethod()
{
return typeof(SaveGameEngine).GetMethod("SerializeGameToBuffer");
}
}
}

+ 24
- 0
GamecraftScripting/Serialization/ScriptBuilder.cs View File

@@ -0,0 +1,24 @@
using System;
using Svelto.ECS;
using Svelto.ECS.Experimental;

namespace GamecraftScripting.Serialization
{
public static class ScriptBuilder
{
public static readonly ExclusiveGroup ScriptGroup = new ExclusiveGroup("SCRIPT_GROUP");

private static uint nextScriptId = 0u;

public static EGID BuildScriptEntity(string name, string script, IEntityFactory entityFactory)
{
EGID scriptId = new EGID(nextScriptId++, ScriptGroup);
EntityStructInitializer builder = entityFactory.BuildEntity<ScriptEntityDescriptor>(scriptId);
builder.Init(new ScriptStruct {
name = new ECSString(name),
script = new ECSString(script),
});
return scriptId;
}
}
}

+ 25
- 0
GamecraftScripting/Serialization/ScriptDeserializationFactory.cs View File

@@ -0,0 +1,25 @@
using System;
using Svelto.ECS;
using Svelto.ECS.Serialization;
using GamecraftModdingAPI.Utility;
namespace GamecraftScripting.Serialization
{
public class ScriptDeserializationFactory : IDeserializationFactory
{
private IEntityFactory entityFactory;

public ScriptDeserializationFactory(IEntityFactory entityFactory)
{
this.entityFactory = entityFactory;
}

public EntityStructInitializer BuildDeserializedEntity(EGID egid, ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor, SerializationType serializationType, IEntitySerialization entitySerialization)
{
Logging.MetaLog(entityFactory == null);
EntityStructInitializer esi = entityFactory.BuildEntity<ScriptEntityDescriptor>(egid);
entitySerialization.DeserializeEntityStructs(serializationData, entityDescriptor, ref esi, serializationType);
Logging.MetaLog($"Deserialized script named {esi.Get<ScriptStruct>().name.ToString()}");
return esi;
}
}
}

+ 28
- 0
GamecraftScripting/Serialization/ScriptEntityDescriptor.cs View File

@@ -0,0 +1,28 @@
using System;
using Svelto.ECS;
using Svelto.ECS.Serialization;

namespace GamecraftScripting.Serialization
{
public class ScriptEntityDescriptor : SerializableEntityDescriptor<ScriptEntityDescriptor._ScriptEntityDescriptor>
{
[HashName("GamecraftScriptingScriptEntityDescriptorV0")]
public class _ScriptEntityDescriptor : IEntityDescriptor
{
public IEntityBuilder[] entitiesToBuild => _entityBuilders;

private static readonly IEntityBuilder[] _entityBuilders = new IEntityBuilder[1]
{
new SerializableEntityBuilder<ScriptStruct>((SerializationType.Network, new ScriptSerializer()),
(SerializationType.Storage, new ScriptSerializer()))
};
}

public ScriptEntityDescriptor() : base()
{
GamecraftModdingAPI.Utility.Logging.MetaLog("ScriptEntityDescriptor Initialized");
GamecraftModdingAPI.Utility.Logging.MetaLog($"Entities to serialize: {entitiesToSerialize.Length}");
GamecraftModdingAPI.Utility.Logging.MetaLog($"Entities to serialize: {entitiesToBuild.Length}");
}
}
}

+ 61
- 0
GamecraftScripting/Serialization/ScriptSerializer.cs View File

@@ -0,0 +1,61 @@
using System;
using System.Text;

using RobocraftX.Common;
using Svelto.ECS;
using Svelto.ECS.Serialization;

namespace GamecraftScripting.Serialization
{
public class ScriptSerializer : ISerializer<ScriptStruct>
{
private static readonly UTF8Encoding utf8Encoding = new UTF8Encoding();

private static readonly uint padding = 42u;

public uint size => 0u;

public bool Deserialize(ref ScriptStruct value, ISerializationData serializationData)
{
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos: {serializationData.dataPos}");
BinaryBufferReader bbr = new BinaryBufferReader(serializationData.data.ToArrayFast(out uint count), serializationData.dataPos);
short nameLength = bbr.ReadShort();
GamecraftModdingAPI.Utility.Logging.MetaLog($"nameLength: {nameLength}");
byte[] serialName = new byte[nameLength];
bbr.ReadBytes(serialName, (uint)nameLength);
value.name.Set(utf8Encoding.GetString(serialName));
int scriptLength = bbr.ReadInt();
GamecraftModdingAPI.Utility.Logging.MetaLog($"scriptLength: {scriptLength}");
byte[] serialScript = new byte[scriptLength];
bbr.ReadBytes(serialScript, (uint)scriptLength);
value.script.Set(utf8Encoding.GetString(serialScript));
uint serialPadding = bbr.ReadUint();
serializationData.dataPos += (uint)(2 + nameLength + 4 + scriptLength + 4);
GamecraftModdingAPI.Utility.Logging.MetaLog($"Deserializing Script successful: {serialPadding == padding}");
return true;
}

public bool Serialize(in ScriptStruct value, ISerializationData serializationData)
{
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos: {serializationData.dataPos}");
byte[] serialName = utf8Encoding.GetBytes(value.name);
byte[] serialScript = utf8Encoding.GetBytes(value.script);
uint actualSize = (uint)(2 + serialName.Length + 4 + serialScript.Length + 4);
serializationData.data.ExpandBy(actualSize);
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out uint count), serializationData.dataPos);
bbw.Write((short)serialName.Length);
for (int i = 0; i < serialName.Length; i++)
{
bbw.Write(serialName[i]);
}
bbw.Write((int)serialScript.Length);
for (int i = 0; i < serialScript.Length; i++)
{
bbw.Write(serialScript[i]);
}
bbw.Write(padding);
serializationData.dataPos += actualSize;
return true;
}
}
}

+ 13
- 0
GamecraftScripting/Serialization/ScriptStruct.cs View File

@@ -0,0 +1,13 @@
using System;
using Svelto.ECS;
using Svelto.ECS.Experimental;

namespace GamecraftScripting.Serialization
{
public struct ScriptStruct : IEntityStruct
{
public ECSString name;

public ECSString script;
}
}

+ 60
- 0
GamecraftScripting/Serialization/SerializationDescriptorMapPatch.cs View File

@@ -0,0 +1,60 @@
using System;
using System.Reflection;
using Harmony;
using Svelto.ECS;
using Svelto.ECS.Serialization;
using GamecraftModdingAPI.Utility;

namespace GamecraftScripting.Serialization
{
#if DEBUG
[HarmonyPatch]
public class SerializationDescriptorMapPatch
{
public static void Prefix(ISerializableEntityDescriptor descriptor)
{
if (descriptor.entitiesToSerialize.Length == 0)
{
if (descriptor.entitiesToBuild.Length != 0)
{
Logging.MetaLog($"Descriptor: {descriptor.entitiesToBuild[0].GetType().FullName} (hash:{descriptor.hash})");
}
else
{
Logging.MetaLog($"Emtpy descriptor (hash: {descriptor.hash})");
}

}
else
{
Logging.MetaLog($"Descriptor: {descriptor.entitiesToSerialize[0].GetType().FullName} (hash:{descriptor.hash}) (serializables: {descriptor.entitiesToSerialize.Length})");
}
}

public static MethodBase TargetMethod()
{
//Logging.MetaLog(AccessTools.Method(AccessTools.Inner(AccessTools.TypeByName("Svelto.ECS.EnginesRoot"), "SerializationDescriptorMap"), "RegisterEntityDescriptor") == null);
return AccessTools.Method(AccessTools.Inner(AccessTools.TypeByName("Svelto.ECS.EnginesRoot"), "SerializationDescriptorMap"), "RegisterEntityDescriptor");
}
}

[HarmonyPatch]
public class SerializationDescriptorMapGetPatch
{
public static void Prefix(uint descriptorID)
{
if (descriptorID == 3520129338u)
{
Logging.MetaLog("Got hash for ScriptStruct");
}
Logging.MetaLog($"GetDescriptorFromHash({descriptorID})");
}

public static MethodBase TargetMethod()
{
//Logging.MetaLog(AccessTools.Method(AccessTools.Inner(AccessTools.TypeByName("Svelto.ECS.EnginesRoot"), "SerializationDescriptorMap"), "RegisterEntityDescriptor") == null);
return AccessTools.Method(AccessTools.Inner(AccessTools.TypeByName("Svelto.ECS.EnginesRoot"), "SerializationDescriptorMap"), "GetDescriptorFromHash");
}
}
#endif
}