Browse Source

Implemented serialization for events

Kenric Nugteren 11 months ago
parent
commit
c4cf1596a2
3 changed files with 319 additions and 5 deletions
  1. 1 1
      prs.classes/Entities/Events/Event.cs
  2. 189 4
      prs.shared/Events/Event.cs
  3. 129 0
      prs.shared/Events/SaveEvent.cs

+ 1 - 1
prs.classes/Entities/Events/Event.cs

@@ -28,7 +28,7 @@ namespace Comal.Classes
         /// Serialised event data.
         /// </summary>
         [NullEditor]
-        public string Data { get; set; } = "";
+        public byte[] Data { get; set; } = Array.Empty<byte>();
 
         [ExpressionEditor(null)]
         public string NotificationExpression { get; set; } = "";

+ 189 - 4
prs.shared/Events/Event.cs

@@ -2,16 +2,24 @@
 using Expressive;
 using InABox.Core;
 using InABox.Database;
+using Org.BouncyCastle.Asn1.X509.Qualified;
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
+using System.Reflection;
 using System.Text;
 using System.Threading.Tasks;
 
 namespace PRS.Shared.Events;
 
-public class EventData<T, TDataModel>
+public interface IEventData : ISerializeBinary
+{
+    IEvent Event { get; }
+}
+
+public class EventData<T, TDataModel> : IEventData
     where T : IEvent<TDataModel>
     where TDataModel : IEventDataModel
 {
@@ -24,6 +32,8 @@ public class EventData<T, TDataModel>
 
     public List<IEventAction<T>> Actions { get; set; }
 
+    IEvent IEventData.Event => Event;
+
     public EventData(T eventData)
     {
         Event = eventData;
@@ -32,10 +42,183 @@ public class EventData<T, TDataModel>
     }
 
     public Notification GenerateNotification(TDataModel model) => Event.GenerateNotification(model);
+
+    public void SerializeBinary(CoreBinaryWriter writer)
+    {
+        Event.SerializeBinary(writer);
+        writer.Write(Triggers.Count);
+        foreach(var trigger in Triggers)
+        {
+            EventUtils.SerializeObject(trigger, writer);
+        }
+        writer.Write(Actions.Count);
+        foreach(var action in Actions)
+        {
+            EventUtils.SerializeObject(action, writer);
+        }
+    }
+
+    public void DeserializeBinary(CoreBinaryReader reader)
+    {
+        Event.DeserializeBinary(reader);
+        var nTriggers = reader.ReadInt32();
+        for(int i = 0; i < nTriggers; ++i)
+        {
+            var trigger = EventUtils.DeserializeObject<ITrigger<T, TDataModel>>(EventUtils.GetTriggerType, reader);
+            Triggers.Add(trigger);
+        }
+        var nActions = reader.ReadInt32();
+        for(int i = 0; i < nActions; ++i)
+        {
+            var action = EventUtils.DeserializeObject<IEventAction<T>>(EventUtils.GetTriggerType, reader);
+            Actions.Add(action);
+        }
+    }
 }
 
 public static class EventUtils
 {
+    #region Serialisation
+
+    public static void SerializeObject(ISerializeBinary obj, CoreBinaryWriter writer)
+    {
+        writer.Write(obj.GetType().Name);
+        foreach(var arg in obj.GetType().GenericTypeArguments)
+        {
+            writer.Write(CoreUtils.EntityName(arg));
+        }
+        obj.SerializeBinary(writer);
+    }
+    public static T DeserializeObject<T>(Func<string, Type> typeSource, CoreBinaryReader reader)
+        where T : class, ISerializeBinary
+    {
+        var eventTypeName = reader.ReadString();
+        var objType = typeSource(eventTypeName);
+
+        var typeParams = objType.GetTypeInfo().GenericTypeParameters;
+        var typeArgs = new Type[typeParams.Length];
+        for(int i = 0; i < typeParams.Length; ++i)
+        {
+            var entityType = CoreUtils.GetEntity(reader.ReadString());
+            typeArgs[i] = entityType;
+        }
+        if(typeArgs.Length > 0)
+        {
+            objType = objType.MakeGenericType(typeArgs);
+        }
+        var obj = (Activator.CreateInstance(objType) as T)!;
+        obj.DeserializeBinary(reader);
+
+        return obj;
+    }
+
+    public static void Serialize(IEventData data, CoreBinaryWriter writer)
+    {
+        writer.Write(data.Event.GetType().Name);
+        foreach(var arg in data.Event.GetType().GenericTypeArguments)
+        {
+            writer.Write(CoreUtils.EntityName(arg));
+        }
+        data.SerializeBinary(writer);
+    }
+
+    public static IEventData Deserialize(CoreBinaryReader reader)
+    {
+        var eventTypeName = reader.ReadString();
+        var eventType = EventUtils.GetEventType(eventTypeName);
+
+        var typeParams = eventType.GetTypeInfo().GenericTypeParameters;
+        var typeArgs = new Type[typeParams.Length];
+        for(int i = 0; i < typeParams.Length; ++i)
+        {
+            var entityType = CoreUtils.GetEntity(reader.ReadString());
+            typeArgs[i] = entityType;
+        }
+        if(typeArgs.Length > 0)
+        {
+            eventType = eventType.MakeGenericType(typeArgs);
+        }
+        var ev = (Activator.CreateInstance(eventType) as IEvent)!;
+
+        var dataModelType = ev.GetType().GetInterfaceDefinition(typeof(IEvent<>))!.GenericTypeArguments[0];
+
+        var eventDataType = typeof(EventData<,>).MakeGenericType(ev.GetType(), dataModelType);
+
+        var eventData = (Activator.CreateInstance(eventDataType, ev) as IEventData)!;
+
+        eventData.DeserializeBinary(reader);
+
+        return eventData;
+    }
+
+    #endregion
+
+    #region Event Types
+
+    private static Dictionary<string, Type>? _eventTypes;
+    private static Dictionary<string, Type>? _triggerTypes;
+    private static Dictionary<string, Type>? _actionTypes;
+
+    [MemberNotNullWhen(true, nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes))]
+    private static bool _loadedTypes { get; set; }
+
+    [MemberNotNull(nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes))]
+    private static void LoadTypes()
+    {
+        if (_loadedTypes) return;
+
+        _loadedTypes = true;
+        _eventTypes = new();
+        _triggerTypes = new();
+        _actionTypes = new();
+        foreach(var type in CoreUtils.TypeList(x => true))
+        {
+            if (type.HasInterface(typeof(IEvent<>)))
+            {
+                if (type.GetConstructor([]) is not null)
+                {
+                    _eventTypes[type.Name] = type;
+                }
+            }
+            else if (type.HasInterface(typeof(ITrigger<,>)))
+            {
+                if (type.GetConstructor([]) is not null)
+                {
+                    _triggerTypes[type.Name] = type;
+                }
+            }
+            else if (type.HasInterface(typeof(IEventAction<>)))
+            {
+                if (type.GetConstructor([]) is not null)
+                {
+                    _triggerTypes[type.Name] = type;
+                }
+            }
+        }
+    }
+
+    public static Type GetEventType(string type)
+    {
+        LoadTypes();
+        return _eventTypes[type]; // Force exception if not a valid type name.
+    }
+
+    public static Type GetTriggerType(string type)
+    {
+        LoadTypes();
+        return _triggerTypes[type]; // Force exception if not a valid type name.
+    }
+
+    public static Type GetActionType(string type)
+    {
+        LoadTypes();
+        return _actionTypes[type]; // Force exception if not a valid type name.
+    }
+
+    #endregion
+
+    #region Execution
+
     private static bool Check<T, TDataModel>(EventData<T, TDataModel> ev, TDataModel dataModel)
         where T : IEvent<TDataModel>
         where TDataModel : IEventDataModel
@@ -94,6 +277,8 @@ public static class EventUtils
         }
         store.Provider.Save(notifications);
     }
+
+    #endregion
 }
 
 #region DataModel Definition
@@ -199,7 +384,7 @@ public class ChildEventDataModel : IChildEventDataModel
 
 #endregion
 
-public interface IEvent
+public interface IEvent : ISerializeBinary
 {
     IEventDataModelDefinition DataModelDefinition();
 
@@ -263,14 +448,14 @@ public interface IEvent<TDataModel> : IEvent
     Notification GenerateNotification(TDataModel model);
 }
 
-public interface ITrigger<TEvent, TDataModel>
+public interface ITrigger<TEvent, TDataModel> : ISerializeBinary
     where TEvent : IEvent<TDataModel>
     where TDataModel : IEventDataModel
 {
     bool Check(TDataModel dataModel);
 }
 
-public interface IEventAction
+public interface IEventAction : ISerializeBinary
 {
     IEnumerable<string> ReferencedVariables();
 

+ 129 - 0
prs.shared/Events/SaveEvent.cs

@@ -32,6 +32,13 @@ public class SaveEvent<T> : IEvent<SaveEventDataModel<T>>
         }
         return notification;
     }
+
+    public void SerializeBinary(CoreBinaryWriter writer)
+    {
+    }
+    public void DeserializeBinary(CoreBinaryReader reader)
+    {
+    }
 }
 
 public class SaveEventDataModelDefinition<T>(SaveEvent<T> ev) : IEventDataModelDefinition
@@ -104,6 +111,13 @@ public class CreatedSaveEventTrigger<T> : ITrigger<SaveEvent<T>, SaveEventDataMo
     {
         return dataModel.Entity.HasOriginalValue(x => x.ID);
     }
+
+    public void SerializeBinary(CoreBinaryWriter writer)
+    {
+    }
+    public void DeserializeBinary(CoreBinaryReader reader)
+    {
+    }
 }
 
 public class PropertyChangedSaveEventTrigger<T> : ITrigger<SaveEvent<T>, SaveEventDataModel<T>>
@@ -135,6 +149,30 @@ public class PropertyChangedSaveEventTrigger<T> : ITrigger<SaveEvent<T>, SaveEve
         }
         return true;
     }
+
+    public void SerializeBinary(CoreBinaryWriter writer)
+    {
+        if(TriggerProperty is null)
+        {
+            writer.Write(false);
+        }
+        else
+        {
+            writer.Write(true);
+            writer.Write(TriggerProperty.Name);
+        }
+    }
+    public void DeserializeBinary(CoreBinaryReader reader)
+    {
+        if(reader.ReadBoolean())
+        {
+            TriggerProperty = DatabaseSchema.Property(typeof(T), reader.ReadString());
+        }
+        else
+        {
+            TriggerProperty = null;
+        }
+    }
 }
 
 public class ScriptSaveEventTrigger<T> : ITrigger<SaveEvent<T>, SaveEventDataModel<T>>
@@ -182,6 +220,23 @@ public class Module
         }
         return _scriptDocument.Execute(methodname: "Check", parameters: [dataModel]);
     }
+
+    public void SerializeBinary(CoreBinaryWriter writer)
+    {
+        writer.Write(Script ?? "");
+    }
+    public void DeserializeBinary(CoreBinaryReader reader)
+    {
+        var script = reader.ReadString();
+        if (script.IsNullOrWhiteSpace())
+        {
+            Script = null;
+        }
+        else
+        {
+            Script = script;
+        }
+    }
 }
 
 #endregion
@@ -250,6 +305,23 @@ public class Module
     {
         yield break;
     }
+
+    public void SerializeBinary(CoreBinaryWriter writer)
+    {
+        writer.Write(Script ?? "");
+    }
+    public void DeserializeBinary(CoreBinaryReader reader)
+    {
+        var script = reader.ReadString();
+        if (script.IsNullOrWhiteSpace())
+        {
+            Script = null;
+        }
+        else
+        {
+            Script = script;
+        }
+    }
 }
 
 public class CreateEntityAction<T> : IEventAction<SaveEvent<T>>
@@ -277,6 +349,42 @@ public class CreateEntityAction<T> : IEventAction<SaveEvent<T>>
     {
         return Initializers.SelectMany(x => x.ReferencedVariables());
     }
+
+    public void SerializeBinary(CoreBinaryWriter writer)
+    {
+        if(EntityType is null)
+        {
+            writer.Write(false);
+        }
+        else
+        {
+            writer.Write(true);
+            writer.Write(EntityType.EntityName());
+        }
+        writer.Write(Initializers.Count);
+        foreach(var init in Initializers)
+        {
+            init.SerializeBinary(writer);
+        }
+    }
+    public void DeserializeBinary(CoreBinaryReader reader)
+    {
+        if(reader.ReadBoolean())
+        {
+            EntityType = CoreUtils.GetEntity(reader.ReadString());
+        }
+        else
+        {
+            EntityType = null;
+        }
+
+        var nInit = reader.ReadInt32();
+        for(int i = 0; i < nInit; ++i)
+        {
+            var init = new PropertyInitializer(reader, EntityType!);
+            Initializers.Add(init);
+        }
+    }
 }
 
 public class PropertyInitializer
@@ -308,6 +416,14 @@ public class PropertyInitializer
         }
     }
 
+    /// <summary>
+    /// <b>Only for use in serialisation.</b>
+    /// </summary>
+    public PropertyInitializer(CoreBinaryReader reader, Type entityType)
+    {
+        DeserializeBinary(reader, entityType);
+    }
+
     public PropertyInitializer(IProperty property, string value)
     {
         Property = property;
@@ -323,6 +439,19 @@ public class PropertyInitializer
     {
         return ValueExpression.ReferencedVariables;
     }
+
+    public void SerializeBinary(CoreBinaryWriter writer)
+    {
+        writer.Write(Property.Name);
+        writer.Write(Value);
+    }
+
+    [MemberNotNull(nameof(Property), nameof(_value))]
+    public void DeserializeBinary(CoreBinaryReader reader, Type entityType)
+    {
+        Property = DatabaseSchema.PropertyStrict(entityType, reader.ReadString());
+        Value = reader.ReadString();
+    }
 }
 
 #endregion