diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/ConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/ConditionOperator.cs
index 416f41912..3011059ea 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/ConditionOperator.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/ConditionOperator.cs
@@ -13,20 +13,30 @@ namespace Artemis.Core
/// The parameter on the left side of the expression
/// The parameter on the right side of the expression
public abstract bool Evaluate(TLeftSide a, TRightSide b);
-
+
///
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
{
// TODO: Can we avoid boxing/unboxing?
TLeftSide leftSide;
if (leftSideValue != null)
- leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
+ {
+ if (leftSideValue.GetType() != typeof(TLeftSide))
+ leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
+ else
+ leftSide = (TLeftSide) leftSideValue;
+ }
else
leftSide = default;
TRightSide rightSide;
if (rightSideValue != null)
- rightSide = (TRightSide) Convert.ChangeType(rightSideValue, typeof(TRightSide));
+ {
+ if (rightSideValue.GetType() != typeof(TRightSide))
+ rightSide = (TRightSide) Convert.ChangeType(rightSideValue, typeof(TRightSide));
+ else
+ rightSide = (TRightSide) rightSideValue;
+ }
else
rightSide = default;
@@ -57,7 +67,12 @@ namespace Artemis.Core
// TODO: Can we avoid boxing/unboxing?
TLeftSide leftSide;
if (leftSideValue != null)
- leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
+ {
+ if (leftSideValue.GetType() != typeof(TLeftSide))
+ leftSide = (TLeftSide)Convert.ChangeType(leftSideValue, typeof(TLeftSide));
+ else
+ leftSide = (TLeftSide)leftSideValue;
+ }
else
leftSide = default;
diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs
new file mode 100644
index 000000000..be0cf0e81
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs
@@ -0,0 +1,189 @@
+using System;
+using Artemis.Storage.Entities.Profile.Abstract;
+using Artemis.Storage.Entities.Profile.Conditions;
+
+namespace Artemis.Core
+{
+ ///
+ /// A condition that evaluates to true when an event is triggered
+ ///
+ public class DataModelConditionEvent : DataModelConditionPart
+ {
+ private bool _disposed;
+ private bool _reinitializing;
+ private IDataModelEvent? _event;
+ private bool _eventTriggered;
+
+ ///
+ /// Creates a new instance of the class
+ ///
+ ///
+ public DataModelConditionEvent(DataModelConditionPart parent)
+ {
+ Parent = parent;
+ Entity = new DataModelConditionEventEntity();
+
+ Initialize();
+ }
+
+ internal DataModelConditionEvent(DataModelConditionPart parent, DataModelConditionEventEntity entity)
+ {
+ Parent = parent;
+ Entity = entity;
+
+ Initialize();
+ }
+
+ ///
+ /// Gets the path of the event property
+ ///
+ public DataModelPath? EventPath { get; private set; }
+
+
+ internal DataModelConditionEventEntity Entity { get; set; }
+
+ ///
+ public override bool Evaluate()
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("DataModelConditionEvent");
+
+ // Ensure the event has not been replaced
+ if (EventPath?.GetValue() is IDataModelEvent dataModelEvent && _event != dataModelEvent)
+ SubscribeToDataModelEvent(dataModelEvent);
+
+ // Only evaluate to true once every time the event has been triggered
+ if (!_eventTriggered)
+ return false;
+
+ _eventTriggered = false;
+ return true;
+ }
+
+ private void SubscribeToDataModelEvent(IDataModelEvent dataModelEvent)
+ {
+ if (_event != null)
+ _event.EventTriggered -= OnEventTriggered;
+
+ _event = dataModelEvent;
+ if (_event != null)
+ _event.EventTriggered += OnEventTriggered;
+ }
+
+ ///
+ /// Updates the event the condition is triggered by
+ ///
+ public void UpdateEvent(DataModelPath? path)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("DataModelConditionEvent");
+
+ if (path != null && !path.IsValid)
+ throw new ArtemisCoreException("Cannot update event to an invalid path");
+
+ EventPath?.Dispose();
+ EventPath = path != null ? new DataModelPath(path) : null;
+ SubscribeToEventPath();
+ }
+
+ #region IDisposable
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ _disposed = true;
+
+ EventPath?.Dispose();
+
+ foreach (DataModelConditionPart child in Children)
+ child.Dispose();
+
+ base.Dispose(disposing);
+ }
+
+ #endregion
+
+ internal override bool EvaluateObject(object target)
+ {
+ return false;
+ }
+
+ internal override void Save()
+ {
+ // Don't save an invalid state
+ if (EventPath != null && !EventPath.IsValid)
+ return;
+
+ // Target list
+ EventPath?.Save();
+ Entity.EventPath = EventPath?.Entity;
+ }
+
+ internal override DataModelConditionPartEntity GetEntity()
+ {
+ return Entity;
+ }
+
+ internal void Initialize()
+ {
+ if (Entity.EventPath == null)
+ return;
+
+ // Ensure the list path is valid and points to a list
+ DataModelPath eventPath = new DataModelPath(null, Entity.EventPath);
+ // Can't check this on an invalid list, if it becomes valid later lets hope for the best
+ if (eventPath.IsValid && !PointsToEvent(eventPath))
+ return;
+
+ EventPath = eventPath;
+ SubscribeToEventPath();
+ }
+
+ private bool PointsToEvent(DataModelPath dataModelPath)
+ {
+ Type? type = dataModelPath.GetPropertyType();
+ if (type == null)
+ return false;
+
+ return typeof(IDataModelEvent).IsAssignableFrom(type);
+ }
+
+ private void SubscribeToEventPath()
+ {
+ if (EventPath == null) return;
+ EventPath.PathValidated += EventPathOnPathValidated;
+ EventPath.PathInvalidated += EventPathOnPathInvalidated;
+ }
+
+ #region Event handlers
+
+ private void OnEventTriggered(object? sender, EventArgs e)
+ {
+ _eventTriggered = true;
+ }
+
+ private void EventPathOnPathValidated(object? sender, EventArgs e)
+ {
+ if (_reinitializing)
+ return;
+
+ _reinitializing = true;
+ EventPath?.Dispose();
+ Initialize();
+ _reinitializing = false;
+ }
+
+ private void EventPathOnPathInvalidated(object? sender, EventArgs e)
+ {
+ if (_reinitializing)
+ return;
+
+ _reinitializing = true;
+ EventPath?.Dispose();
+ Initialize();
+ _reinitializing = false;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs
index a3459f385..392e1ba51 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs
@@ -180,7 +180,7 @@ namespace Artemis.Core
DataModelPath listPath = new DataModelPath(null, Entity.ListPath);
Type listType = listPath.GetPropertyType()!;
// Can't check this on an invalid list, if it becomes valid later lets hope for the best
- if (listPath.IsValid && !listPath.PointsToList)
+ if (listPath.IsValid && !PointsToList(listPath))
return;
ListPath = listPath;
@@ -208,6 +208,12 @@ namespace Artemis.Core
}
}
+ private bool PointsToList(DataModelPath dataModelPath)
+ {
+ Type? type = dataModelPath.GetPropertyType();
+ return type?.IsGenericEnumerable() ?? false;
+ }
+
private void SubscribeToListPath()
{
if (ListPath == null) return;
diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs
new file mode 100644
index 000000000..79e719881
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs
@@ -0,0 +1,85 @@
+using System;
+using Artemis.Core.DataModelExpansions;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents a data model event with event arguments of type
+ ///
+ public class DataModelEvent : IDataModelEvent where T : DataModelEventArgs
+ {
+ ///
+ /// Trigger the event with the given
+ ///
+ /// The event argument to pass to the event
+ public void Trigger(T eventArgs)
+ {
+ if (eventArgs == null) throw new ArgumentNullException(nameof(eventArgs));
+ eventArgs.TriggerTime = DateTime.Now;
+
+ LastEventArguments = eventArgs;
+ LastTrigger = DateTime.Now;
+ TriggerCount++;
+
+ OnEventTriggered();
+ }
+
+ internal virtual void OnEventTriggered()
+ {
+ EventTriggered?.Invoke(this, EventArgs.Empty);
+ }
+
+ ///
+ public DateTime LastTrigger { get; private set; }
+
+ ///
+ public int TriggerCount { get; private set; }
+
+ ///
+ /// Gets the event arguments of the last time the event was triggered
+ ///
+ public T? LastEventArguments { get; private set; }
+
+ ///
+ [DataModelIgnore]
+ public Type ArgumentsType => typeof(T);
+
+ ///
+ public event EventHandler? EventTriggered;
+ }
+
+ ///
+ /// Represents a data model event without event arguments
+ ///
+ public class DataModelEvent : IDataModelEvent
+ {
+ ///
+ /// Trigger the event
+ ///
+ public void Trigger()
+ {
+ LastTrigger = DateTime.Now;
+ TriggerCount++;
+
+ OnEventTriggered();
+ }
+
+ internal virtual void OnEventTriggered()
+ {
+ EventTriggered?.Invoke(this, EventArgs.Empty);
+ }
+
+ ///
+ public DateTime LastTrigger { get; private set; }
+
+ ///
+ public int TriggerCount { get; private set; }
+
+ ///
+ [DataModelIgnore]
+ public Type? ArgumentsType => null;
+
+ ///
+ public event EventHandler? EventTriggered;
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelEventArgs.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelEventArgs.cs
new file mode 100644
index 000000000..3e20a9b1c
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelEventArgs.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents the base class for data model events that contain event data
+ ///
+ public class DataModelEventArgs
+ {
+ ///
+ /// Gets the time at which the event with these arguments was triggered
+ ///
+ public DateTime TriggerTime { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs
index 64f62ed68..bd4f0b540 100644
--- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs
+++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs
@@ -111,18 +111,6 @@ namespace Artemis.Core
///
public IReadOnlyCollection Segments => _segments.ToList().AsReadOnly();
- ///
- /// Gets a boolean indicating whether this data model path points to a list
- ///
- public bool PointsToList
- {
- get
- {
- Type? type = GetPropertyType();
- return type?.IsGenericEnumerable() ?? false;
- }
- }
-
internal DataModelPathEntity Entity { get; }
internal Func