| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- using Comal.Classes;
- 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 interface IEventData : ISerializeBinary
- {
- IEvent Event { get; }
- }
- public class EventData<T, TDataModel> : IEventData
- where T : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- public T Event { get; set; }
- /// <summary>
- /// A list of triggers for this event. If any of the triggers match, the event runs.
- /// </summary>
- public List<IEventTrigger<T, TDataModel>> Triggers { get; set; }
- public List<IEventAction<T>> Actions { get; set; }
- IEvent IEventData.Event => Event;
- public EventData(T eventData)
- {
- Event = eventData;
- Triggers = new List<IEventTrigger<T, TDataModel>>();
- Actions = new List<IEventAction<T>>();
- }
- 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<IEventTrigger<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;
- private static Dictionary<Type, List<Type>>? _eventTriggerTypes;
- private static Dictionary<Type, List<Type>>? _eventActionTypes;
- [MemberNotNullWhen(true, nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes), nameof(_eventActionTypes))]
- private static bool _loadedTypes { get; set; }
- [MemberNotNull(nameof(_eventTypes), nameof(_triggerTypes), nameof(_actionTypes), nameof(_eventTriggerTypes), nameof(_eventActionTypes))]
- private static void LoadTypes()
- {
- if (_loadedTypes) return;
- _loadedTypes = true;
- _eventTypes = new();
- _triggerTypes = new();
- _actionTypes = new();
- _eventTriggerTypes = new();
- _eventActionTypes = 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.GetInterfaceDefinition(typeof(IEventTrigger<,>)) is Type eventTriggerInterface)
- {
- if (type.GetConstructor([]) is not null)
- {
- _triggerTypes[type.Name] = type;
- var eventType = eventTriggerInterface.GenericTypeArguments[0];
- eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
- _eventTriggerTypes.GetValueOrAdd(eventType).Add(type);
- }
- }
- else if (type.GetInterfaceDefinition(typeof(IEventAction<>)) is Type eventActionInterface)
- {
- if (type.GetConstructor([]) is not null)
- {
- _actionTypes[type.Name] = type;
- var eventType = eventActionInterface.GenericTypeArguments[0];
- eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
- _eventActionTypes.GetValueOrAdd(eventType).Add(type);
- }
- }
- }
- }
- public static IEnumerable<Type> GetEventTriggerTypes(Type eventType)
- {
- LoadTypes();
- eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
- return _eventTriggerTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty<Type>();
- }
- public static IEnumerable<Type> GetEventActionTypes(Type eventType)
- {
- LoadTypes();
- eventType = eventType.IsGenericType ? eventType.GetGenericTypeDefinition() : eventType;
- return _eventActionTypes.GetValueOrDefault(eventType) ?? Enumerable.Empty<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
- {
- return ev.Triggers.Any(x => x.Check(dataModel));
- }
- public static void Run<T, TDataModel>(IStore store, Event ev, EventData<T, TDataModel> evData, TDataModel dataModel)
- where T : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- if (!Check(evData, dataModel)) return;
- foreach(var action in evData.Actions)
- {
- IEvent.RunAction(evData.Event, dataModel, action);
- }
- NotifySubscribers(store, ev, evData, dataModel);
- }
- private static void NotifySubscribers<T, TDataModel>(IStore store, Event ev, EventData<T, TDataModel> evData, TDataModel dataModel)
- where T : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- string? description;
- if (ev.NotificationExpression.IsNullOrWhiteSpace())
- {
- description = null;
- }
- else
- {
- var descriptionExpr = new CoreExpression(ev.NotificationExpression, typeof(string));
- if(!descriptionExpr.TryEvaluate<string>(dataModel).Get(out description, out var error))
- {
- CoreUtils.LogException(store.UserID, error, extra: "Error notifying subscribers", store.Logger);
- return;
- }
- }
- var subscribers = store.Provider.Query(
- new Filter<EventSubscriber>(x => x.Event.ID).IsEqualTo(ev.ID),
- Columns.None<EventSubscriber>().Add(x => x.Employee.ID))
- .ToArray<EventSubscriber>();
- var notifications = new List<Notification>();
- foreach(var subscriber in subscribers)
- {
- var notification = evData.GenerateNotification(dataModel);
- notification.Employee.CopyFrom(subscriber.Employee);
- if(description is not null)
- {
- notification.Description = description;
- }
- notifications.Add(notification);
- }
- store.Provider.Save(notifications);
- }
- #endregion
- }
- #region DataModel Definition
- public interface IEventVariable
- {
- string Name { get; set; }
- Type Type { get; set; }
- }
- public class StandardEventVariable : IEventVariable
- {
- public string Name { get; set; }
- public Type Type { get; set; }
- public StandardEventVariable(string name, Type type)
- {
- Name = name;
- Type = type;
- }
- }
- public class ListEventVariable : IEventVariable
- {
- public string Name { get; set; }
- public Type Type { get; set; }
- public ListEventVariable(string name, Type type)
- {
- Name = name;
- Type = type;
- }
- }
- public interface IEventDataModelDefinition
- {
- IEnumerable<IEventVariable> GetVariables();
- IEventVariable? GetVariable(string name);
- }
- #endregion
- #region DataModel
- public interface IEventDataModel : IVariableProvider
- {
- bool TryGetVariable(string name, out object? value);
- bool IVariableProvider.TryGetValue(string variableName, out object? value)
- {
- return TryGetVariable(variableName, out value);
- }
- T RootModel<T>()
- where T : IEventDataModel
- {
- if(this is IChildEventDataModel child)
- {
- return child.Parent.RootModel<T>();
- }
- else if(this is T root)
- {
- return root;
- }
- else
- {
- throw new Exception($"Root model of wrong type; expected {typeof(T).FullName}; got {GetType().FullName}");
- }
- }
- }
- public interface ITypedEventDataModel : IEventDataModel
- {
- public Type EntityType { get; }
- }
- public interface IChildEventDataModel : IEventDataModel
- {
- IEventDataModel Parent { get; }
- }
- public class ChildEventDataModel : IChildEventDataModel
- {
- public IEventDataModel Parent { get; set; }
- public Dictionary<string, object?> Values { get; set; } = new Dictionary<string, object?>();
- public ChildEventDataModel(IEventDataModel parent)
- {
- Parent = parent;
- }
- public bool TryGetVariable(string name, out object? value)
- {
- return Values.TryGetValue(name, out value)
- || Parent.TryGetVariable(name, out value);
- }
- }
- #endregion
- public interface IEvent : ISerializeBinary
- {
- IEventDataModelDefinition DataModelDefinition();
- public static void RunAction(IEvent ev, IEventDataModel model, IEventAction action)
- {
- var dataModelDef = ev.DataModelDefinition();
- var values = new List<(string name, object? value)[]>() { Array.Empty<(string, object?)>() };
- var vars = action.ReferencedVariables();
- foreach(var variable in vars)
- {
- var varDef = dataModelDef.GetVariable(variable);
- if(varDef is ListEventVariable)
- {
- if (model.TryGetVariable(varDef.Name, out var list) && list is IEnumerable enumerable)
- {
- var oldValues = values;
- values = new List<(string name, object? value)[]>();
- foreach(var item in enumerable)
- {
- foreach(var valueList in oldValues)
- {
- values.Add(valueList.Concatenate(new (string name, object? value)[]
- {
- (varDef.Name, item)
- }));
- }
- }
- }
- else
- {
- values.Clear();
- break;
- }
- }
- }
- if(values.Count > 0 && (values.Count > 1 || values[0].Length > 0))
- {
- var subModel = new ChildEventDataModel(model);
- foreach(var valueSet in values)
- {
- subModel.Values.Clear();
- foreach(var (name, value) in valueSet)
- {
- subModel.Values[name] = value;
- }
- action.Execute(subModel);
- }
- }
- else
- {
- action.Execute(model);
- }
- }
- }
- public interface IEvent<TDataModel> : IEvent
- {
- Notification GenerateNotification(TDataModel model);
- }
- public interface IEventTrigger
- {
- string GetDescription();
- }
- public interface IEventTrigger<TEvent, TDataModel> : ISerializeBinary, IEventTrigger
- where TEvent : IEvent<TDataModel>
- where TDataModel : IEventDataModel
- {
- bool Check(TDataModel dataModel);
- }
- public interface IEventAction : ISerializeBinary
- {
- IEnumerable<string> ReferencedVariables();
- object? Execute(IEventDataModel dataModel);
- string GetDescription();
- }
- public interface IEventAction<TEvent> : IEventAction
- where TEvent : IEvent
- {
- }
|