diff --git a/.gitignore b/.gitignore index 045e58f8..e055d12f 100644 --- a/.gitignore +++ b/.gitignore @@ -497,3 +497,5 @@ Transport/@stateflows/http-client/dist/ Examples/SignalR/SignalR.Client/ClientApp/.angular/ *.txt Publish.ps1 +/Transport/@stateflows/common/dist +/Transport/@stateflows/http-client/dist diff --git a/Core/Stateflows.Common/Activities/Classes/ActivityId.cs b/Core/Stateflows.Common/Activities/Classes/ActivityId.cs index 363c05e2..0e2bfc08 100644 --- a/Core/Stateflows.Common/Activities/Classes/ActivityId.cs +++ b/Core/Stateflows.Common/Activities/Classes/ActivityId.cs @@ -1,4 +1,5 @@ using System; +using Newtonsoft.Json; using Stateflows.Common.Exceptions; using Stateflows.Common.Utilities; @@ -27,6 +28,10 @@ public ActivityId(BehaviorId id) public string Instance { get; set; } + [JsonIgnore] + public readonly ActivityClass ActivityClass => new ActivityClass(Name); + + [JsonIgnore] public readonly BehaviorId BehaviorId => new BehaviorId(BehaviorType.Activity, Name, Instance); public static bool operator ==(ActivityId id1, ActivityId id2) diff --git a/Core/Stateflows.Common/Activities/Classes/ActivityWrapper.cs b/Core/Stateflows.Common/Activities/Classes/ActivityWrapper.cs index 789935a1..cfd529f0 100644 --- a/Core/Stateflows.Common/Activities/Classes/ActivityWrapper.cs +++ b/Core/Stateflows.Common/Activities/Classes/ActivityWrapper.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System; using System.Threading.Tasks; using System.Collections.Generic; using Stateflows.Activities; @@ -28,9 +28,6 @@ public Task> ExecuteAsync(InitializationRequest return Behavior.RequestAsync(executionRequest); } - public Task> CancelAsync() - => RequestAsync(new CancelRequest()); - public Task SendAsync(TEvent @event) where TEvent : Event, new() => Behavior.SendAsync(@event); @@ -38,5 +35,25 @@ public Task SendAsync(TEvent @event) public Task> RequestAsync(Request request) where TResponse : Response, new() => Behavior.RequestAsync(request); + + public Task WatchAsync(Action handler) + where TNotification : Notification, new() + => Behavior.WatchAsync(handler); + + public Task UnwatchAsync() + where TNotification : Notification, new() + => Behavior.UnwatchAsync(); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + => Behavior.Dispose(); + + ~ActivityWrapper() + => Dispose(false); } } diff --git a/Core/Stateflows.Common/Activities/Events/CancelRequest.cs b/Core/Stateflows.Common/Activities/Events/CancelRequest.cs deleted file mode 100644 index 555bcc70..00000000 --- a/Core/Stateflows.Common/Activities/Events/CancelRequest.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Stateflows.Common; - -namespace Stateflows.Activities.Events -{ - public sealed class CancelRequest : Request - { } -} diff --git a/Core/Stateflows.Common/Activities/Events/CancelResponse.cs b/Core/Stateflows.Common/Activities/Events/CancelResponse.cs deleted file mode 100644 index e79543f7..00000000 --- a/Core/Stateflows.Common/Activities/Events/CancelResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Stateflows.Common; - -namespace Stateflows.Activities.Events -{ - public sealed class CancelResponse : Response - { - public bool CancelSuccessful { get; set; } - } -} diff --git a/Core/Stateflows.Common/Activities/Interfaces/IActivity.cs b/Core/Stateflows.Common/Activities/Interfaces/IActivity.cs index d4e3a7ca..07d0f751 100644 --- a/Core/Stateflows.Common/Activities/Interfaces/IActivity.cs +++ b/Core/Stateflows.Common/Activities/Interfaces/IActivity.cs @@ -8,7 +8,5 @@ namespace Stateflows.Activities public interface IActivity : IBehavior { Task> ExecuteAsync(InitializationRequest initializationRequest = null, IEnumerable inputTokens = null); - - Task> CancelAsync(); } } diff --git a/Core/Stateflows.Common/Classes/RequestResult.cs b/Core/Stateflows.Common/Classes/RequestResult.cs index ba091fd9..3078217e 100644 --- a/Core/Stateflows.Common/Classes/RequestResult.cs +++ b/Core/Stateflows.Common/Classes/RequestResult.cs @@ -8,8 +8,8 @@ public class RequestResult : SendResult [JsonConstructor] protected RequestResult() : base() { } - public RequestResult(Request request, EventStatus status, EventValidation requestValidation) - : base(request, status, requestValidation) + public RequestResult(Request request, EventStatus status, EventValidation validation = null) + : base(request, status, validation) { Response = request.Response; } @@ -19,8 +19,8 @@ public RequestResult(Request request, EventStatus status, EventValida public class RequestResult : SendResult { - public RequestResult(Event request, Response response, EventStatus status, EventValidation requestValidation) - : base(request, status, requestValidation) + public RequestResult(Event request, Response response, EventStatus status, EventValidation validation = null) + : base(request, status, validation) { Response = response; } diff --git a/Core/Stateflows.Common/Context/StateflowsContext.cs b/Core/Stateflows.Common/Context/StateflowsContext.cs index 1b3c4ec0..3e936267 100644 --- a/Core/Stateflows.Common/Context/StateflowsContext.cs +++ b/Core/Stateflows.Common/Context/StateflowsContext.cs @@ -1,6 +1,6 @@ using System; -using System.Collections.Generic; using System.Linq; +using System.Collections.Generic; namespace Stateflows.Common.Context { @@ -21,6 +21,78 @@ public bool ShouldSerializePendingTimeEvents() public Dictionary PendingTimeEvents { get; set; } = new Dictionary(); + public Dictionary> Subscriptions { get; set; } = new Dictionary>(); + + public bool AddSubscription(BehaviorId subscribeeBehaviorId, string eventName) + { + if (!Subscriptions.TryGetValue(subscribeeBehaviorId, out var eventNames)) + { + eventNames = new List(); + Subscriptions[subscribeeBehaviorId] = eventNames; + } + + if (!eventNames.Contains(eventName)) + { + eventNames.Add(eventName); + + return true; + } + + return false; + } + + public bool RemoveSubscription(BehaviorId subscribeeBehaviorId, string eventName) + { + if ( + Subscriptions.TryGetValue(subscribeeBehaviorId, out var eventNames) && + eventNames.Contains(eventName) + ) + { + eventNames.Remove(eventName); + + return true; + } + + return false; + } + + public Dictionary> Subscribers { get; set; } = new Dictionary>(); + + public bool AddSubscribers(BehaviorId subscriberBehaviorId, IEnumerable notificationNames) + { + foreach (var notificationName in notificationNames) + { + if (!Subscribers.TryGetValue(notificationName, out var behaviorIds)) + { + behaviorIds = new List(); + Subscribers[notificationName] = behaviorIds; + } + + if (!behaviorIds.Contains(subscriberBehaviorId)) + { + behaviorIds.Add(subscriberBehaviorId); + } + } + + return true; + } + + public bool RemoveSubscribers(BehaviorId subscriberBehaviorId, IEnumerable notificationNames) + { + foreach (var notificationName in notificationNames) + { + if ( + Subscribers.TryGetValue(notificationName, out var behaviorIds) && + behaviorIds.Contains(subscriberBehaviorId) + ) + { + behaviorIds.Remove(subscriberBehaviorId); + } + } + + return true; + } + public bool ShouldSerializeValues() => Values.Any(); diff --git a/Core/Stateflows.Common/Enums/EventStatus.cs b/Core/Stateflows.Common/Enums/EventStatus.cs index c36eade0..005a36b5 100644 --- a/Core/Stateflows.Common/Enums/EventStatus.cs +++ b/Core/Stateflows.Common/Enums/EventStatus.cs @@ -29,6 +29,10 @@ public enum EventStatus /// /// Event was omitted because other events in CompoundRequest were invalid /// - Omitted + Omitted, + /// + /// Event was forwarded to embedded behavior + /// + Forwarded } } diff --git a/Core/Stateflows.Common/Events/BehaviorStatusNotification.cs b/Core/Stateflows.Common/Events/BehaviorStatusNotification.cs new file mode 100644 index 00000000..43a30975 --- /dev/null +++ b/Core/Stateflows.Common/Events/BehaviorStatusNotification.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Stateflows.Common +{ + public class BehaviorStatusNotification : Notification + { + public BehaviorStatus BehaviorStatus { get; set; } + + [JsonProperty(TypeNameHandling = TypeNameHandling.None)] + public IEnumerable ExpectedEvents { get; set; } + } +} diff --git a/Core/Stateflows.Common/Events/BehaviorStatusResponse.cs b/Core/Stateflows.Common/Events/BehaviorStatusResponse.cs index 138d4fae..895aba7c 100644 --- a/Core/Stateflows.Common/Events/BehaviorStatusResponse.cs +++ b/Core/Stateflows.Common/Events/BehaviorStatusResponse.cs @@ -1,9 +1,15 @@ -namespace Stateflows.Common +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Stateflows.Common { public class BehaviorStatusResponse : Response { public override string Name => nameof(BehaviorStatusResponse); public BehaviorStatus BehaviorStatus { get; set; } + + [JsonProperty(TypeNameHandling = TypeNameHandling.None)] + public IEnumerable ExpectedEvents { get; set; } } } diff --git a/Core/Stateflows.Common/Events/Command.cs b/Core/Stateflows.Common/Events/Command.cs new file mode 100644 index 00000000..68ed0f59 --- /dev/null +++ b/Core/Stateflows.Common/Events/Command.cs @@ -0,0 +1,5 @@ +namespace Stateflows.Common +{ + public abstract class Command : Event + { } +} diff --git a/Core/Stateflows.Common/Events/Event.cs b/Core/Stateflows.Common/Events/Event.cs index 1dee4d95..3cff9c74 100644 --- a/Core/Stateflows.Common/Events/Event.cs +++ b/Core/Stateflows.Common/Events/Event.cs @@ -9,6 +9,8 @@ public class Event : Token { [JsonProperty(TypeNameHandling = TypeNameHandling.None)] public List Headers { get; set; } = new List(); + + public DateTime SentAt { get; set; } } public class Event : Event diff --git a/Core/Stateflows.Common/Events/EventValidation.cs b/Core/Stateflows.Common/Events/EventValidation.cs index 6a800513..b7029c11 100644 --- a/Core/Stateflows.Common/Events/EventValidation.cs +++ b/Core/Stateflows.Common/Events/EventValidation.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; -using Stateflows.Common.Extensions; namespace Stateflows.Common { diff --git a/Core/Stateflows.Common/Events/FinalizationRequest.cs b/Core/Stateflows.Common/Events/FinalizationRequest.cs new file mode 100644 index 00000000..0a448e17 --- /dev/null +++ b/Core/Stateflows.Common/Events/FinalizationRequest.cs @@ -0,0 +1,5 @@ +namespace Stateflows.Common +{ + public sealed class FinalizationRequest : Request + { } +} diff --git a/Core/Stateflows.Common/Events/FinalizationResponse.cs b/Core/Stateflows.Common/Events/FinalizationResponse.cs new file mode 100644 index 00000000..5d2421a0 --- /dev/null +++ b/Core/Stateflows.Common/Events/FinalizationResponse.cs @@ -0,0 +1,7 @@ +namespace Stateflows.Common +{ + public sealed class FinalizationResponse : Response + { + public bool FinalizationSuccessful { get; set; } + } +} diff --git a/Core/Stateflows.Common/Events/Notification.cs b/Core/Stateflows.Common/Events/Notification.cs new file mode 100644 index 00000000..0ee4d7f9 --- /dev/null +++ b/Core/Stateflows.Common/Events/Notification.cs @@ -0,0 +1,17 @@ +namespace Stateflows.Common +{ + public class Notification : Event + { + public BehaviorId SenderId { get; set; } + } + + public class Notification : Notification + { + public Notification() + { + Payload = default; + } + + public TPayload Payload { get; set; } + } +} diff --git a/Core/Stateflows.Common/Events/NotificationsRequest.cs b/Core/Stateflows.Common/Events/NotificationsRequest.cs new file mode 100644 index 00000000..4ac00644 --- /dev/null +++ b/Core/Stateflows.Common/Events/NotificationsRequest.cs @@ -0,0 +1,5 @@ +namespace Stateflows.Common +{ + public sealed class NotificationsRequest : Request + { } +} diff --git a/Core/Stateflows.Common/Events/NotificationsResponse.cs b/Core/Stateflows.Common/Events/NotificationsResponse.cs new file mode 100644 index 00000000..660d6682 --- /dev/null +++ b/Core/Stateflows.Common/Events/NotificationsResponse.cs @@ -0,0 +1,5 @@ +namespace Stateflows.Common +{ + public sealed class NotificationsResponse : Response + { } +} diff --git a/Core/Stateflows.Common/Events/ResetRequest.cs b/Core/Stateflows.Common/Events/ResetRequest.cs index 9fc97bb3..2bc4c446 100644 --- a/Core/Stateflows.Common/Events/ResetRequest.cs +++ b/Core/Stateflows.Common/Events/ResetRequest.cs @@ -1,5 +1,7 @@ namespace Stateflows.Common { public class ResetRequest : Request - { } + { + public bool KeepVersion { get; set; } = false; + } } diff --git a/Core/Stateflows.Common/Events/SubscriptionRequest.cs b/Core/Stateflows.Common/Events/SubscriptionRequest.cs new file mode 100644 index 00000000..3b0be0ce --- /dev/null +++ b/Core/Stateflows.Common/Events/SubscriptionRequest.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Stateflows.Common +{ + public sealed class SubscriptionRequest : Request + { + public BehaviorId BehaviorId { get; set; } + + [JsonProperty(TypeNameHandling = TypeNameHandling.None)] + public List NotificationNames { get; set; } = new List(); + } +} diff --git a/Core/Stateflows.Common/Events/SubscriptionResponse.cs b/Core/Stateflows.Common/Events/SubscriptionResponse.cs new file mode 100644 index 00000000..dea0ac66 --- /dev/null +++ b/Core/Stateflows.Common/Events/SubscriptionResponse.cs @@ -0,0 +1,7 @@ +namespace Stateflows.Common +{ + public sealed class SubscriptionResponse : Response + { + public bool SubscriptionSuccessful { get; set; } + } +} diff --git a/Core/Stateflows.Common/Events/UnsubscriptionRequest.cs b/Core/Stateflows.Common/Events/UnsubscriptionRequest.cs new file mode 100644 index 00000000..93e5c16f --- /dev/null +++ b/Core/Stateflows.Common/Events/UnsubscriptionRequest.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Stateflows.Common +{ + public sealed class UnsubscriptionRequest : Request + { + public BehaviorId BehaviorId { get; set; } + + [JsonProperty(TypeNameHandling = TypeNameHandling.None)] + public List NotificationNames { get; set; } = new List(); + } +} diff --git a/Core/Stateflows.Common/Events/UnsubscriptionResponse.cs b/Core/Stateflows.Common/Events/UnsubscriptionResponse.cs new file mode 100644 index 00000000..585b9470 --- /dev/null +++ b/Core/Stateflows.Common/Events/UnsubscriptionResponse.cs @@ -0,0 +1,7 @@ +namespace Stateflows.Common +{ + public sealed class UnsubscriptionResponse : Response + { + public bool UnsubscriptionSuccessful { get; set; } + } +} diff --git a/Core/Stateflows.Common/Extensions/TypeExtensions.cs b/Core/Stateflows.Common/Extensions/TypeExtensions.cs index 6d1da464..89245845 100644 --- a/Core/Stateflows.Common/Extensions/TypeExtensions.cs +++ b/Core/Stateflows.Common/Extensions/TypeExtensions.cs @@ -8,16 +8,25 @@ public static class TypeExtensions { public static string GetReadableName(this Type type) { + var result = string.Empty; if (!type.IsGenericType) { - return type.FullName; + result = type.FullName; } else { var typeName = type.GetGenericTypeDefinition().FullName.Split('`').First(); var typeNames = string.Join(", ", type.GetGenericArguments().Select(t => t.FullName)); - return $"{typeName}<{typeNames}>"; + result = $"{typeName}<{typeNames}>"; } + + var standardPrefix = "Stateflows.Activities."; + if (result.StartsWith(standardPrefix)) + { + result = result.Substring(standardPrefix.Length); + } + + return result; } public static object GetUninitializedInstance(this Type type) diff --git a/Core/Stateflows.Common/Interfaces/IBehavior.cs b/Core/Stateflows.Common/Interfaces/IBehavior.cs index 376ff538..2909d731 100644 --- a/Core/Stateflows.Common/Interfaces/IBehavior.cs +++ b/Core/Stateflows.Common/Interfaces/IBehavior.cs @@ -1,18 +1,56 @@ -using System.Threading.Tasks; +using System; +using System.Linq; +using System.Threading.Tasks; +using Stateflows.Common.Data; +using Stateflows.Common.Interfaces; namespace Stateflows.Common { - public interface IBehavior + public interface IBehavior : IWatches, IDisposable { BehaviorId Id { get; } Task SendAsync(TEvent @event) where TEvent : Event, new(); + Task> SendCompoundAsync(params Event[] events) + => RequestAsync(new CompoundRequest() { Events = events }); + Task> RequestAsync(Request request) where TResponse : Response, new(); Task> InitializeAsync(InitializationRequest initializationRequest = null) => RequestAsync(initializationRequest ?? new InitializationRequest()); + + Task> FinalizeAsync() + => RequestAsync(new FinalizationRequest()); + + Task> ResetAsync(bool keepVersion = false) + => RequestAsync(new ResetRequest() { KeepVersion = keepVersion }); + + async Task> ReinitializeAsync(InitializationRequest initializationRequest = null, bool keepVersion = true) + { + initializationRequest ??= new InitializationRequest(); + var compoundResult = await SendCompoundAsync( + new ResetRequest() { KeepVersion = keepVersion }, + initializationRequest + ); + + var result = compoundResult.Response.Results.Last(); + + return new RequestResult(initializationRequest, result.Status, result.Validation); + } + + Task> ReinitializeAsync(TInitializationPayload payload, bool keepVersion = true) + => ReinitializeAsync(payload.ToInitializationRequest(), keepVersion); + + Task> GetStatusAsync() + => RequestAsync(new BehaviorStatusRequest()); + + Task WatchStatusAsync(Action handler) + => WatchAsync(handler); + + Task UnwatchStatusAsync() + => UnwatchAsync(); } } \ No newline at end of file diff --git a/Core/Stateflows.Common/Interfaces/ISubscriptions.cs b/Core/Stateflows.Common/Interfaces/ISubscriptions.cs new file mode 100644 index 00000000..f5b511c4 --- /dev/null +++ b/Core/Stateflows.Common/Interfaces/ISubscriptions.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Stateflows.Common +{ + public interface ISubscriptions + { + Task> SubscribeAsync(BehaviorId behaviorId) + where TNotification : Notification, new(); + + Task> UnsubscribeAsync(BehaviorId behaviorId) + where TNotification : Notification, new(); + } +} \ No newline at end of file diff --git a/Core/Stateflows.Common/Interfaces/IWatches.cs b/Core/Stateflows.Common/Interfaces/IWatches.cs new file mode 100644 index 00000000..0c00995a --- /dev/null +++ b/Core/Stateflows.Common/Interfaces/IWatches.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; + +namespace Stateflows.Common.Interfaces +{ + public interface IWatches + { + Task WatchAsync(Action handler) + where TNotification : Notification, new(); + + Task UnwatchAsync() + where TNotification : Notification, new(); + } +} diff --git a/Core/Stateflows.Common/Locator/BehaviorProxy.cs b/Core/Stateflows.Common/Locator/BehaviorProxy.cs index 92f681b5..16ccfae3 100644 --- a/Core/Stateflows.Common/Locator/BehaviorProxy.cs +++ b/Core/Stateflows.Common/Locator/BehaviorProxy.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Stateflows.Common.Engine; namespace Stateflows.Common.Locator @@ -40,5 +41,25 @@ public async Task> RequestAsync(Request(Action handler) + where TNotification : Notification, new() + => Behavior.WatchAsync(handler); + + public Task UnwatchAsync() + where TNotification : Notification, new() + => Behavior.UnwatchAsync(); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + => Behavior.Dispose(); + + ~BehaviorProxy() + => Dispose(false); } } diff --git a/Core/Stateflows.Common/StateMachines/Classes/StateMachineWrapper.cs b/Core/Stateflows.Common/StateMachines/Classes/StateMachineWrapper.cs index 8429ce55..3d029187 100644 --- a/Core/Stateflows.Common/StateMachines/Classes/StateMachineWrapper.cs +++ b/Core/Stateflows.Common/StateMachines/Classes/StateMachineWrapper.cs @@ -1,6 +1,6 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Stateflows.StateMachines; -using Stateflows.StateMachines.Events; namespace Stateflows.Common.StateMachines.Classes { @@ -15,9 +15,6 @@ public StateMachineWrapper(IBehavior consumer) Behavior = consumer; } - public Task> GetCurrentStateAsync() - => RequestAsync(new CurrentStateRequest()); - public Task SendAsync(TEvent @event) where TEvent : Event, new() => Behavior.SendAsync(@event); @@ -25,5 +22,25 @@ public Task SendAsync(TEvent @event) public Task> RequestAsync(Request request) where TResponse : Response, new() => Behavior.RequestAsync(request); + + public Task WatchAsync(Action handler) + where TNotification : Notification, new() + => Behavior.WatchAsync(handler); + + public Task UnwatchAsync() + where TNotification : Notification, new() + => Behavior.UnwatchAsync(); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + => Behavior.Dispose(); + + ~StateMachineWrapper() + => Dispose(false); } } diff --git a/Core/Stateflows.Common/StateMachines/Events/CurrentStateNotification.cs b/Core/Stateflows.Common/StateMachines/Events/CurrentStateNotification.cs new file mode 100644 index 00000000..4f2c42ba --- /dev/null +++ b/Core/Stateflows.Common/StateMachines/Events/CurrentStateNotification.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; +using Stateflows.Common; +using System.Collections.Generic; + +namespace Stateflows.StateMachines.Events +{ + public sealed class CurrentStateNotification : BehaviorStatusNotification + { + [JsonProperty(TypeNameHandling = TypeNameHandling.None)] + public IEnumerable StatesStack { get; set; } + } +} diff --git a/Core/Stateflows.Common/StateMachines/Events/CurrentStateResponse.cs b/Core/Stateflows.Common/StateMachines/Events/CurrentStateResponse.cs index 52e3ef7c..6286b278 100644 --- a/Core/Stateflows.Common/StateMachines/Events/CurrentStateResponse.cs +++ b/Core/Stateflows.Common/StateMachines/Events/CurrentStateResponse.cs @@ -8,8 +8,5 @@ public sealed class CurrentStateResponse : BehaviorStatusResponse { [JsonProperty(TypeNameHandling = TypeNameHandling.None)] public IEnumerable StatesStack { get; set; } - - [JsonProperty(TypeNameHandling = TypeNameHandling.None)] - public IEnumerable ExpectedEvents { get; set; } } } diff --git a/Core/Stateflows.Common/StateMachines/Interfaces/IStateMachine.cs b/Core/Stateflows.Common/StateMachines/Interfaces/IStateMachine.cs index 77450106..d8d9876a 100644 --- a/Core/Stateflows.Common/StateMachines/Interfaces/IStateMachine.cs +++ b/Core/Stateflows.Common/StateMachines/Interfaces/IStateMachine.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Stateflows.Common; using Stateflows.StateMachines.Events; @@ -6,6 +7,13 @@ namespace Stateflows.StateMachines { public interface IStateMachine : IBehavior { - Task> GetCurrentStateAsync(); + Task> GetCurrentStateAsync() + => RequestAsync(new CurrentStateRequest()); + + Task WatchCurrentStateAsync(Action handler) + => WatchAsync(handler); + + Task UnwatchCurrentStateAsync() + => UnwatchAsync(); } } diff --git a/Core/Stateflows.Common/StateMachines/Interfaces/IStateMachineLocator.cs b/Core/Stateflows.Common/StateMachines/Interfaces/IStateMachineLocator.cs index bab0682f..39bd1212 100644 --- a/Core/Stateflows.Common/StateMachines/Interfaces/IStateMachineLocator.cs +++ b/Core/Stateflows.Common/StateMachines/Interfaces/IStateMachineLocator.cs @@ -1,7 +1,4 @@ -using System; -using System.Threading.Tasks; - -namespace Stateflows.StateMachines +namespace Stateflows.StateMachines { public interface IStateMachineLocator { diff --git a/Core/Stateflows.Common/Utilities/BehaviorExtensions.cs b/Core/Stateflows.Common/Utilities/BehaviorExtensions.cs index b4cb6d25..10198d61 100644 --- a/Core/Stateflows.Common/Utilities/BehaviorExtensions.cs +++ b/Core/Stateflows.Common/Utilities/BehaviorExtensions.cs @@ -1,13 +1,46 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using Stateflows.Common.Data; +using Stateflows.StateMachines.Events; +using System; namespace Stateflows.Common { public static class BehaviorExtensions { - public static Task> ResetAsync(this IBehavior behavior) - => behavior.RequestAsync(new ResetRequest()); + // public static Task> ResetAsync(this IBehavior behavior, bool keepVersion = false) + // => behavior.RequestAsync(new ResetRequest() { KeepVersion = keepVersion }); - public static Task> GetStatusAsync(this IBehavior behavior) - => behavior.RequestAsync(new BehaviorStatusRequest()); + // public static async Task> ReinitializeAsync(this IBehavior behavior, InitializationRequest initializationRequest = null, bool keepVersion = true) + // { + // initializationRequest ??= new InitializationRequest(); + // var compoundResult = await behavior.RequestAsync( + // new CompoundRequest() + // { + // Events = new List() + // { + // new ResetRequest() { KeepVersion = keepVersion }, + // initializationRequest + // } + // } + // ); + + // var result = compoundResult.Response.Results.Last(); + + // return new RequestResult(initializationRequest, result.Status, result.Validation); + // } + + // public static Task> ReinitializeAsync(this IBehavior behavior, TInitializationPayload payload, bool keepVersion = true) + // => behavior.ReinitializeAsync(payload.ToInitializationRequest(), keepVersion); + + // public static Task> GetStatusAsync(this IBehavior behavior) + // => behavior.RequestAsync(new BehaviorStatusRequest()); + + // public static Task WatchStatusAsync(this IBehavior behavior, Action handler) + // => behavior.WatchAsync(handler); + + // public static Task UnwatchStatusAsync(this IBehavior behavior) + // => behavior.UnwatchAsync(); } } \ No newline at end of file diff --git a/Core/Stateflows.Common/Utilities/StateflowsJsonConverter.cs b/Core/Stateflows.Common/Utilities/StateflowsJsonConverter.cs index 4be5c216..acd1c0a0 100644 --- a/Core/Stateflows.Common/Utilities/StateflowsJsonConverter.cs +++ b/Core/Stateflows.Common/Utilities/StateflowsJsonConverter.cs @@ -1,17 +1,31 @@ using System; using System.Diagnostics; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; namespace Stateflows.Common.Utilities { public static class StateflowsJsonConverter { + private static JsonSerializerSettings polymorphicCamelSettings = new JsonSerializerSettings() + { + TypeNameHandling = TypeNameHandling.All, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + private static JsonSerializerSettings polymorphicSettings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All, ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; + private static JsonSerializerSettings camelSettings = new JsonSerializerSettings() + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + private static JsonSerializerSettings settings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore @@ -28,10 +42,10 @@ public static T CloneObject(T value) /// The object to serialize. /// A JSON string representation of the object. [DebuggerStepThrough] - public static string SerializePolymorphicObject(object value, Formatting formatting = Formatting.None) + public static string SerializePolymorphicObject(object value, bool useCamelCase = false, Formatting formatting = Formatting.None) { settings.Formatting = formatting; - return JsonConvert.SerializeObject(value, null, polymorphicSettings); + return JsonConvert.SerializeObject(value, null, useCamelCase ? polymorphicCamelSettings : polymorphicSettings); } /// @@ -40,10 +54,10 @@ public static string SerializePolymorphicObject(object value, Formatting formatt /// The object to serialize. /// A JSON string representation of the object. [DebuggerStepThrough] - public static string SerializeObject(object value, Formatting formatting = Formatting.None) + public static string SerializeObject(object value, bool useCamelCase = false, Formatting formatting = Formatting.None) { settings.Formatting = formatting; - return JsonConvert.SerializeObject(value, null, settings); + return JsonConvert.SerializeObject(value, null, useCamelCase ? camelSettings : settings); } /// diff --git a/Core/Stateflows.Testing/StateMachines/Sequence/ExecutionSequence.cs b/Core/Stateflows.Testing/StateMachines/Sequence/ExecutionSequence.cs index a74ef90e..b0ac8087 100644 --- a/Core/Stateflows.Testing/StateMachines/Sequence/ExecutionSequence.cs +++ b/Core/Stateflows.Testing/StateMachines/Sequence/ExecutionSequence.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Stateflows.Common; using Stateflows.Common.Exceptions; using Stateflows.StateMachines.Events; diff --git a/Core/Stateflows.Testing/StateflowsTestClass.cs b/Core/Stateflows.Testing/StateflowsTestClass.cs index 314cdcc7..54d4897c 100644 --- a/Core/Stateflows.Testing/StateflowsTestClass.cs +++ b/Core/Stateflows.Testing/StateflowsTestClass.cs @@ -10,6 +10,7 @@ using Stateflows.Common.Registration.Interfaces; using Stateflows.Testing.StateMachines.Sequence; using Microsoft.Extensions.Logging; +using Stateflows.Activities; namespace StateMachine.IntegrationTests.Utils { @@ -21,8 +22,10 @@ public abstract class StateflowsTestClass private IServiceProvider _serviceProvider; protected IServiceProvider ServiceProvider => _serviceProvider ?? (_serviceProvider = ServiceCollection.BuildServiceProvider()); - protected IStateMachineLocator Locator => ServiceProvider.GetRequiredService(); - + protected IStateMachineLocator StateMachineLocator => ServiceProvider.GetRequiredService(); + + protected IActivityLocator ActivityLocator => ServiceProvider.GetRequiredService(); + protected ExecutionSequenceObserver ExecutionSequence => ServiceProvider.GetRequiredService(); public virtual void Initialize() diff --git a/Core/Stateflows.Transport.Common/Classes/NotificationTarget.cs b/Core/Stateflows.Transport.Common/Classes/NotificationTarget.cs new file mode 100644 index 00000000..1325171d --- /dev/null +++ b/Core/Stateflows.Transport.Common/Classes/NotificationTarget.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Stateflows.Common.Transport.Classes +{ + public class NotificationTarget + { + public BehaviorId Id { get; set; } + + [JsonProperty(TypeNameHandling = TypeNameHandling.None)] + public IEnumerable Watches { get; set; } + } +} diff --git a/Core/Stateflows.Transport.Common/Classes/StateflowsRequest.cs b/Core/Stateflows.Transport.Common/Classes/StateflowsRequest.cs index f8aad28d..35c4b9fc 100644 --- a/Core/Stateflows.Transport.Common/Classes/StateflowsRequest.cs +++ b/Core/Stateflows.Transport.Common/Classes/StateflowsRequest.cs @@ -1,5 +1,5 @@ -using Stateflows.Common.Utilities; -using System.Text.Json.Serialization; +using Newtonsoft.Json; +using System.Collections.Generic; namespace Stateflows.Common.Transport.Classes { @@ -7,24 +7,9 @@ public class StateflowsRequest { public Event Event { get; set; } - //public string EventString { get; set; } - - //[JsonIgnore] - //public Event Event - //{ - // get => StateflowsJsonConverter.DeserializeObject(EventString); - // set => EventString = StateflowsJsonConverter.SerializePolymorphicObject(value); - //} - public BehaviorId BehaviorId { get; set; } - //public string BehaviorIdString { get; set; } - - //[JsonIgnore] - //public BehaviorId BehaviorId - //{ - // get => StateflowsJsonConverter.DeserializeObject(BehaviorIdString); - // set => BehaviorIdString = StateflowsJsonConverter.SerializePolymorphicObject(value); - //} + [JsonProperty(TypeNameHandling = TypeNameHandling.None)] + public IEnumerable Watches { get; set; } } } diff --git a/Core/Stateflows.Transport.Common/Classes/StateflowsRequestConverter.cs b/Core/Stateflows.Transport.Common/Classes/StateflowsRequestConverter.cs deleted file mode 100644 index 5c41e7b7..00000000 --- a/Core/Stateflows.Transport.Common/Classes/StateflowsRequestConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Stateflows.Common.Utilities; -using System; -using System.Collections.Generic; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Stateflows.Common.Transport.Classes -{ - public class StateflowsRequestConverter : JsonConverter - { - public override StateflowsRequest Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - using var jsonDocument = JsonDocument.ParseValue(ref reader); - var jsonText = jsonDocument.RootElement.GetRawText(); - - return StateflowsJsonConverter.DeserializeObject(jsonText); - } - - public override void Write(Utf8JsonWriter writer, StateflowsRequest value, JsonSerializerOptions options) - { - } - } -} diff --git a/Core/Stateflows.Transport.Common/Classes/StateflowsResponse.cs b/Core/Stateflows.Transport.Common/Classes/StateflowsResponse.cs index 965468be..51cd4dba 100644 --- a/Core/Stateflows.Transport.Common/Classes/StateflowsResponse.cs +++ b/Core/Stateflows.Transport.Common/Classes/StateflowsResponse.cs @@ -1,5 +1,6 @@ -using Stateflows.Common.Utilities; -using System.Text.Json.Serialization; +using System; +using System.Collections.Generic; +using Newtonsoft.Json; namespace Stateflows.Common.Transport.Classes { @@ -9,24 +10,11 @@ public class StateflowsResponse public EventValidation Validation { get; set; } - //public string ValidationString { get; set; } - - //[JsonIgnore] - //public EventValidation Validation - //{ - // get => StateflowsJsonConverter.DeserializeObject(ValidationString); - // set => ValidationString = StateflowsJsonConverter.SerializePolymorphicObject(value); - //} - public Response Response { get; set; } - //public string ResponseString { get; set; } + public DateTime ResponseTime { get; set; } - //[JsonIgnore] - //public Response Response - //{ - // get => StateflowsJsonConverter.DeserializeObject(ResponseString); - // set => ResponseString = StateflowsJsonConverter.SerializePolymorphicObject(value); - //} + [JsonProperty(TypeNameHandling = TypeNameHandling.None)] + public IEnumerable Notifications { get; set; } = Array.Empty(); } } diff --git a/Core/Stateflows.Transport.Common/Classes/Watch.cs b/Core/Stateflows.Transport.Common/Classes/Watch.cs new file mode 100644 index 00000000..20e1af6a --- /dev/null +++ b/Core/Stateflows.Transport.Common/Classes/Watch.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Stateflows.Common.Transport.Classes +{ + public class Watch + { + public string NotificationName { get; set; } + + public DateTime? LastNotificationCheck { get; set; } + + public int? MilisecondsSinceLastNotificationCheck { get; set; } + + [JsonIgnore] + public List> Handlers { get; } = new List>(); + + [JsonIgnore] + public List Notifications { get; }= new List(); + } +} diff --git a/Core/Stateflows.Transport.Common/Extensions/NotificationsHubExtensions.cs b/Core/Stateflows.Transport.Common/Extensions/NotificationsHubExtensions.cs new file mode 100644 index 00000000..3bd5d009 --- /dev/null +++ b/Core/Stateflows.Transport.Common/Extensions/NotificationsHubExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using Stateflows.Common.Transport.Classes; + +namespace Stateflows.Common.Interfaces +{ + public static class NotificationsHubExtensions + { + public static Notification[] GetPendingNotifications(this Dictionary> notifications, BehaviorId behaviorId, IEnumerable watches) + { + lock (notifications) + { + var result = notifications.TryGetValue(behaviorId, out var behaviorNotifications) + ? behaviorNotifications + .Where(notification => + watches?.Any(watch => + watch.NotificationName == notification.Name && + ( + ( + watch.LastNotificationCheck != null && + notification.SentAt >= watch.LastNotificationCheck + ) || + ( + watch.MilisecondsSinceLastNotificationCheck != null && + notification.SentAt >= DateTime.Now.AddMilliseconds(- (int)watch.MilisecondsSinceLastNotificationCheck) + ) + ) + ) ?? false + ) + .ToArray() + : Array.Empty(); + + return result; + } + } + } +} diff --git a/Core/Stateflows.Transport.Common/Interfaces/INotificationTarget.cs b/Core/Stateflows.Transport.Common/Interfaces/INotificationTarget.cs new file mode 100644 index 00000000..b5643d75 --- /dev/null +++ b/Core/Stateflows.Transport.Common/Interfaces/INotificationTarget.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Stateflows.Common.Transport.Classes; + +namespace Stateflows.Common.Transport.Interfaces +{ + public interface INotificationTarget + { + [JsonProperty(TypeNameHandling = TypeNameHandling.None)] + IEnumerable Watches { get; } + + BehaviorId Id { get; } + + } +} diff --git a/Core/Stateflows/Activities/ActivitiesDependencyInjection.cs b/Core/Stateflows/Activities/ActivitiesDependencyInjection.cs index 203767e6..bf6952d4 100644 --- a/Core/Stateflows/Activities/ActivitiesDependencyInjection.cs +++ b/Core/Stateflows/Activities/ActivitiesDependencyInjection.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Stateflows.Common.Interfaces; using Stateflows.Common.Initializer; @@ -13,7 +14,7 @@ namespace Stateflows.Activities { public static class ActivitiesDependencyInjection { - private static ActivitiesRegister Register; + private readonly static Dictionary Registers = new Dictionary(); [DebuggerHidden] public static IStateflowsBuilder AddActivities(this IStateflowsBuilder stateflowsBuilder, ActivitiesBuildAction buildAction) @@ -24,33 +25,38 @@ public static IStateflowsBuilder AddActivities(this IStateflowsBuilder stateflow } [DebuggerHidden] - public static IStateflowsBuilder AddDefaultInstance(this IStateflowsBuilder stateflowsBuilder, InitializationRequestFactoryAsync initializationRequestFactoryAsync = null) + public static IStateflowsBuilder AddDefaultInstance(this IStateflowsBuilder stateflowsBuilder, DefaultInstanceInitializationRequestFactoryAsync initializationRequestFactoryAsync = null) where TActivity: Activity => stateflowsBuilder.AddDefaultInstance(new ActivityClass(ActivityInfo.Name).BehaviorClass, initializationRequestFactoryAsync); private static ActivitiesRegister EnsureActivitiesServices(this IStateflowsBuilder stateflowsBuilder) { - if (Register == null) + if (!Registers.TryGetValue(stateflowsBuilder, out var register)) { - Register = new ActivitiesRegister(stateflowsBuilder.ServiceCollection); + register = new ActivitiesRegister(stateflowsBuilder.ServiceCollection); + Registers.Add(stateflowsBuilder, register); stateflowsBuilder .EnsureStateflowServices() .ServiceCollection .AddScoped() .AddScoped(serviceProvider => serviceProvider.GetRequiredService()) - .AddSingleton(Register) + .AddSingleton(register) .AddScoped() .AddTransient() + .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() ; } - return Register; + return register; } } } diff --git a/Core/Stateflows/Activities/Classes/ActivityFinal.cs b/Core/Stateflows/Activities/Classes/ActivityFinal.cs index a20d454d..cf44a251 100644 --- a/Core/Stateflows/Activities/Classes/ActivityFinal.cs +++ b/Core/Stateflows/Activities/Classes/ActivityFinal.cs @@ -1,7 +1,9 @@ -namespace Stateflows.Activities +using Stateflows.Common.Extensions; + +namespace Stateflows.Activities { public abstract class ActivityFinal : ActivityNode { - public const string Name = "Stateflows.Activities.ActivityFinal"; + public static string Name => typeof(ActivityFinal).GetReadableName(); } } diff --git a/Core/Stateflows/Activities/Classes/DecisionNode.cs b/Core/Stateflows/Activities/Classes/DecisionNode.cs index 6bc6573a..d979ae2e 100644 --- a/Core/Stateflows/Activities/Classes/DecisionNode.cs +++ b/Core/Stateflows/Activities/Classes/DecisionNode.cs @@ -1,10 +1,11 @@ using Stateflows.Common; +using Stateflows.Common.Extensions; namespace Stateflows.Activities { public sealed class DecisionNode : ActivityNode where TToken : Token, new() { - public string Name => $"Stateflows.Activities.DecisionNode<{TokenInfo.Name}>"; + public static string Name => typeof(DecisionNode).GetReadableName(); } } diff --git a/Core/Stateflows/Activities/Classes/FinalNode.cs b/Core/Stateflows/Activities/Classes/FinalNode.cs index bcf75e15..24c31844 100644 --- a/Core/Stateflows/Activities/Classes/FinalNode.cs +++ b/Core/Stateflows/Activities/Classes/FinalNode.cs @@ -1,7 +1,9 @@ -namespace Stateflows.Activities +using Stateflows.Common.Extensions; + +namespace Stateflows.Activities { public sealed class FinalNode : ActivityNode { - public const string Name = "Stateflows.Activities.FinalNode"; + public static string Name => typeof(FinalNode).GetReadableName(); } } diff --git a/Core/Stateflows/Activities/Classes/Flow.cs b/Core/Stateflows/Activities/Classes/Flow.cs index 3e73c4fa..3adc731b 100644 --- a/Core/Stateflows/Activities/Classes/Flow.cs +++ b/Core/Stateflows/Activities/Classes/Flow.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; -using Stateflows.Activities.Context.Interfaces; using Stateflows.Common; using Stateflows.Common.Data; +using Stateflows.Activities.Context.Interfaces; namespace Stateflows.Activities { @@ -21,7 +21,8 @@ public abstract class BaseFlow : BaseControlFlow { public virtual int Weight => 1; - new public IFlowContext Context { get; internal set; } + new public IFlowContext Context + => (IFlowContext)base.Context; } public abstract class Flow : BaseFlow @@ -37,7 +38,8 @@ public abstract class BaseTransformationFlow : BaseCo { public virtual int Weight => 1; - new public IFlowContext Context { get; internal set; } + new public IFlowContext Context + => (IFlowContext)base.Context; public abstract Task TransformAsync(); } diff --git a/Core/Stateflows/Activities/Classes/ForkNode.cs b/Core/Stateflows/Activities/Classes/ForkNode.cs index 06ac7e39..d790774b 100644 --- a/Core/Stateflows/Activities/Classes/ForkNode.cs +++ b/Core/Stateflows/Activities/Classes/ForkNode.cs @@ -1,7 +1,9 @@ -namespace Stateflows.Activities +using Stateflows.Common.Extensions; + +namespace Stateflows.Activities { public sealed class ForkNode : ActivityNode { - public const string Name = "Stateflows.Activities.ForkNode"; + public static string Name => typeof(ForkNode).GetReadableName(); } } diff --git a/Core/Stateflows/Activities/Classes/IterativeActivityNode.cs b/Core/Stateflows/Activities/Classes/IterativeActivityNode.cs index 0d5a0549..d56191d9 100644 --- a/Core/Stateflows/Activities/Classes/IterativeActivityNode.cs +++ b/Core/Stateflows/Activities/Classes/IterativeActivityNode.cs @@ -1,10 +1,11 @@ using Stateflows.Common; +using Stateflows.Common.Extensions; namespace Stateflows.Activities { public abstract class IterativeActivityNode : StructuredActivityNode where TToken : Token, new() { - public string Name => $"Stateflows.Activities.IterativeActivityNode<{TokenInfo.Name}>"; + public static string Name => typeof(IterativeActivityNode).GetReadableName(); } } diff --git a/Core/Stateflows/Activities/Classes/JoinNode.cs b/Core/Stateflows/Activities/Classes/JoinNode.cs index 17de7919..0b436578 100644 --- a/Core/Stateflows/Activities/Classes/JoinNode.cs +++ b/Core/Stateflows/Activities/Classes/JoinNode.cs @@ -1,7 +1,9 @@ -namespace Stateflows.Activities +using Stateflows.Common.Extensions; + +namespace Stateflows.Activities { public sealed class JoinNode : ActivityNode { - public const string Name = "Stateflows.Activities.JoinNode"; + public static string Name => typeof(JoinNode).GetReadableName(); } } diff --git a/Core/Stateflows/Activities/Classes/MergeNode.cs b/Core/Stateflows/Activities/Classes/MergeNode.cs index a22ebdc0..c0f2a082 100644 --- a/Core/Stateflows/Activities/Classes/MergeNode.cs +++ b/Core/Stateflows/Activities/Classes/MergeNode.cs @@ -1,7 +1,9 @@ -namespace Stateflows.Activities +using Stateflows.Common.Extensions; + +namespace Stateflows.Activities { public sealed class MergeNode : ActivityNode { - public const string Name = "Stateflows.Activities.MergeNode"; + public static string Name => typeof(MergeNode).GetReadableName(); } } diff --git a/Core/Stateflows/Activities/Classes/OutputNode.cs b/Core/Stateflows/Activities/Classes/OutputNode.cs index 40fe057f..944f3481 100644 --- a/Core/Stateflows/Activities/Classes/OutputNode.cs +++ b/Core/Stateflows/Activities/Classes/OutputNode.cs @@ -1,7 +1,9 @@ -namespace Stateflows.Activities +using Stateflows.Common.Extensions; + +namespace Stateflows.Activities { public sealed class OutputNode : ActivityNode { - public const string Name = "Stateflows.Activities.OutputNode"; + public static string Name => typeof(OutputNode).GetReadableName(); } } diff --git a/Core/Stateflows/Activities/Classes/ParalellActivityNode.cs b/Core/Stateflows/Activities/Classes/ParalellActivityNode.cs index ab8bce4b..6edcf377 100644 --- a/Core/Stateflows/Activities/Classes/ParalellActivityNode.cs +++ b/Core/Stateflows/Activities/Classes/ParalellActivityNode.cs @@ -1,10 +1,11 @@ using Stateflows.Common; +using Stateflows.Common.Extensions; namespace Stateflows.Activities { public abstract class ParallelActivityNode : StructuredActivityNode where TToken : Token, new() { - public string Name => $"Stateflows.Activities.ParallelActivityNode<{TokenInfo.Name}>"; + public static string Name => typeof(ParallelActivityNode).GetReadableName(); } } diff --git a/Core/Stateflows/Activities/Context/Classes/ActionContext.cs b/Core/Stateflows/Activities/Context/Classes/ActionContext.cs index c83d13a0..cddbb3e9 100644 --- a/Core/Stateflows/Activities/Context/Classes/ActionContext.cs +++ b/Core/Stateflows/Activities/Context/Classes/ActionContext.cs @@ -10,7 +10,7 @@ namespace Stateflows.Activities.Context.Classes { internal class ActionContext : ActivityNodeContext, IActionContext, IActivityNodeInspectionContext { - public ActionContext(BaseContext context, Node node, IEnumerable inputTokens = null, Token selectionToken = null) + public ActionContext(BaseContext context, Node node, IEnumerable inputTokens = null, IEnumerable selectionTokens = null) : base(context, node) { if (inputTokens != null) @@ -18,20 +18,20 @@ public ActionContext(BaseContext context, Node node, IEnumerable inputTok InputTokens.AddRange(inputTokens); } - if (selectionToken != null) + if (selectionTokens != null) { - Token = selectionToken; + Tokens = selectionTokens; } } - public ActionContext(RootContext context, NodeScope nodeScope, Node node, IEnumerable inputTokens, Token selectionToken = null) + public ActionContext(RootContext context, NodeScope nodeScope, Node node, IEnumerable inputTokens, IEnumerable selectionTokens = null) : base(context, nodeScope, node) { InputTokens.AddRange(inputTokens); - if (selectionToken != null) + if (selectionTokens != null) { - Token = selectionToken; + Tokens = selectionTokens; } } @@ -66,6 +66,6 @@ public void PassAllOn() public IEnumerable Input => InputTokens; - public Token Token { get; } + public IEnumerable Tokens { get; } } } \ No newline at end of file diff --git a/Core/Stateflows/Activities/Context/Classes/ActivityContext.cs b/Core/Stateflows/Activities/Context/Classes/ActivityContext.cs index 7019a8b2..6a9393f7 100644 --- a/Core/Stateflows/Activities/Context/Classes/ActivityContext.cs +++ b/Core/Stateflows/Activities/Context/Classes/ActivityContext.cs @@ -1,15 +1,27 @@ -using Stateflows.Common; +using System.Threading.Tasks; +using Stateflows.Common; using Stateflows.Common.Classes; using Stateflows.Common.Interfaces; +using Stateflows.Common.Subscription; +using Stateflows.Common.Context.Interfaces; using Stateflows.Activities.Engine; using Stateflows.Activities.Inspection.Interfaces; +using Microsoft.Extensions.DependencyInjection; namespace Stateflows.Activities.Context.Classes { internal class ActivityContext : BaseContext, IActivityInspectionContext { + BehaviorId IBehaviorContext.Id => Context.Id; + + public object LockHandle => Context; + public ActivityId Id => Context.Id; + private BehaviorSubscriber subscriber; + private BehaviorSubscriber Subscriber + => subscriber ??= new BehaviorSubscriber(Id, Context.Context, this, ServiceProvider.GetRequiredService()); + public ActivityContext(RootContext context, NodeScope nodeScope) : base(context, nodeScope) { @@ -23,5 +35,17 @@ public ActivityContext(RootContext context, NodeScope nodeScope) public void Send(TEvent @event) where TEvent : Event, new() => _ = Context.Send(@event); + + public void Publish(TNotification notification) + where TNotification : Notification, new() + => _ = Subscriber.PublishAsync(Id, notification); + + public Task> SubscribeAsync(BehaviorId behaviorId) + where TNotification : Notification, new() + => Subscriber.SubscribeAsync(behaviorId); + + public Task> UnsubscribeAsync(BehaviorId behaviorId) + where TNotification : Notification, new() + => Subscriber.UnsubscribeAsync(behaviorId); } } diff --git a/Core/Stateflows/Activities/Context/Classes/BaseContext.cs b/Core/Stateflows/Activities/Context/Classes/BaseContext.cs index b4ba4610..f52285be 100644 --- a/Core/Stateflows/Activities/Context/Classes/BaseContext.cs +++ b/Core/Stateflows/Activities/Context/Classes/BaseContext.cs @@ -2,13 +2,13 @@ using System.Threading; using Microsoft.Extensions.DependencyInjection; using Stateflows.Common; -using Stateflows.Activities.Engine; -using Stateflows.Common.Interfaces; using Stateflows.Common.Context; +using Stateflows.Common.Interfaces; +using Stateflows.Activities.Engine; namespace Stateflows.Activities.Context.Classes { - internal class BaseContext : IStateflowsContextProvider + internal class BaseContext : IStateflowsContextProvider, IBehaviorLocator { public BaseContext(RootContext context, NodeScope nodeScope) { diff --git a/Core/Stateflows/Activities/Context/Interfaces/IActivityContext.cs b/Core/Stateflows/Activities/Context/Interfaces/IActivityContext.cs index e27408e2..33d2a42d 100644 --- a/Core/Stateflows/Activities/Context/Interfaces/IActivityContext.cs +++ b/Core/Stateflows/Activities/Context/Interfaces/IActivityContext.cs @@ -1,15 +1,11 @@ -using Stateflows.Common; -using Stateflows.Common.Interfaces; +using Stateflows.Common.Context.Interfaces; namespace Stateflows.Activities.Context.Interfaces { - public interface IActivityContext + public interface IActivityContext : IBehaviorContext { - ActivityId Id { get; } + new ActivityId Id { get; } - IContextValues Values { get; } - - void Send(TEvent @event) - where TEvent : Event, new(); + object LockHandle { get; } } } diff --git a/Core/Stateflows/Activities/Context/Interfaces/ITokenContext.cs b/Core/Stateflows/Activities/Context/Interfaces/ITokenContext.cs index f387d600..5c0702ba 100644 --- a/Core/Stateflows/Activities/Context/Interfaces/ITokenContext.cs +++ b/Core/Stateflows/Activities/Context/Interfaces/ITokenContext.cs @@ -1,10 +1,11 @@ using Stateflows.Common; +using System.Collections.Generic; namespace Stateflows.Activities.Context.Interfaces { public interface ITokenContext : IActivityActionContext where TToken : Token, new() { - TToken Token { get; } + IEnumerable Tokens { get; } } } diff --git a/Core/Stateflows/Activities/Engine/Executor.cs b/Core/Stateflows/Activities/Engine/Executor.cs index 2af1e634..af5c3016 100644 --- a/Core/Stateflows/Activities/Engine/Executor.cs +++ b/Core/Stateflows/Activities/Engine/Executor.cs @@ -12,6 +12,7 @@ using Stateflows.Activities.Streams; using Stateflows.Activities.Registration; using Stateflows.Activities.Context.Classes; +using Stateflows.Utils; namespace Stateflows.Activities.Engine { @@ -70,14 +71,18 @@ public IEnumerable GetActiveNodes() .OrderByDescending(node => node.Level) .ToArray(); - public async Task HydrateAsync(RootContext context) + public IEnumerable GetExpectedEvents() + => GetActiveNodes() + .Select(node => node.EventType) + .Distinct() + .ToArray(); + + public Task HydrateAsync(RootContext context) { Context = context; Context.Executor = this; - await Inspector.AfterHydrateAsync(new ActivityActionContext(Context, NodeScope.CreateChildScope())); - - return true; + return Inspector.AfterHydrateAsync(new ActivityActionContext(Context, NodeScope.CreateChildScope())); } public async Task DehydrateAsync() @@ -122,7 +127,7 @@ public async Task InitializeAsync(InitializationRequest @event) { Context.Initialized = true; - Context.ClearEvent(); + //Context.ClearEvent(); await ExecuteGraphAsync(); @@ -157,14 +162,17 @@ public async Task CancelAsync() return false; } - public void Reset() + public void Reset(bool keepVersion) { Debug.Assert(Context != null, $"Context is unavailable. Is state machine '{Graph.Name}' hydrated?"); if (Initialized) { Context.Context.Values.Clear(); - Context.Context.Version = 0; + if (!keepVersion) + { + Context.Context.Version = 0; + } } } @@ -260,25 +268,50 @@ public async Task DoFinalizeAsync(IEnumerable outputTokens = null) public async Task<(IEnumerable Output, bool Finalized)> DoExecuteStructuredNodeAsync(Node node, NodeScope nodeScope, IEnumerable input = null) { - if (Context.NodeThreads.TryGetValue(node.Identifier, out var storedThreadId)) - { - nodeScope.ThreadId = storedThreadId; - } - else + if (node.Anchored) { - Context.NodeThreads.Add(node.Identifier, nodeScope.ThreadId); + if (Context.NodeThreads.TryGetValue(node.Identifier, out var storedThreadId)) + { + nodeScope.ThreadId = storedThreadId; + } + else + { + Context.NodeThreads.Add(node.Identifier, nodeScope.ThreadId); + } } - var finalized = await DoExecuteNodeStructureAsync( + var finalized = await DoExecuteStructureAsync( node, nodeScope, input ); + //lock (Context.Streams) + //{ + // if ( + // node.OutputNode != null && + // Context.OutputTokens.TryGetValue(nodeScope.ThreadId, out var nodesOutputTokens) && + // nodesOutputTokens.TryGetValue(node.OutputNode.Identifier, out var tokens) + // ) + // { + // outputTokens.AddRange(Context.GetOutputTokens(node.OutputNode.Identifier, nodeScope.ThreadId)); + // } + + // Context.Streams.Remove(nodeScope.ThreadId); + // Context.OutputTokens.Remove(nodeScope.ThreadId); + //} + + var output = node.OutputNode != null + ? Context.GetOutputTokens(node.OutputNode.Identifier, nodeScope.ThreadId) + : new List(); + + if (node.ExceptionHandlers.Any()) + { + output.AddRange(node.ExceptionHandlers.SelectMany(exceptionHandler => Context.GetOutputTokens(exceptionHandler.Identifier, nodeScope.ThreadId)).ToList()); + } + var result = ( - node.OutputNode != null - ? Context.GetOutputTokens(node.OutputNode.Identifier, nodeScope.ThreadId) - : new List(), + output, finalized ); @@ -290,36 +323,48 @@ public async Task DoFinalizeAsync(IEnumerable outputTokens = null) return result; } - public async Task> DoExecuteParallelNodeAsync(ActionContext context) + public async Task> DoExecuteParallelNodeAsync(Node node, NodeScope nodeScope, IEnumerable input = null) where TToken : Token, new() { - var restOfInput = context.InputTokens.Where(t => !(t is TToken)).ToArray(); + var restOfInput = input.Where(t => !(t is TToken)).ToArray(); - var parallelInput = context.InputTokens.OfType().ToArray(); + var parallelInput = input.OfType().Partition(node.ChunkSize).ToArray(); - var threadIds = parallelInput.ToDictionary(t => t, t => Guid.NewGuid()); + var outputTokens = new List(); await Task.WhenAll(parallelInput - .Select(async token => - { - var threadId = threadIds[token]; + .Select(tokensPartition => + Task.Run(async () => + { + var threadId = Guid.NewGuid(); - await DoExecuteNodeStructureAsync( - context.Node, - context.NodeScope.CreateChildScope(context.Node, threadId), - restOfInput.Concat(new Token[] { token }).ToArray(), - token - ); + await DoExecuteStructureAsync( + node, + nodeScope.CreateChildScope(node, threadId), + restOfInput.Concat(tokensPartition).ToArray(), + tokensPartition + ); - lock (Context.Streams) - { - Context.Streams.Remove(threadId); - } - }) + lock (Context.Streams) + { + if ( + node.OutputNode != null && + Context.OutputTokens.TryGetValue(threadId, out var nodesOutputTokens) && + nodesOutputTokens.TryGetValue(node.OutputNode.Identifier, out var tokens) + ) + { + outputTokens.AddRange(Context.GetOutputTokens(node.OutputNode.Identifier, threadId)); + } + + Context.Streams.Remove(threadId); + Context.OutputTokens.Remove(threadId); + } + }) + ).ToArray() ); - return context.Node.OutputNode != null - ? threadIds.Values.SelectMany(threadId => Context.GetOutputTokens(context.Node.OutputNode.Identifier, threadId)) + return node.OutputNode != null + ? outputTokens : new List(); } @@ -350,27 +395,39 @@ public async Task> DoExecuteIterativeNodeAsync(Action var threadId = Guid.NewGuid(); - foreach (var token in iterativeInput) + var outputTokens = new List(); + + foreach (var tokensPartition in iterativeInput.Partition(context.Node.ChunkSize)) { - await DoExecuteNodeStructureAsync( + await DoExecuteStructureAsync( context.Node, context.NodeScope.CreateChildScope(context.Node, threadId), - restOfInput.Concat(new Token[] { token }), - token + restOfInput.Concat(tokensPartition), + tokensPartition ); + if ( + context.Node.OutputNode != null && + Context.OutputTokens.TryGetValue(threadId, out var nodesOutputTokens) && + nodesOutputTokens.TryGetValue(context.Node.OutputNode.Identifier, out var iterationOutputTokens) + ) + { + outputTokens.AddRange(Context.GetOutputTokens(context.Node.OutputNode.Identifier, threadId)); + } + lock (Context.Streams) { Context.Streams.Remove(threadId); + Context.OutputTokens.Remove(threadId); } } return context.Node.OutputNode != null - ? Context.GetOutputTokens(context.Node.OutputNode.Identifier, threadId) + ? outputTokens : new List(); } - private async Task DoExecuteNodeStructureAsync(Node node, NodeScope nodeScope, IEnumerable input = null, Token selectionToken = null) + private async Task DoExecuteStructureAsync(Node node, NodeScope nodeScope, IEnumerable input = null, IEnumerable selectionTokens = null) { var startingNode = Context.NodesToExecute.Any() ? node.Nodes.Values.FirstOrDefault(n => Context.NodesToExecute.Contains(n)) @@ -392,7 +449,7 @@ private async Task DoExecuteNodeStructureAsync(Node node, NodeScope nodeSc } await Task.WhenAll( - nodes.Select(n => DoHandleNodeAsync(n, nodeScope, n == node.InputNode ? input : null, selectionToken)) + nodes.Select(n => DoHandleNodeAsync(n, nodeScope, n == node.InputNode ? input : null, selectionTokens)).ToArray() ); var result = Context.IsNodeCompleted(node, nodeScope); @@ -432,7 +489,53 @@ public async Task DoInitializeActivityAsync(InitializationRequest @event) return result; } - public async Task DoHandleNodeAsync(Node node, NodeScope nodeScope, IEnumerable input = null, Token selectionToken = null) + public async Task> HandleExceptionAsync(Node node, Exception exception, BaseContext context) + { + Node handler = null; + var currentNode = node; + while (currentNode != null) + { + handler = currentNode.ExceptionHandlers.FirstOrDefault(n => exception.GetType().IsAssignableFrom(n.ExceptionType)); + + if (handler != null) + { + break; + } + + currentNode = currentNode.Parent; + } + + var currentScope = context.NodeScope; + while (currentNode != null) + { + if (currentScope.Node == currentNode) + { + break; + } + + currentScope = currentScope.BaseNodeScope; + } + + if (handler != null) + { + var exceptionContext = new ActionContext( + context.Context, + currentScope, + handler, + new Token[] { new ExceptionToken() { Exception = exception } } + ); + + await handler.Action.WhenAll(exceptionContext); + + DoHandleOutput(exceptionContext); + + return exceptionContext.OutputTokens; + } + + return new Token[0]; + } + + public async Task DoHandleNodeAsync(Node node, NodeScope nodeScope, IEnumerable input = null, IEnumerable selectionTokens = null) { if (CancellableTypes.Contains(node.Type) && nodeScope.CancellationToken.IsCancellationRequested) { @@ -491,7 +594,7 @@ public async Task DoHandleNodeAsync(Node node, NodeScope nodeScope, IEnumerable< Debug.WriteLine($">>> Executing node {node.Name}, threadId: {nodeScope.ThreadId}"); } - var actionContext = new ActionContext(Context, nodeScope, node, inputTokens, selectionToken); + var actionContext = new ActionContext(Context, nodeScope, node, inputTokens, selectionTokens); await node.Action.WhenAll(actionContext); @@ -519,25 +622,32 @@ public async Task DoHandleNodeAsync(Node node, NodeScope nodeScope, IEnumerable< } } - if (node.Type == NodeType.Output) + if (node.Type == NodeType.Output || node.Type == NodeType.ExceptionHandler) { DoHandleOutput(actionContext); } else { - if (StructuralTypes.Contains(node.Type) && + List outputTokens; + lock (actionContext.OutputTokens) + { + outputTokens = actionContext.OutputTokens.ToList(); + } + + if ( + StructuralTypes.Contains(node.Type) && ( Context.IsNodeCompleted(node, nodeScope) || - actionContext.OutputTokens.Any() + outputTokens.Any() ) ) { - actionContext.OutputTokens.Add(new ControlToken()); + outputTokens.Add(new ControlToken()); } - if (actionContext.OutputTokens.Any(t => t is ControlToken)) + if (outputTokens.Any(t => t is ControlToken)) { - var tokenNames = actionContext.OutputTokens.Select(token => token.Name).Distinct().ToArray(); + var tokenNames = outputTokens.Select(token => token.Name).Distinct().ToArray(); var edges = node.Edges .Where(edge => tokenNames.Contains(edge.TokenType.GetTokenName()) || edge.Weight == 0) .OrderBy(edge => edge.IsElse) @@ -548,9 +658,9 @@ public async Task DoHandleNodeAsync(Node node, NodeScope nodeScope, IEnumerable< _ = await DoHandleEdgeAsync(edge, actionContext); } - var nodes = edges.Select(edge => edge.Target).Distinct(); + var nodes = edges.Select(edge => edge.Target).Distinct().ToArray(); - await Task.WhenAll(nodes.Select(n => DoHandleNodeAsync(n, nodeScope))); + await Task.WhenAll(nodes.Select(n => DoHandleNodeAsync(n, nodeScope)).ToArray()); } } diff --git a/Core/Stateflows/Activities/Engine/NodeScope.cs b/Core/Stateflows/Activities/Engine/NodeScope.cs index c0906e70..b642c670 100644 --- a/Core/Stateflows/Activities/Engine/NodeScope.cs +++ b/Core/Stateflows/Activities/Engine/NodeScope.cs @@ -9,7 +9,7 @@ namespace Stateflows.Activities.Engine { internal class NodeScope : IDisposable { - private readonly NodeScope BaseNodeScope = null; + public readonly NodeScope BaseNodeScope = null; private IServiceProvider baseServiceProvider = null; private IServiceProvider BaseServiceProvider diff --git a/Core/Stateflows/Activities/Engine/Plugins/AcceptEvents.cs b/Core/Stateflows/Activities/Engine/Plugins/AcceptEvents.cs index 776dfd65..edac37f4 100644 --- a/Core/Stateflows/Activities/Engine/Plugins/AcceptEvents.cs +++ b/Core/Stateflows/Activities/Engine/Plugins/AcceptEvents.cs @@ -156,7 +156,7 @@ Task IActivityInterceptor.AfterProcessEventAsync(IEventContext context) Task IActivityObserver.BeforeActivityInitializationAsync(IActivityInitializationContext context) { RegisterAcceptEventNodes( - (context as IRootContext).Context.Executor.Graph.DanglingTimeEventActionNodes.Select(node => (node, Guid.NewGuid())) + (context as IRootContext).Context.Executor.Graph.AcceptEventActionNodes.Select(node => (node, Guid.NewGuid())) ); return Task.CompletedTask; diff --git a/Core/Stateflows/Activities/Engine/Processor.cs b/Core/Stateflows/Activities/Engine/Processor.cs index 0451ed67..a7261ed4 100644 --- a/Core/Stateflows/Activities/Engine/Processor.cs +++ b/Core/Stateflows/Activities/Engine/Processor.cs @@ -11,6 +11,7 @@ using Stateflows.Activities.Models; using Stateflows.Common.Extensions; using System.ComponentModel.DataAnnotations; +using Stateflows.Common.Initializer; namespace Stateflows.Activities.Engine { @@ -19,7 +20,7 @@ internal class Processor : IEventProcessor string IEventProcessor.BehaviorType => BehaviorType.Activity; public readonly ActivitiesRegister Register; - public readonly Dictionary EventHandlers; + public readonly IEnumerable EventHandlers; public readonly IServiceProvider ServiceProvider; public Processor( @@ -29,15 +30,19 @@ IServiceProvider serviceProvider ) { Register = register; - EventHandlers = eventHandlers.ToDictionary(h => h.EventType, h => h); ServiceProvider = serviceProvider.CreateScope().ServiceProvider; + EventHandlers = eventHandlers; } - private async Task TryHandleEventAsync(EventContext context) + private Task TryHandleEventAsync(EventContext context) where TEvent : Event, new() - => EventHandlers.TryGetValue(context.Event.GetType(), out var eventHandler) - ? await eventHandler.TryHandleEventAsync(context) - : EventStatus.NotConsumed; + { + var eventHandler = EventHandlers.FirstOrDefault(h => h.EventType.IsInstanceOfType(context.Event)); + + return eventHandler != null + ? eventHandler.TryHandleEventAsync(context) + : Task.FromResult(EventStatus.NotConsumed); + } public async Task ProcessEventAsync(BehaviorId id, TEvent @event, IServiceProvider serviceProvider) where TEvent : Event, new() @@ -61,33 +66,32 @@ public async Task ProcessEventAsync(BehaviorId id, TEvent @ { var context = new RootContext(stateflowsContext); - if (await executor.HydrateAsync(context)) + await executor.HydrateAsync(context); + + if (@event is CompoundRequest compoundRequest) { - if (@event is CompoundRequest compoundRequest) + result = EventStatus.Consumed; + var results = new List(); + foreach (var ev in compoundRequest.Events) { - result = EventStatus.Consumed; - var results = new List(); - foreach (var ev in compoundRequest.Events) - { - ev.Headers.AddRange(@event.Headers); - - var status = await ExecuteBehaviorAsync(ev, result, stateflowsContext, graph, executor, context); + ev.Headers.AddRange(@event.Headers); - results.Add(new RequestResult(ev, ev.GetResponse(), status, new EventValidation(true, new List()))); - } + var status = await ExecuteBehaviorAsync(ev, result, stateflowsContext, graph, executor, context); - compoundRequest.Respond(new CompoundResponse() - { - Results = results - }); - } - else - { - result = await ExecuteBehaviorAsync(@event, result, stateflowsContext, graph, executor, context); + results.Add(new RequestResult(ev, ev.GetResponse(), status, new EventValidation(true, new List()))); } - await storage.DehydrateAsync((await executor.DehydrateAsync()).Context); + compoundRequest.Respond(new CompoundResponse() + { + Results = results + }); + } + else + { + result = await ExecuteBehaviorAsync(@event, result, stateflowsContext, graph, executor, context); } + + await storage.DehydrateAsync((await executor.DehydrateAsync()).Context); } return result; @@ -101,6 +105,21 @@ public async Task ProcessEventAsync(BehaviorId id, TEvent @ if (await executor.Inspector.BeforeProcessEventAsync(eventContext)) { + if (!executor.Initialized) + { + var token = BehaviorClassesInitializations.Instance.AutoInitializationTokens.Find(token => token.BehaviorClass == executor.Context.Id.ActivityClass); + + InitializationRequest initializationRequest; + if (token != null && (initializationRequest = await token.InitializationRequestFactory.Invoke(executor.NodeScope.ServiceProvider, executor.Context.Id)) != null) + { + context.SetEvent(initializationRequest); + + await executor.InitializeAsync(initializationRequest); + + context.ClearEvent(); + } + } + result = await TryHandleEventAsync(eventContext); if (result != EventStatus.Consumed) @@ -122,19 +141,13 @@ public async Task ProcessEventAsync(BehaviorId id, TEvent @ stateflowsContext.LastExecutedAt = DateTime.Now; - var statuses = new BehaviorStatus[] { BehaviorStatus.Initialized, BehaviorStatus.Finalized }; - - if (statuses.Contains(stateflowsContext.Status)) + stateflowsContext.Version = stateflowsContext.Status switch { - stateflowsContext.Version = graph.Version; - } - else - { - if (stateflowsContext.Status == BehaviorStatus.NotInitialized) - { - stateflowsContext.Version = 0; - } - } + BehaviorStatus.NotInitialized => stateflowsContext.Version, + BehaviorStatus.Initialized => graph.Version, + BehaviorStatus.Finalized => graph.Version, + _ => 0 + }; return result; } diff --git a/Core/Stateflows/Activities/EventHandlers/BehaviorStatusHandler.cs b/Core/Stateflows/Activities/EventHandlers/BehaviorStatusHandler.cs index 0b26b64f..3db9949a 100644 --- a/Core/Stateflows/Activities/EventHandlers/BehaviorStatusHandler.cs +++ b/Core/Stateflows/Activities/EventHandlers/BehaviorStatusHandler.cs @@ -1,8 +1,10 @@ using System; +using System.Linq; using System.Threading.Tasks; using Stateflows.Common; using Stateflows.Activities.Extensions; using Stateflows.Activities.Inspection.Interfaces; +using Stateflows.StateMachines.Events; namespace Stateflows.Activities.EventHandlers { @@ -13,18 +15,19 @@ internal class BehaviorStatusHandler : IActivityEventHandler public Task TryHandleEventAsync(IEventInspectionContext context) where TEvent : Event, new() { - if (context.Event is BehaviorStatusRequest) + if (context.Event is BehaviorStatusRequest request) { var executor = context.Activity.GetExecutor(); - var status = (executor.Initialized, false) switch - { - (false, false) => BehaviorStatus.NotInitialized, - (true, false) => BehaviorStatus.Initialized, - (true, true) => BehaviorStatus.Finalized, - _ => BehaviorStatus.NotInitialized - }; - (context.Event as BehaviorStatusRequest).Respond(new BehaviorStatusResponse() { BehaviorStatus = status }); + request.Respond(new BehaviorStatusResponse() + { + BehaviorStatus = executor.BehaviorStatus, + ExpectedEvents = executor.GetExpectedEvents() + .Where(type => !type.IsSubclassOf(typeof(TimeEvent))) + .Where(type => type != typeof(CompletionEvent)) + .Select(type => type.GetEventName()) + .ToArray(), + }); return Task.FromResult(EventStatus.Consumed); } diff --git a/Core/Stateflows/Activities/EventHandlers/CancelHandler.cs b/Core/Stateflows/Activities/EventHandlers/FinalizationHandler.cs similarity index 59% rename from Core/Stateflows/Activities/EventHandlers/CancelHandler.cs rename to Core/Stateflows/Activities/EventHandlers/FinalizationHandler.cs index ea654b10..b9d37b1e 100644 --- a/Core/Stateflows/Activities/EventHandlers/CancelHandler.cs +++ b/Core/Stateflows/Activities/EventHandlers/FinalizationHandler.cs @@ -1,24 +1,23 @@ using System; using System.Threading.Tasks; using Stateflows.Common; -using Stateflows.Activities.Events; using Stateflows.Activities.Extensions; using Stateflows.Activities.Inspection.Interfaces; namespace Stateflows.Activities.EventHandlers { - internal class CancelHandler : IActivityEventHandler + internal class FinalizationHandler : IActivityEventHandler { - public Type EventType => typeof(CancelRequest); + public Type EventType => typeof(FinalizationRequest); public async Task TryHandleEventAsync(IEventInspectionContext context) where TEvent : Event, new() { - if (context.Event is CancelRequest cancelRequest) + if (context.Event is FinalizationRequest request) { - var cancelled = await context.Activity.GetExecutor().CancelAsync(); + var finalized = await context.Activity.GetExecutor().CancelAsync(); - cancelRequest.Respond(new CancelResponse() { CancelSuccessful = cancelled }); + request.Respond(new FinalizationResponse() { FinalizationSuccessful = finalized }); return EventStatus.Consumed; } diff --git a/Core/Stateflows/Activities/EventHandlers/NotificationsHandler.cs b/Core/Stateflows/Activities/EventHandlers/NotificationsHandler.cs new file mode 100644 index 00000000..f433f183 --- /dev/null +++ b/Core/Stateflows/Activities/EventHandlers/NotificationsHandler.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using Stateflows.Common; +using Stateflows.Activities.Inspection.Interfaces; + +namespace Stateflows.Activities.EventHandlers +{ + internal class NotificationsHandler : IActivityEventHandler + { + public Type EventType => typeof(NotificationsRequest); + + public Task TryHandleEventAsync(IEventInspectionContext context) + where TEvent : Event, new() + => Task.FromResult(context.Event is NotificationsRequest + ? EventStatus.Consumed + : EventStatus.NotConsumed + ); + } +} diff --git a/Core/Stateflows/Activities/EventHandlers/ResetHandler.cs b/Core/Stateflows/Activities/EventHandlers/ResetHandler.cs index 5bcf57c6..93811546 100644 --- a/Core/Stateflows/Activities/EventHandlers/ResetHandler.cs +++ b/Core/Stateflows/Activities/EventHandlers/ResetHandler.cs @@ -15,7 +15,7 @@ public Task TryHandleEventAsync(IEventInspectionContext typeof(SubscriptionRequest); + + public Task TryHandleEventAsync(IEventInspectionContext context) + where TEvent : Event, new() + { + if (context.Event is SubscriptionRequest request) + { + var result = context.Activity.GetExecutor().Context.Context.AddSubscribers(request.BehaviorId, request.NotificationNames); + + request.Respond(new SubscriptionResponse() { SubscriptionSuccessful = result }); + + return Task.FromResult(EventStatus.Consumed); + } + + return Task.FromResult(EventStatus.NotConsumed); + } + } +} diff --git a/Core/Stateflows/Activities/EventHandlers/UnsubscriptionHandler.cs b/Core/Stateflows/Activities/EventHandlers/UnsubscriptionHandler.cs new file mode 100644 index 00000000..f4020be5 --- /dev/null +++ b/Core/Stateflows/Activities/EventHandlers/UnsubscriptionHandler.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using Stateflows.Common; +using Stateflows.Activities.Extensions; +using Stateflows.Activities.Inspection.Interfaces; + +namespace Stateflows.Activities.EventHandlers +{ + internal class UnsubscriptionHandler : IActivityEventHandler + { + public Type EventType => typeof(UnsubscriptionRequest); + + public Task TryHandleEventAsync(IEventInspectionContext context) + where TEvent : Event, new() + { + if (context.Event is UnsubscriptionRequest request) + { + var result = context.Activity.GetExecutor().Context.Context.RemoveSubscribers(request.BehaviorId, request.NotificationNames); + + request.Respond(new UnsubscriptionResponse() { UnsubscriptionSuccessful = result }); + + return Task.FromResult(EventStatus.Consumed); + } + + return Task.FromResult(EventStatus.NotConsumed); + } + } +} diff --git a/Core/Stateflows/Activities/Extensions/BuilderExtensions.cs b/Core/Stateflows/Activities/Extensions/BuilderExtensions.cs index c77a70a2..ff5ca7a7 100644 --- a/Core/Stateflows/Activities/Extensions/BuilderExtensions.cs +++ b/Core/Stateflows/Activities/Extensions/BuilderExtensions.cs @@ -8,7 +8,7 @@ using Stateflows.Activities.Context.Interfaces; using Stateflows.Activities.Registration.Builders; using Stateflows.Activities.Registration.Interfaces; -using Stateflows.StateMachines.Registration; +using Stateflows.Activities.Events; namespace Stateflows.Activities.Extensions { @@ -45,7 +45,7 @@ public static void AddActivityEvents(this IActivityBuilder builder, Type activit (builder as ActivityBuilder).AddInitializer(requestType, requestName, c => { var activity = c.Context.Executor.GetActivity(activityType, c.Context); - return methodInfo.Invoke(activity, new object[] { c.Context.Event }) as Task; + return methodInfo.Invoke(activity, new object[] { c.Context.Event is ExecutionRequest executionRequest ? executionRequest.InitializationRequest : c.Context.Event }) as Task; }); } } diff --git a/Core/Stateflows/Activities/Extensions/CompositeStateBuilderExtensions.cs b/Core/Stateflows/Activities/Extensions/CompositeStateBuilderExtensions.cs deleted file mode 100644 index 6bea5c92..00000000 --- a/Core/Stateflows/Activities/Extensions/CompositeStateBuilderExtensions.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Threading.Tasks; -using Stateflows.Common; -using Stateflows.StateMachines.Sync; -using Stateflows.Activities.Extensions; -using Stateflows.StateMachines.Registration; -using Stateflows.StateMachines.Registration.Interfaces; - -namespace Stateflows.Activities -{ - public static class CompositeStateBuilderExtensions - { - public static ICompositeStateBuilder AddOnEntryActivity(this ICompositeStateBuilder builder, string activityName, StateActionActivityInitializationBuilder initializationBuilder = null) - => builder - .AddOnEntry( - async c => - { - if (c.TryLocateActivity(activityName, Constants.Entry, out var a)) - { - InitializationRequest initializationRequest = initializationBuilder?.Invoke(c); - await a.ExecuteAsync(initializationRequest); - } - } - ); - - public static ICompositeStateBuilder AddOnEntryActivity(this ICompositeStateBuilder builder, StateActionActivityInitializationBuilder initializationBuilder = null) - where TActivity : Activity - => AddOnEntryActivity(builder, ActivityInfo.Name, initializationBuilder); - - public static ICompositeStateBuilder AddOnExitActivity(this ICompositeStateBuilder builder, string activityName, StateActionActivityInitializationBuilder initializationBuilder = null) - => builder - .AddOnExit( - async c => - { - if (c.TryLocateActivity(activityName, Constants.Exit, out var a)) - { - InitializationRequest initializationRequest = initializationBuilder?.Invoke(c); - await a.ExecuteAsync(initializationRequest); - } - } - ); - - public static ICompositeStateBuilder AddOnExitActivity(this ICompositeStateBuilder builder, StateActionActivityInitializationBuilder initializationBuilder = null) - where TActivity : Activity - => AddOnExitActivity(builder, ActivityInfo.Name, initializationBuilder); - - public static ICompositeStateBuilder AddOnDoActivity(this ICompositeStateBuilder builder, string activityName, StateActionActivityInitializationBuilder initializationBuilder = null) - => builder - .AddOnEntry( - c => - { - if (c.TryLocateActivity(activityName, Constants.Do, out var a)) - { - InitializationRequest initializationRequest = initializationBuilder?.Invoke(c); - Task.Run(() => a.ExecuteAsync(initializationRequest)); - } - } - ) - .AddOnExit( - async c => - { - if (c.TryLocateActivity(activityName, Constants.Do, out var a)) - { - await a.CancelAsync(); - } - } - ); - - public static ICompositeStateBuilder AddOnDoActivity(this ICompositeStateBuilder builder, StateActionActivityInitializationBuilder initializationBuilder = null) - where TActivity : Activity - => AddOnDoActivity(builder, ActivityInfo.Name, initializationBuilder); - } -} diff --git a/Core/Stateflows/Activities/Extensions/RootContextExtensions.cs b/Core/Stateflows/Activities/Extensions/RootContextExtensions.cs deleted file mode 100644 index 44da6bc4..00000000 --- a/Core/Stateflows/Activities/Extensions/RootContextExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Stateflows.Activities.Streams; -using Stateflows.Activities.Context.Classes; -using Stateflows.Activities.Models; - -namespace Stateflows.Activities.Extensions -{ - //internal static class RootContextExtensions - //{ - // public static Stream GetStream(this RootContext context, Element element) - // { - // Stream stream = null; - - // lock (context.Streams) - // { - // if (!context.Streams.TryGetValue(element.Identifier, out stream)) - // { - // stream = new Stream(); - // context.Streams.Add(element.Identifier, stream); - // } - // } - - // return stream; - // } - //} -} diff --git a/Core/Stateflows/Activities/Extensions/StateBuilderExtensions.cs b/Core/Stateflows/Activities/Extensions/StateBuilderExtensions.cs deleted file mode 100644 index 5a4131b1..00000000 --- a/Core/Stateflows/Activities/Extensions/StateBuilderExtensions.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Stateflows.Common; -using Stateflows.Activities.Extensions; -using Stateflows.StateMachines.Sync; -using Stateflows.StateMachines.Registration; -using Stateflows.StateMachines.Registration.Interfaces; - -namespace Stateflows.Activities -{ - public static class StateBuilderExtensions - { - public static IStateBuilder AddOnEntryActivity(this IStateBuilder builder, string activityName, StateActionActivityInitializationBuilder parametersBuilder = null) - => builder - .AddOnEntry( - async c => - { - if (c.TryLocateActivity(activityName, Constants.Entry, out var a)) - { - var initializationRequest = parametersBuilder?.Invoke(c) ?? new InitializationRequest(); - await a.ExecuteAsync(initializationRequest); - } - } - ); - - public static IStateBuilder AddOnEntryActivity(this IStateBuilder builder, StateActionActivityInitializationBuilder parametersBuilder = null) - where TActivity : Activity - => AddOnEntryActivity(builder, ActivityInfo.Name, parametersBuilder); - - public static IStateBuilder AddOnExitActivity(this IStateBuilder builder, string activityName, StateActionActivityInitializationBuilder parametersBuilder = null) - => builder - .AddOnExit( - async c => - { - if (c.TryLocateActivity(activityName, Constants.Entry, out var a)) - { - var initializationRequest = parametersBuilder?.Invoke(c) ?? new InitializationRequest(); - await a.ExecuteAsync(initializationRequest); - } - } - ); - - public static IStateBuilder AddOnExitActivity(this IStateBuilder builder, StateActionActivityInitializationBuilder parametersBuilder = null) - where TActivity : Activity - => AddOnExitActivity(builder, ActivityInfo.Name, parametersBuilder); - - public static IStateBuilder AddOnDoActivity(this IStateBuilder builder, string activityName, StateActionActivityInitializationBuilder parametersBuilder = null) - => builder - .AddOnEntry( - c => - { - if (c.TryLocateActivity(activityName, Constants.Do, out var a)) - { - var initializationRequest = parametersBuilder?.Invoke(c) ?? new InitializationRequest(); - _ = a.ExecuteAsync(initializationRequest); - } - } - ) - .AddOnExit( - async c => - { - if (c.TryLocateActivity(activityName, Constants.Do, out var a)) - { - await a.CancelAsync(); - } - } - ); - - public static IStateBuilder AddOnDoActivity(this IStateBuilder builder, StateActionActivityInitializationBuilder parametersBuilder = null) - where TActivity : Activity - => AddOnDoActivity(builder, ActivityInfo.Name, parametersBuilder); - } -} diff --git a/Core/Stateflows/Activities/Interfaces/IActivity.cs b/Core/Stateflows/Activities/Interfaces/IActivity.cs index 175080a2..cae07321 100644 --- a/Core/Stateflows/Activities/Interfaces/IActivity.cs +++ b/Core/Stateflows/Activities/Interfaces/IActivity.cs @@ -11,7 +11,5 @@ public interface IActivity : IBehavior Task Execute(IDictionary parameters); Task Execute(IDictionary parameters); - - Task Cancel(); } } diff --git a/Core/Stateflows/Activities/Models/Graph.cs b/Core/Stateflows/Activities/Models/Graph.cs index a07b5c75..b64b361f 100644 --- a/Core/Stateflows/Activities/Models/Graph.cs +++ b/Core/Stateflows/Activities/Models/Graph.cs @@ -2,11 +2,10 @@ using System.Linq; using System.Diagnostics; using System.Collections.Generic; +using Stateflows.Common; using Stateflows.Common.Models; using Stateflows.Activities.Exceptions; using Stateflows.Activities.Registration.Interfaces; -using System.Xml.Linq; -using Stateflows.Common; namespace Stateflows.Activities.Models { @@ -49,8 +48,8 @@ public void Build() else { throw new FlowDefinitionException(!AllNamedNodes.ContainsKey(edge.TargetName) - ? $"Flow target action '{edge.TargetName}' is not registered in activity '{Name}'" - : $"Flow target action '{edge.TargetName}' is not defined on the same level as flow source '{edge.SourceName}' in activity '{Name}'" + ? $"Invalid activity '{Name}': flow target action '{edge.TargetName}' is not registered." + : $"Invalid activity '{Name}': flow target action '{edge.TargetName}' is not defined on the same level as flow source '{edge.SourceName}'." ); } } @@ -67,7 +66,7 @@ public void Build() if (danglingNodes.Any()) { var node = danglingNodes.First(); - throw new NodeDefinitionException(node.Name, $"Invalid activity: node '{node.Name}' doesn't have any incoming flow."); + throw new NodeDefinitionException(node.Name, $"Invalid activity '{Name}': node '{node.Name}' doesn't have any incoming flow."); } var transitiveNodeTypes = new NodeType[] { @@ -89,7 +88,7 @@ public void Build() if (undeclaredOutgoingTokens.Any()) { - throw new NodeDefinitionException(node.Name, $"Invalid outgoing flow: node '{node.Name}' doesn't have incoming flow with '{TokenInfo.GetName(undeclaredOutgoingTokens.First())}' tokens."); + throw new NodeDefinitionException(node.Name, $"Invalid activity '{Name}': node '{node.Name}' doesn't have incoming flow with '{TokenInfo.GetName(undeclaredOutgoingTokens.First())}' tokens, outgoing flow is invalid."); } } @@ -104,17 +103,17 @@ public void Build() if (undeclaredIncomingTokens.Any()) { - throw new NodeDefinitionException(node.Name, $"Invalid incoming flow: action '{node.Name}' doesn't accept incoming '{TokenInfo.GetName(undeclaredIncomingTokens.First())}' tokens."); + throw new NodeDefinitionException(node.Name, $"Invalid activity '{Name}': action '{node.Name}' doesn't accept incoming '{TokenInfo.GetName(undeclaredIncomingTokens.First())}' tokens, incoming flow is invalid."); } if (undeclaredOutgoingTokens.Any()) { - throw new NodeDefinitionException(node.Name, $"Invalid outgoing flow: action '{node.Name}' doesn't produce outgoing '{TokenInfo.GetName(undeclaredOutgoingTokens.First())}' tokens."); + throw new NodeDefinitionException(node.Name, $"Invalid activity '{Name}': action '{node.Name}' doesn't produce outgoing '{TokenInfo.GetName(undeclaredOutgoingTokens.First())}' tokens, outgoing flow is invalid."); } if (unsatisfiedIncomingTokens.Any()) { - throw new NodeDefinitionException(node.Name, $"Missing incoming flow: action '{node.Name}' requires '{TokenInfo.GetName(unsatisfiedIncomingTokens.First())}' input tokens, but there is no incoming flow with them."); + throw new NodeDefinitionException(node.Name, $"Invalid activity '{Name}': action '{node.Name}' requires '{TokenInfo.GetName(unsatisfiedIncomingTokens.First())}' input tokens, but there is no incoming flow with them."); } } } diff --git a/Core/Stateflows/Activities/Models/Node.cs b/Core/Stateflows/Activities/Models/Node.cs index 18a406c7..3290107b 100644 --- a/Core/Stateflows/Activities/Models/Node.cs +++ b/Core/Stateflows/Activities/Models/Node.cs @@ -6,7 +6,6 @@ using Stateflows.Common.Models; using Stateflows.Activities.Registration; using Stateflows.Activities.Context.Classes; -using System.Runtime.InteropServices; namespace Stateflows.Activities.Models { @@ -26,6 +25,8 @@ public override string Identifier public NodeOptions Options { get; set; } = NodeOptions.ActionDefault; public Type ExceptionType { get; set; } public Type EventType { get; set; } + public int ChunkSize { get; set; } + public bool Anchored { get; set; } = true; private Logic action = null; public Logic Action @@ -164,19 +165,35 @@ public async Task> HandleExceptionAsync(Exception exception, { Node handler = null; var currentNode = this; - while (handler == null && currentNode != null) + while (currentNode != null) { handler = currentNode.ExceptionHandlers.FirstOrDefault(n => exception.GetType().IsAssignableFrom(n.ExceptionType)); + if (handler != null) + { + break; + } + currentNode = currentNode.Parent; } + var currentScope = context.NodeScope; + while (currentNode != null) + { + if (currentScope.Node == currentNode) + { + break; + } + + currentScope = currentScope.BaseNodeScope; + } + if (handler != null) { var exceptionContext = new ActionContext( context.Context, - context.NodeScope, - this, + currentScope, + handler, new Token[] { new ExceptionToken() { Exception = exception } } ); diff --git a/Core/Stateflows/Activities/Registration/Builders/ActivityBuilder.cs b/Core/Stateflows/Activities/Registration/Builders/ActivityBuilder.cs index 8c06dc6e..b4d74bb9 100644 --- a/Core/Stateflows/Activities/Registration/Builders/ActivityBuilder.cs +++ b/Core/Stateflows/Activities/Registration/Builders/ActivityBuilder.cs @@ -4,12 +4,13 @@ using Stateflows.Common; using Stateflows.Common.Models; using Stateflows.Activities.Models; +using Stateflows.Activities.Events; +using Stateflows.Activities.Extensions; using Stateflows.Activities.Context.Classes; using Stateflows.Activities.Context.Interfaces; using Stateflows.Activities.Registration.Extensions; using Stateflows.Activities.Registration.Interfaces; using Stateflows.Activities.Registration.Interfaces.Base; -using Stateflows.Activities.Extensions; namespace Stateflows.Activities.Registration.Builders { @@ -67,7 +68,15 @@ public IActivityBuilder AddOnInitialize(Func { var result = false; - var context = new ActivityInitializationContext(c, c.Context.Event as TInitializationRequest); + var context = new ActivityInitializationContext( + c, + ( + c.Context.Event is ExecutionRequest executionRequest + ? executionRequest.InitializationRequest + : c.Context.Event + ) as TInitializationRequest + ); + try { result = await actionAsync(context); @@ -104,11 +113,11 @@ IActivityBuilder IInput.AddInput(InputBuildAction buildAction) IActivityBuilder IOutput.AddOutput() => AddOutput() as IActivityBuilder; - IActivityBuilder IReactiveActivity.AddParallelActivity(string actionNodeName, ParallelActivityBuildAction buildAction) - => AddParallelActivity(actionNodeName, buildAction) as IActivityBuilder; + IActivityBuilder IReactiveActivity.AddParallelActivity(string actionNodeName, ParallelActivityBuildAction buildAction, int chunkSize) + => AddParallelActivity(actionNodeName, buildAction, chunkSize) as IActivityBuilder; - IActivityBuilder IReactiveActivity.AddIterativeActivity(string actionNodeName, IterativeActivityBuildAction buildAction) - => AddIterativeActivity(actionNodeName, buildAction) as IActivityBuilder; + IActivityBuilder IReactiveActivity.AddIterativeActivity(string actionNodeName, IterativeActivityBuildAction buildAction, int chunkSize) + => AddIterativeActivity(actionNodeName, buildAction, chunkSize) as IActivityBuilder; IActivityBuilder IAcceptEvent.AddAcceptEventAction(string actionNodeName, AcceptEventActionDelegateAsync eventActionAsync, AcceptEventActionBuildAction buildAction) => AddAcceptEventAction(actionNodeName, eventActionAsync, buildAction) as IActivityBuilder; @@ -136,11 +145,11 @@ ITypedActivityBuilder IInput.AddInput(InputBuildAction bu ITypedActivityBuilder IOutput.AddOutput() => AddOutput() as ITypedActivityBuilder; - ITypedActivityBuilder IReactiveActivity.AddParallelActivity(string actionNodeName, ParallelActivityBuildAction buildAction) - => AddParallelActivity(actionNodeName, buildAction) as ITypedActivityBuilder; + ITypedActivityBuilder IReactiveActivity.AddParallelActivity(string actionNodeName, ParallelActivityBuildAction buildAction, int chunkSize) + => AddParallelActivity(actionNodeName, buildAction, chunkSize) as ITypedActivityBuilder; - ITypedActivityBuilder IReactiveActivity.AddIterativeActivity(string actionNodeName, IterativeActivityBuildAction buildAction) - => AddIterativeActivity(actionNodeName, buildAction) as ITypedActivityBuilder; + ITypedActivityBuilder IReactiveActivity.AddIterativeActivity(string actionNodeName, IterativeActivityBuildAction buildAction, int chunkSize) + => AddIterativeActivity(actionNodeName, buildAction, chunkSize) as ITypedActivityBuilder; ITypedActivityBuilder IAcceptEvent.AddAcceptEventAction(string actionNodeName, AcceptEventActionDelegateAsync eventActionAsync, AcceptEventActionBuildAction buildAction) => AddAcceptEventAction(actionNodeName, eventActionAsync, buildAction) as ITypedActivityBuilder; diff --git a/Core/Stateflows/Activities/Registration/Builders/BaseActivityBuilder.cs b/Core/Stateflows/Activities/Registration/Builders/BaseActivityBuilder.cs index 87461142..cb051292 100644 --- a/Core/Stateflows/Activities/Registration/Builders/BaseActivityBuilder.cs +++ b/Core/Stateflows/Activities/Registration/Builders/BaseActivityBuilder.cs @@ -29,7 +29,7 @@ public BaseActivityBuilder(Node parentNode, IServiceCollection services) Node = parentNode; } - public BaseActivityBuilder AddNode(NodeType type, string nodeName, ActionDelegateAsync actionAsync, NodeBuildAction buildAction = null, Type exceptionOrEventType = null) + public BaseActivityBuilder AddNode(NodeType type, string nodeName, ActionDelegateAsync actionAsync, NodeBuildAction buildAction = null, Type exceptionOrEventType = null, int chunkSize = 1) { if (Node.Type != NodeType.Activity) { @@ -71,7 +71,8 @@ public BaseActivityBuilder AddNode(NodeType type, string nodeName, ActionDelegat Name = nodeName, Parent = Node, Graph = Result, - Level = Node.Level + 1 + Level = Node.Level + 1, + Anchored = type != NodeType.ParallelActivity && Node.Anchored, }; if (type == NodeType.ExceptionHandler) @@ -94,6 +95,8 @@ public BaseActivityBuilder AddNode(NodeType type, string nodeName, ActionDelegat node.EventType = exceptionOrEventType; } + node.ChunkSize = chunkSize; + buildAction?.Invoke(new NodeBuilder(node, this, Services)); node.Action.Actions.Add(async c => @@ -112,7 +115,10 @@ public BaseActivityBuilder AddNode(NodeType type, string nodeName, ActionDelegat } catch (Exception e) { - c.OutputRange(await node.HandleExceptionAsync(e, c as BaseContext)); + await (c as BaseContext).Context.Executor.HandleExceptionAsync(node, e, c as BaseContext); + //c.OutputRange( + //await node.HandleExceptionAsync(e, c as BaseContext); + //); } } ); @@ -170,7 +176,7 @@ public BaseActivityBuilder AddAcceptEventAction( public BaseActivityBuilder AddInitial(InitialBuildAction buildAction) => AddNode( NodeType.Initial, - nameof(NodeType.Initial), + $"{nameof(NodeType.Initial)}Node", c => Task.CompletedTask, b => buildAction(b) ); @@ -190,7 +196,7 @@ public BaseActivityBuilder AddFinal() public BaseActivityBuilder AddInput(InputBuildAction buildAction) => AddNode( NodeType.Input, - nameof(NodeType.Input), + $"{nameof(NodeType.Input)}Node", c => { c.PassAllOn(); @@ -225,7 +231,7 @@ public BaseActivityBuilder AddStructuredActivity(string structuredActivityNodeNa await executor.DoInitializeNodeAsync(node, c as ActionContext); } - (var output, var finalized) = await executor.DoExecuteStructuredNodeAsync(node, c.Activity.GetNodeScope().CreateChildScope(), c.Input); + (var output, var finalized) = await executor.DoExecuteStructuredNodeAsync(node, c.Activity.GetNodeScope(), c.Input); c.OutputRange(output); @@ -237,7 +243,7 @@ public BaseActivityBuilder AddStructuredActivity(string structuredActivityNodeNa b => buildAction?.Invoke(new StructuredActivityBuilder(b.Node, this, Services)) ); - public BaseActivityBuilder AddParallelActivity(string parallelActivityNodeName, ParallelActivityBuildAction buildAction = null) + public BaseActivityBuilder AddParallelActivity(string parallelActivityNodeName, ParallelActivityBuildAction buildAction = null, int chunkSize = 1) where TToken : Token, new() => AddNode( NodeType.ParallelActivity, @@ -248,13 +254,15 @@ public BaseActivityBuilder AddParallelActivity(string parallelActivityNo var node = c.GetNode(); await executor.DoInitializeNodeAsync(node, c as ActionContext); - c.OutputRange(await executor.DoExecuteParallelNodeAsync(c as ActionContext)); + c.OutputRange(await executor.DoExecuteParallelNodeAsync(node, c.Activity.GetNodeScope(), c.Input)); await executor.DoFinalizeNodeAsync(node, c as ActionContext); }, - b => buildAction?.Invoke(new StructuredActivityBuilder(b.Node, this, Services)) + b => buildAction?.Invoke(new StructuredActivityBuilder(b.Node, this, Services)), + null, + chunkSize ); - public BaseActivityBuilder AddIterativeActivity(string parallelActivityNodeName, IterativeActivityBuildAction buildAction = null) + public BaseActivityBuilder AddIterativeActivity(string parallelActivityNodeName, IterativeActivityBuildAction buildAction = null, int chunkSize = 1) where TToken : Token, new() => AddNode( NodeType.IterativeActivity, @@ -268,7 +276,9 @@ public BaseActivityBuilder AddIterativeActivity(string parallelActivityN c.OutputRange(await executor.DoExecuteIterativeNodeAsync(c as ActionContext)); await executor.DoFinalizeNodeAsync(node, c as ActionContext); }, - b => buildAction?.Invoke(new StructuredActivityBuilder(b.Node, this, Services)) + b => buildAction?.Invoke(new StructuredActivityBuilder(b.Node, this, Services)), + null, + chunkSize ); public BaseActivityBuilder AddOnFinalize(Func actionAsync) diff --git a/Core/Stateflows/Activities/Registration/Builders/NodeBuilder.cs b/Core/Stateflows/Activities/Registration/Builders/NodeBuilder.cs index 27a91b13..0d7f100f 100644 --- a/Core/Stateflows/Activities/Registration/Builders/NodeBuilder.cs +++ b/Core/Stateflows/Activities/Registration/Builders/NodeBuilder.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Stateflows.Common; using Stateflows.Activities.Models; using Stateflows.Activities.Context.Classes; using Stateflows.Activities.Registration.Builders; using Stateflows.Activities.Registration.Interfaces; using Stateflows.Activities.Registration.Interfaces.Base; -using Stateflows.Common; namespace Stateflows.Activities.Registration { diff --git a/Core/Stateflows/Activities/Registration/Builders/StructuredActivityBuilder.cs b/Core/Stateflows/Activities/Registration/Builders/StructuredActivityBuilder.cs index fcef00cb..8a4b4bf7 100644 --- a/Core/Stateflows/Activities/Registration/Builders/StructuredActivityBuilder.cs +++ b/Core/Stateflows/Activities/Registration/Builders/StructuredActivityBuilder.cs @@ -12,6 +12,7 @@ namespace Stateflows.Activities.Registration.Builders internal class StructuredActivityBuilder : BaseActivityBuilder, IActionBuilder, + ITypedActionBuilder, IActionBuilderWithOptions, IReactiveStructuredActivityBuilder, IReactiveStructuredActivityBuilderWithOptions, @@ -119,11 +120,11 @@ IReactiveStructuredActivityBuilder IOutput.A IReactiveStructuredActivityBuilder IReactiveActivity.AddStructuredActivity(string actionNodeName, ReactiveStructuredActivityBuildAction buildAction) => AddStructuredActivity(actionNodeName, buildAction) as IReactiveStructuredActivityBuilder; - IReactiveStructuredActivityBuilder IReactiveActivity.AddParallelActivity(string actionNodeName, ParallelActivityBuildAction buildAction) - => AddParallelActivity(actionNodeName, buildAction) as IReactiveStructuredActivityBuilder; + IReactiveStructuredActivityBuilder IReactiveActivity.AddParallelActivity(string actionNodeName, ParallelActivityBuildAction buildAction, int chunkSize) + => AddParallelActivity(actionNodeName, buildAction, chunkSize) as IReactiveStructuredActivityBuilder; - IReactiveStructuredActivityBuilder IReactiveActivity.AddIterativeActivity(string actionNodeName, IterativeActivityBuildAction buildAction) - => AddIterativeActivity(actionNodeName, buildAction) as IReactiveStructuredActivityBuilder; + IReactiveStructuredActivityBuilder IReactiveActivity.AddIterativeActivity(string actionNodeName, IterativeActivityBuildAction buildAction, int chunkSize) + => AddIterativeActivity(actionNodeName, buildAction, chunkSize) as IReactiveStructuredActivityBuilder; IReactiveStructuredActivityBuilderWithOptions INodeOptions.SetOptions(NodeOptions nodeOptions) { @@ -159,11 +160,11 @@ IReactiveStructuredActivityBuilderWithOptions IOutput.AddStructuredActivity(string actionNodeName, ReactiveStructuredActivityBuildAction buildAction) => AddStructuredActivity(actionNodeName, buildAction) as IReactiveStructuredActivityBuilderWithOptions; - IReactiveStructuredActivityBuilderWithOptions IReactiveActivity.AddParallelActivity(string actionNodeName, ParallelActivityBuildAction buildAction) - => AddParallelActivity(actionNodeName, buildAction) as IReactiveStructuredActivityBuilderWithOptions; + IReactiveStructuredActivityBuilderWithOptions IReactiveActivity.AddParallelActivity(string actionNodeName, ParallelActivityBuildAction buildAction, int chunkSize) + => AddParallelActivity(actionNodeName, buildAction, chunkSize) as IReactiveStructuredActivityBuilderWithOptions; - IReactiveStructuredActivityBuilderWithOptions IReactiveActivity.AddIterativeActivity(string actionNodeName, IterativeActivityBuildAction buildAction) - => AddIterativeActivity(actionNodeName, buildAction) as IReactiveStructuredActivityBuilderWithOptions; + IReactiveStructuredActivityBuilderWithOptions IReactiveActivity.AddIterativeActivity(string actionNodeName, IterativeActivityBuildAction buildAction, int chunkSize) + => AddIterativeActivity(actionNodeName, buildAction, chunkSize) as IReactiveStructuredActivityBuilderWithOptions; IReactiveStructuredActivityBuilder IExceptionHandler.AddExceptionHandler(ExceptionHandlerDelegateAsync exceptionHandler) => AddExceptionHandler(exceptionHandler) as IReactiveStructuredActivityBuilder; @@ -273,5 +274,16 @@ IStructuredActivityBuilder ISendEvent.AddSendEventAc IStructuredActivityBuilderWithOptions ISendEvent.AddSendEventAction(string actionNodeName, SendEventActionDelegateAsync actionAsync, BehaviorIdSelectorAsync targetSelectorAsync, SendEventActionBuildAction buildAction) => AddSendEventAction(actionNodeName, actionAsync, targetSelectorAsync, buildAction) as IStructuredActivityBuilderWithOptions; #endregion + + #region ITypedActionBuilder + ITypedActionBuilder IObjectFlow.AddFlow(string targetNodeName, ObjectFlowBuildAction buildAction) + => AddFlow(targetNodeName, buildAction) as ITypedActionBuilder; + + ITypedActionBuilder IControlFlow.AddControlFlow(string targetNodeName, ControlFlowBuildAction buildAction) + => AddControlFlow(targetNodeName, buildAction) as ITypedActionBuilder; + + ITypedActionBuilder IExceptionHandler.AddExceptionHandler(ExceptionHandlerDelegateAsync exceptionHandler) + => AddExceptionHandler(exceptionHandler) as ITypedActionBuilder; + #endregion } } \ No newline at end of file diff --git a/Core/Stateflows/Activities/Registration/Interfaces/Base/IReactiveActivity.cs b/Core/Stateflows/Activities/Registration/Interfaces/Base/IReactiveActivity.cs index 504568d9..7e7a0d89 100644 --- a/Core/Stateflows/Activities/Registration/Interfaces/Base/IReactiveActivity.cs +++ b/Core/Stateflows/Activities/Registration/Interfaces/Base/IReactiveActivity.cs @@ -8,10 +8,10 @@ public interface IReactiveActivity TReturn AddStructuredActivity(string actionNodeName, ReactiveStructuredActivityBuildAction buildAction); - TReturn AddParallelActivity(string actionNodeName, ParallelActivityBuildAction buildAction) + TReturn AddParallelActivity(string actionNodeName, ParallelActivityBuildAction buildAction, int chunkSize = 1) where TParallelizationToken : Token, new(); - TReturn AddIterativeActivity(string actionNodeName, IterativeActivityBuildAction buildAction) + TReturn AddIterativeActivity(string actionNodeName, IterativeActivityBuildAction buildAction, int chunkSize = 1) where TIterationToken : Token, new(); } } diff --git a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ActivityBuilder/ActivityBuilderAnonymousExtensions.cs b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ActivityBuilder/ActivityBuilderAnonymousExtensions.cs index 960b72b3..95030cbc 100644 --- a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ActivityBuilder/ActivityBuilderAnonymousExtensions.cs +++ b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ActivityBuilder/ActivityBuilderAnonymousExtensions.cs @@ -8,12 +8,12 @@ public static class ActivityBuilderAnonymousExtensions public static IActivityBuilder AddStructuredActivity(this IActivityBuilder builder, ReactiveStructuredActivityBuildAction buildAction) => builder.AddStructuredActivity(ActivityNodeInfo.Name, buildAction); - public static IActivityBuilder AddParallelActivity(this IActivityBuilder builder, ParallelActivityBuildAction buildAction) + public static IActivityBuilder AddParallelActivity(this IActivityBuilder builder, ParallelActivityBuildAction buildAction, int chunkSize = 1) where TParallelizationToken : Token, new() - => builder.AddParallelActivity(ActivityNodeInfo>.Name, buildAction); + => builder.AddParallelActivity(ActivityNodeInfo>.Name, buildAction, chunkSize); - public static IActivityBuilder AddIterativeActivity(this IActivityBuilder builder, IterativeActivityBuildAction buildAction) + public static IActivityBuilder AddIterativeActivity(this IActivityBuilder builder, IterativeActivityBuildAction buildAction, int chunkSize = 1) where TIterationToken : Token, new() - => builder.AddIterativeActivity(ActivityNodeInfo>.Name, buildAction); + => builder.AddIterativeActivity(ActivityNodeInfo>.Name, buildAction, chunkSize); } } diff --git a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ActivityBuilder/ActivityBuilderTypedExtensions.cs b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ActivityBuilder/ActivityBuilderTypedExtensions.cs index 4581f33e..0889a972 100644 --- a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ActivityBuilder/ActivityBuilderTypedExtensions.cs +++ b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ActivityBuilder/ActivityBuilderTypedExtensions.cs @@ -162,12 +162,12 @@ public static IActivityBuilder AddParallelActivity(this IActivityBuilder builder, IterativeActivityBuildAction buildAction = null) + public static IActivityBuilder AddIterativeActivity(this IActivityBuilder builder, IterativeActivityBuildAction buildAction = null, int chunkSize = 1) where TIterationToken : Token, new() where TStructuredActivity : IterativeActivityNode - => AddIterativeActivity(builder, ActivityNodeInfo.Name, buildAction); + => AddIterativeActivity(builder, ActivityNodeInfo.Name, buildAction, chunkSize); - public static IActivityBuilder AddIterativeActivity(this IActivityBuilder builder, string structuredActivityName, IterativeActivityBuildAction buildAction = null) + public static IActivityBuilder AddIterativeActivity(this IActivityBuilder builder, string structuredActivityName, IterativeActivityBuildAction buildAction = null, int chunkSize = 1) where TIterationToken : Token, new() where TStructuredActivity : IterativeActivityNode { @@ -180,7 +180,8 @@ public static IActivityBuilder AddIterativeActivity(); builder.Node.ScanForDeclaredTypes(typeof(TStructuredActivity)); buildAction?.Invoke(b); - } + }, + chunkSize ); } #endregion diff --git a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ReactiveStructuredActivityBuilder/ReactiveStructuredActivityBuilderAnonymousExtensions.cs b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ReactiveStructuredActivityBuilder/ReactiveStructuredActivityBuilderAnonymousExtensions.cs index 54ef5a24..9cc27510 100644 --- a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ReactiveStructuredActivityBuilder/ReactiveStructuredActivityBuilderAnonymousExtensions.cs +++ b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ReactiveStructuredActivityBuilder/ReactiveStructuredActivityBuilderAnonymousExtensions.cs @@ -8,12 +8,12 @@ public static class ReactiveStructuredActivityBuilderAnonymousExtensions public static IReactiveStructuredActivityBuilder AddStructuredActivity(this IReactiveStructuredActivityBuilder builder, ReactiveStructuredActivityBuildAction buildAction) => builder.AddStructuredActivity(ActivityNodeInfo.Name, buildAction); - public static IReactiveStructuredActivityBuilder AddParallelActivity(this IReactiveStructuredActivityBuilder builder, ParallelActivityBuildAction buildAction) + public static IReactiveStructuredActivityBuilder AddParallelActivity(this IReactiveStructuredActivityBuilder builder, ParallelActivityBuildAction buildAction, int chunkSize = 1) where TParallelizationToken : Token, new() - => builder.AddParallelActivity(ActivityNodeInfo>.Name, buildAction); + => builder.AddParallelActivity(ActivityNodeInfo>.Name, buildAction, chunkSize); - public static IReactiveStructuredActivityBuilder AddIterativeActivity(this IReactiveStructuredActivityBuilder builder, IterativeActivityBuildAction buildAction) + public static IReactiveStructuredActivityBuilder AddIterativeActivity(this IReactiveStructuredActivityBuilder builder, IterativeActivityBuildAction buildAction, int chunkSize = 1) where TIterationToken : Token, new() - => builder.AddIterativeActivity(ActivityNodeInfo>.Name, buildAction); + => builder.AddIterativeActivity(ActivityNodeInfo>.Name, buildAction, chunkSize); } } diff --git a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ReactiveStructuredActivityBuilder/ReactiveStructuredActivityBuilderTypedExtensions.cs b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ReactiveStructuredActivityBuilder/ReactiveStructuredActivityBuilderTypedExtensions.cs index 0e6027d6..320c2ae2 100644 --- a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ReactiveStructuredActivityBuilder/ReactiveStructuredActivityBuilderTypedExtensions.cs +++ b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/ReactiveStructuredActivityBuilder/ReactiveStructuredActivityBuilderTypedExtensions.cs @@ -162,12 +162,12 @@ public static IReactiveStructuredActivityBuilder AddParallelActivity(this IReactiveStructuredActivityBuilder builder, IterativeActivityBuildAction buildAction = null) + public static IReactiveStructuredActivityBuilder AddIterativeActivity(this IReactiveStructuredActivityBuilder builder, IterativeActivityBuildAction buildAction = null, int chunkSize = 1) where TIterationToken : Token, new() where TStructuredActivity : IterativeActivityNode - => AddIterativeActivity(builder, ActivityNodeInfo.Name, buildAction); + => AddIterativeActivity(builder, ActivityNodeInfo.Name, buildAction, chunkSize); - public static IReactiveStructuredActivityBuilder AddIterativeActivity(this IReactiveStructuredActivityBuilder builder, string structuredActivityName, IterativeActivityBuildAction buildAction = null) + public static IReactiveStructuredActivityBuilder AddIterativeActivity(this IReactiveStructuredActivityBuilder builder, string structuredActivityName, IterativeActivityBuildAction buildAction = null, int chunkSize = 1) where TIterationToken : Token, new() where TStructuredActivity : IterativeActivityNode { @@ -180,7 +180,8 @@ public static IReactiveStructuredActivityBuilder AddIterativeActivity(); builder.Node.ScanForDeclaredTypes(typeof(TStructuredActivity)); buildAction?.Invoke(b); - } + }, + chunkSize ); } #endregion diff --git a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/TypedActivityBuilder/TypedActivityBuilderSpecialsExtensions.cs b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/TypedActivityBuilder/TypedActivityBuilderSpecialsExtensions.cs index 2f8d8b51..16aaa298 100644 --- a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/TypedActivityBuilder/TypedActivityBuilderSpecialsExtensions.cs +++ b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/TypedActivityBuilder/TypedActivityBuilderSpecialsExtensions.cs @@ -87,8 +87,36 @@ public static ITypedActivityBuilder AddDataStore(this ITypedActivityBuilder buil b => decisionBuildAction(b.SetOptions(NodeOptions.DataStoreDefault) as IDataStoreBuilder) ) as ITypedActivityBuilder; - public static ITypedActivityBuilder AddTimeEventAction(this ITypedActivityBuilder builder, string actionNodeName, ActionDelegateAsync actionAsync, AcceptEventActionBuildAction buildAction) + #region AddAcceptEventAction + public static ITypedActivityBuilder AddAcceptEventAction(this ITypedActivityBuilder builder, string actionNodeName, AcceptEventActionBuildAction buildAction) + where TEvent : Event, new() + => builder.AddAcceptEventAction(actionNodeName, c => Task.CompletedTask, buildAction); + + public static ITypedActivityBuilder AddAcceptEventAction(this ITypedActivityBuilder builder, AcceptEventActionBuildAction buildAction) + where TEvent : Event, new() + => builder.AddAcceptEventAction(ActivityNodeInfo>.Name, c => Task.CompletedTask, buildAction); + + public static ITypedActivityBuilder AddAcceptEventAction(this ITypedActivityBuilder builder, ActionDelegateAsync actionAsync, AcceptEventActionBuildAction buildAction = null) + where TEvent : Event, new() + => builder.AddAcceptEventAction(ActivityNodeInfo>.Name, c => actionAsync(c), buildAction); + #endregion + + #region AddTimeEventAction + public static ITypedActivityBuilder AddTimeEventAction(this ITypedActivityBuilder builder, string actionNodeName, AcceptEventActionBuildAction buildAction) + where TTimeEvent : TimeEvent, new() + => builder.AddAcceptEventAction(actionNodeName, c => Task.CompletedTask, buildAction); + + public static ITypedActivityBuilder AddTimeEventAction(this ITypedActivityBuilder builder, string actionNodeName, ActionDelegateAsync actionAsync, AcceptEventActionBuildAction buildAction = null) where TTimeEvent : TimeEvent, new() => builder.AddAcceptEventAction(actionNodeName, c => actionAsync(c), buildAction); + + public static ITypedActivityBuilder AddTimeEventAction(this ITypedActivityBuilder builder, ActionDelegateAsync actionAsync, AcceptEventActionBuildAction buildAction = null) + where TTimeEvent : TimeEvent, new() + => builder.AddAcceptEventAction(ActivityNodeInfo>.Name, c => actionAsync(c), buildAction); + + public static ITypedActivityBuilder AddTimeEventAction(this ITypedActivityBuilder builder, AcceptEventActionBuildAction buildAction) + where TTimeEvent : TimeEvent, new() + => builder.AddAcceptEventAction(ActivityNodeInfo>.Name, c => Task.CompletedTask, buildAction); + #endregion } } diff --git a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/TypedActivityBuilder/TypedActivityBuilderTypedExtensions.cs b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/TypedActivityBuilder/TypedActivityBuilderTypedExtensions.cs index 353969bb..b89bf70a 100644 --- a/Core/Stateflows/Activities/Registration/Interfaces/Extensions/TypedActivityBuilder/TypedActivityBuilderTypedExtensions.cs +++ b/Core/Stateflows/Activities/Registration/Interfaces/Extensions/TypedActivityBuilder/TypedActivityBuilderTypedExtensions.cs @@ -150,12 +150,12 @@ public static ITypedActivityBuilder AddParallelActivity(this ITypedActivityBuilder builder, IterativeActivityBuildAction buildAction = null) + public static ITypedActivityBuilder AddIterativeActivity(this ITypedActivityBuilder builder, IterativeActivityBuildAction buildAction = null, int chunkSize = 1) where TIterationToken : Token, new() where TStructuredActivity : StructuredActivityNode - => AddIterativeActivity(builder, ActivityNodeInfo.Name, buildAction); + => AddIterativeActivity(builder, ActivityNodeInfo.Name, buildAction, chunkSize); - public static ITypedActivityBuilder AddIterativeActivity(this ITypedActivityBuilder builder, string structuredActivityName, IterativeActivityBuildAction buildAction = null) + public static ITypedActivityBuilder AddIterativeActivity(this ITypedActivityBuilder builder, string structuredActivityName, IterativeActivityBuildAction buildAction = null, int chunkSize = 1) where TIterationToken : Token, new() where TStructuredActivity : StructuredActivityNode { @@ -168,7 +168,8 @@ public static ITypedActivityBuilder AddIterativeActivity(); builder.Node.ScanForDeclaredTypes(typeof(TStructuredActivity)); buildAction?.Invoke(b); - } + }, + chunkSize ); } #endregion diff --git a/Core/Stateflows/Activities/StateMachines/Classes/IntegratedActivityBuilder.cs b/Core/Stateflows/Activities/StateMachines/Classes/IntegratedActivityBuilder.cs new file mode 100644 index 00000000..88edc667 --- /dev/null +++ b/Core/Stateflows/Activities/StateMachines/Classes/IntegratedActivityBuilder.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Stateflows.Activities.Extensions; +using Stateflows.Common; + +namespace Stateflows.Activities.StateMachines.Interfaces +{ + internal class IntegratedActivityBuilder : IIntegratedActivityBuilder + { + public List Notifications { get; } = new List(); + + public IntegratedActivityBuilder(IntegratedActivityBuildAction buildAction) + { + buildAction?.Invoke(this); + } + + public SubscriptionRequest GetSubscriptionRequest() + => new SubscriptionRequest() + { + NotificationNames = Notifications + .Select(notificationType => EventInfo.GetName(notificationType)) + .ToList() + }; + + public UnsubscriptionRequest GetUnsubscriptionRequest() + => new UnsubscriptionRequest() + { + NotificationNames = Notifications + .Select(notificationType => EventInfo.GetName(notificationType)) + .ToList() + }; + + public Task SubscribeAsync(IBehavior behavior) + => Notifications.Any() + ? behavior.RequestAsync(GetSubscriptionRequest()) + : Task.CompletedTask; + + public Task UnsubscribeAsync(IBehavior behavior) + => Notifications.Any() + ? behavior.RequestAsync(GetUnsubscriptionRequest()) + : Task.CompletedTask; + + public IIntegratedActivityBuilder AddSubscription() + where TNotification : Notification, new() + { + Notifications.Add(typeof(TNotification)); + + return this; + } + } +} diff --git a/Core/Stateflows/Activities/StateMachines/Extensions/CompositeStateBuilderExtensions.cs b/Core/Stateflows/Activities/StateMachines/Extensions/CompositeStateBuilderExtensions.cs new file mode 100644 index 00000000..e419e4ed --- /dev/null +++ b/Core/Stateflows/Activities/StateMachines/Extensions/CompositeStateBuilderExtensions.cs @@ -0,0 +1,91 @@ +using System.Threading.Tasks; +using System.Collections.Generic; +using Stateflows.Common; +using Stateflows.StateMachines.Sync; +using Stateflows.Activities.Events; +using Stateflows.Activities.Extensions; +using Stateflows.Activities.StateMachines.Interfaces; +using Stateflows.StateMachines.Registration; +using Stateflows.StateMachines.Registration.Interfaces; + +namespace Stateflows.Activities +{ + public static class CompositeStateBuilderExtensions + { + public static ICompositeStateBuilder AddOnEntryActivity(this ICompositeStateBuilder builder, string activityName, StateActionActivityInitializationBuilder initializationBuilder = null, IntegratedActivityBuildAction buildAction = null) + => builder + .AddOnEntry( + c => + { + if (c.TryLocateActivity(activityName, Constants.Entry, out var a)) + { + InitializationRequest initializationRequest = initializationBuilder?.Invoke(c); + Task.Run(() => + { + var integratedActivityBuilder = new IntegratedActivityBuilder(buildAction); + return a.SendCompoundAsync( + integratedActivityBuilder.GetSubscriptionRequest(), + new ResetRequest() { KeepVersion = true }, + new ExecutionRequest(initializationRequest, new List()), + integratedActivityBuilder.GetUnsubscriptionRequest() + ); + //var request = new CompoundRequest() + //{ + // Events = new List() + // { + // integratedActivityBuilder.GetSubscriptionRequest(), + // new ResetRequest() { KeepVersion = true }, + // new ExecutionRequest(initializationRequest, new List()), + // integratedActivityBuilder.GetUnsubscriptionRequest(), + // } + //}; + + //return a.RequestAsync(request); + }); + } + } + ); + + public static ICompositeStateBuilder AddOnEntryActivity(this ICompositeStateBuilder builder, StateActionActivityInitializationBuilder initializationBuilder = null, IntegratedActivityBuildAction buildAction = null) + where TActivity : Activity + => AddOnEntryActivity(builder, ActivityInfo.Name, initializationBuilder, buildAction); + + public static ICompositeStateBuilder AddOnExitActivity(this ICompositeStateBuilder builder, string activityName, StateActionActivityInitializationBuilder initializationBuilder = null, IntegratedActivityBuildAction buildAction = null) + => builder + .AddOnExit( + c => + { + if (c.TryLocateActivity(activityName, Constants.Exit, out var a)) + { + InitializationRequest initializationRequest = initializationBuilder?.Invoke(c); + Task.Run(() => + { + var integratedActivityBuilder = new IntegratedActivityBuilder(buildAction); + return a.SendCompoundAsync( + integratedActivityBuilder.GetSubscriptionRequest(), + new ResetRequest() { KeepVersion = true }, + new ExecutionRequest(initializationRequest, new List()), + integratedActivityBuilder.GetUnsubscriptionRequest() + ); + //var request = new CompoundRequest() + //{ + // Events = new List() + // { + // integratedActivityBuilder.GetSubscriptionRequest(), + // new ResetRequest() { KeepVersion = true }, + // new ExecutionRequest(initializationRequest, new List()), + // integratedActivityBuilder.GetUnsubscriptionRequest(), + // } + //}; + + //return a.RequestAsync(request); + }); + } + } + ); + + public static ICompositeStateBuilder AddOnExitActivity(this ICompositeStateBuilder builder, StateActionActivityInitializationBuilder initializationBuilder = null, IntegratedActivityBuildAction buildAction = null) + where TActivity : Activity + => AddOnExitActivity(builder, ActivityInfo.Name, initializationBuilder, buildAction); + } +} diff --git a/Core/Stateflows/Activities/Extensions/Delegates.cs b/Core/Stateflows/Activities/StateMachines/Extensions/Delegates.cs similarity index 79% rename from Core/Stateflows/Activities/Extensions/Delegates.cs rename to Core/Stateflows/Activities/StateMachines/Extensions/Delegates.cs index 6d4b70f2..f0b29d40 100644 --- a/Core/Stateflows/Activities/Extensions/Delegates.cs +++ b/Core/Stateflows/Activities/StateMachines/Extensions/Delegates.cs @@ -1,5 +1,6 @@ using Stateflows.Common; using Stateflows.StateMachines.Context.Interfaces; +using Stateflows.Activities.StateMachines.Interfaces; namespace Stateflows.Activities.Extensions { @@ -11,4 +12,5 @@ public delegate InitializationRequest GuardActivityInitializationBuilder(IEventContext context) where TEvent : Event, new(); + public delegate void IntegratedActivityBuildAction(IIntegratedActivityBuilder builder); } \ No newline at end of file diff --git a/Core/Stateflows/Activities/Extensions/IStateActionContextExtensions.cs b/Core/Stateflows/Activities/StateMachines/Extensions/IStateActionContextExtensions.cs similarity index 100% rename from Core/Stateflows/Activities/Extensions/IStateActionContextExtensions.cs rename to Core/Stateflows/Activities/StateMachines/Extensions/IStateActionContextExtensions.cs diff --git a/Core/Stateflows/Activities/Extensions/ITransitionContextExtensions.cs b/Core/Stateflows/Activities/StateMachines/Extensions/ITransitionContextExtensions.cs similarity index 57% rename from Core/Stateflows/Activities/Extensions/ITransitionContextExtensions.cs rename to Core/Stateflows/Activities/StateMachines/Extensions/ITransitionContextExtensions.cs index a4a46c3a..61f25cfc 100644 --- a/Core/Stateflows/Activities/Extensions/ITransitionContextExtensions.cs +++ b/Core/Stateflows/Activities/StateMachines/Extensions/ITransitionContextExtensions.cs @@ -1,11 +1,20 @@ using System; using Stateflows.Common; +using Stateflows.StateMachines.Registration; using Stateflows.StateMachines.Context.Interfaces; namespace Stateflows.Activities { internal static class ITransitionContextExtensions { + public static string GetDoActivityInstance(this ITransitionContext context) + where TEvent : Event, new() + => $"{context.StateMachine.Id}.{context.SourceState.Name}.{Constants.Do}"; + + public static bool TryLocateDoActivity(this ITransitionContext context, string activityName, out IActivity activity) + where TEvent : Event, new() + => context.TryLocateActivity(new ActivityId(activityName, context.GetDoActivityInstance()), out activity); + public static string GetActivityInstance(this ITransitionContext context, string action) where TEvent : Event, new() => $"{context.StateMachine.Id}.{context.SourceState.Name}.{action}.{new Random().Next()}"; diff --git a/Core/Stateflows/Activities/StateMachines/Extensions/StateBuilderExtensions.cs b/Core/Stateflows/Activities/StateMachines/Extensions/StateBuilderExtensions.cs new file mode 100644 index 00000000..0f7546d2 --- /dev/null +++ b/Core/Stateflows/Activities/StateMachines/Extensions/StateBuilderExtensions.cs @@ -0,0 +1,95 @@ +using System.Threading.Tasks; +using System.Collections.Generic; +using Stateflows.Common; +using Stateflows.StateMachines.Sync; +using Stateflows.Activities.Events; +using Stateflows.Activities.Extensions; +using Stateflows.Activities.StateMachines.Interfaces; +using Stateflows.StateMachines.Registration; +using Stateflows.StateMachines.Registration.Interfaces; + +namespace Stateflows.Activities +{ + public static class StateBuilderExtensions + { + internal static readonly List DoActivites = new List(); + + public static IStateBuilder AddOnEntryActivity(this IStateBuilder builder, string activityName, StateActionActivityInitializationBuilder initializationBuilder = null, IntegratedActivityBuildAction buildAction = null) + => builder + .AddOnEntry( + c => + { + if (c.TryLocateActivity(activityName, Constants.Entry, out var a)) + { + InitializationRequest initializationRequest = initializationBuilder?.Invoke(c); + Task.Run(() => + { + var integratedActivityBuilder = new IntegratedActivityBuilder(buildAction); + + return a.SendCompoundAsync( + integratedActivityBuilder.GetSubscriptionRequest(), + new ResetRequest() { KeepVersion = true }, + new ExecutionRequest(initializationRequest, new List()), + integratedActivityBuilder.GetUnsubscriptionRequest() + ); + //var request = new CompoundRequest() + //{ + // Events = new List() + // { + // integratedActivityBuilder.GetSubscriptionRequest(), + // new ResetRequest() { KeepVersion = true }, + // new ExecutionRequest(initializationRequest, new List()), + // integratedActivityBuilder.GetUnsubscriptionRequest(), + // } + //}; + + //return a.RequestAsync(request); + }); + } + } + ); + + public static IStateBuilder AddOnEntryActivity(this IStateBuilder builder, StateActionActivityInitializationBuilder initializationBuilder = null, IntegratedActivityBuildAction buildAction = null) + where TActivity : Activity + => AddOnEntryActivity(builder, ActivityInfo.Name, initializationBuilder, buildAction); + + public static IStateBuilder AddOnExitActivity(this IStateBuilder builder, string activityName, StateActionActivityInitializationBuilder initializationBuilder = null, IntegratedActivityBuildAction buildAction = null) + => builder + .AddOnExit( + c => + { + if (c.TryLocateActivity(activityName, Constants.Exit, out var a)) + { + InitializationRequest initializationRequest = initializationBuilder?.Invoke(c); + Task.Run(() => + { + var integratedActivityBuilder = new IntegratedActivityBuilder(buildAction); + + return a.SendCompoundAsync( + integratedActivityBuilder.GetSubscriptionRequest(), + new ResetRequest() { KeepVersion = true }, + new ExecutionRequest(initializationRequest, new List()), + integratedActivityBuilder.GetUnsubscriptionRequest() + ); + //var request = new CompoundRequest() + //{ + // Events = new List() + // { + // integratedActivityBuilder.GetSubscriptionRequest(), + // new ResetRequest() { KeepVersion = true }, + // new ExecutionRequest(initializationRequest, new List()), + // integratedActivityBuilder.GetUnsubscriptionRequest(), + // } + //}; + + //return a.RequestAsync(request); + }); + } + } + ); + + public static IStateBuilder AddOnExitActivity(this IStateBuilder builder, StateActionActivityInitializationBuilder initializationBuilder = null, IntegratedActivityBuildAction buildAction = null) + where TActivity : Activity + => AddOnExitActivity(builder, ActivityInfo.Name, initializationBuilder, buildAction); + } +} diff --git a/Core/Stateflows/Activities/Extensions/TransitionBuilderExtensions.cs b/Core/Stateflows/Activities/StateMachines/Extensions/TransitionBuilderExtensions.cs similarity index 88% rename from Core/Stateflows/Activities/Extensions/TransitionBuilderExtensions.cs rename to Core/Stateflows/Activities/StateMachines/Extensions/TransitionBuilderExtensions.cs index d05ab910..ffc0ce45 100644 --- a/Core/Stateflows/Activities/Extensions/TransitionBuilderExtensions.cs +++ b/Core/Stateflows/Activities/StateMachines/Extensions/TransitionBuilderExtensions.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Diagnostics; using Stateflows.Common; using Stateflows.Activities.Extensions; using Stateflows.StateMachines.Registration; @@ -9,13 +10,15 @@ namespace Stateflows.Activities public static class TransitionBuilderExtensions { #region AddGuardActivity + [DebuggerHidden] public static ITransitionBuilder AddGuardActivity(this ITransitionBuilder builder, string activityName, GuardActivityInitializationBuilder parametersBuilder = null) where TEvent : Event, new() => builder.AddGuard( async c => c.TryLocateActivity(activityName, Constants.Guard, out var a) - && ((await a.ExecuteAsync(parametersBuilder?.Invoke(c))).Response?.OutputTokens.OfType>().FirstOrDefault()?.Payload ?? false) + && ((await a.ExecuteAsync(parametersBuilder?.Invoke(c), new Token[] { c.Event })).Response?.OutputTokens.OfType>().FirstOrDefault()?.Payload ?? false) ); + [DebuggerHidden] public static ITransitionBuilder AddGuardActivity(this ITransitionBuilder builder, GuardActivityInitializationBuilder parametersBuilder = null) where TEvent : Event, new() where TActivity : Activity @@ -23,6 +26,7 @@ public static ITransitionBuilder AddGuardActivity(thi #endregion #region AddEffectActivity + [DebuggerHidden] public static ITransitionBuilder AddEffectActivity(this ITransitionBuilder builder, string activityName, EffectActivityInitializationBuilder parametersBuilder = null) where TEvent : Event, new() => builder.AddEffect( @@ -30,11 +34,12 @@ public static ITransitionBuilder AddEffectActivity(this ITransit { if (c.TryLocateActivity(activityName, Constants.Guard, out var a)) { - await a.ExecuteAsync(parametersBuilder?.Invoke(c)); + await a.ExecuteAsync(parametersBuilder?.Invoke(c), new Token[] { c.Event }); } } ); + [DebuggerHidden] public static ITransitionBuilder AddEffectActivity(this ITransitionBuilder builder, EffectActivityInitializationBuilder parametersBuilder = null) where TEvent : Event, new() where TActivity : Activity diff --git a/Core/Stateflows/Activities/StateMachines/Extensions/TypedCompositeStateBuilderExtensions.cs b/Core/Stateflows/Activities/StateMachines/Extensions/TypedCompositeStateBuilderExtensions.cs new file mode 100644 index 00000000..e22ba358 --- /dev/null +++ b/Core/Stateflows/Activities/StateMachines/Extensions/TypedCompositeStateBuilderExtensions.cs @@ -0,0 +1,22 @@ +using Stateflows.Activities.Extensions; +using Stateflows.StateMachines.Registration.Interfaces; + +namespace Stateflows.Activities +{ + public static class TypedCompositeStateBuilderExtensions + { + public static ITypedCompositeStateBuilder AddOnEntryActivity(this ITypedCompositeStateBuilder builder, string activityName, StateActionActivityInitializationBuilder parametersBuilder = null, IntegratedActivityBuildAction buildAction = null) + => (builder as ICompositeStateBuilder).AddOnEntryActivity(activityName, parametersBuilder, buildAction) as ITypedCompositeStateBuilder; + + public static ITypedCompositeStateBuilder AddOnEntryActivity(this ITypedCompositeStateBuilder builder, StateActionActivityInitializationBuilder parametersBuilder = null, IntegratedActivityBuildAction buildAction = null) + where TActivity : Activity + => (builder as ICompositeStateBuilder).AddOnEntryActivity(parametersBuilder, buildAction) as ITypedCompositeStateBuilder; + + public static ITypedCompositeStateBuilder AddOnExitActivity(this ITypedCompositeStateBuilder builder, string activityName, StateActionActivityInitializationBuilder parametersBuilder = null, IntegratedActivityBuildAction buildAction = null) + => (builder as ICompositeStateBuilder).AddOnExitActivity(activityName, parametersBuilder, buildAction) as ITypedCompositeStateBuilder; + + public static ITypedCompositeStateBuilder AddOnExitActivity(this ITypedCompositeStateBuilder builder, StateActionActivityInitializationBuilder parametersBuilder = null, IntegratedActivityBuildAction buildAction = null) + where TActivity : Activity + => (builder as ICompositeStateBuilder).AddOnExitActivity(parametersBuilder, buildAction) as ITypedCompositeStateBuilder; + } +} diff --git a/Core/Stateflows/Activities/Extensions/TypedStateBuilderExtensions.cs b/Core/Stateflows/Activities/StateMachines/Extensions/TypedStateBuilderExtensions.cs similarity index 51% rename from Core/Stateflows/Activities/Extensions/TypedStateBuilderExtensions.cs rename to Core/Stateflows/Activities/StateMachines/Extensions/TypedStateBuilderExtensions.cs index 1884e17c..b4b38018 100644 --- a/Core/Stateflows/Activities/Extensions/TypedStateBuilderExtensions.cs +++ b/Core/Stateflows/Activities/StateMachines/Extensions/TypedStateBuilderExtensions.cs @@ -5,25 +5,18 @@ namespace Stateflows.Activities { public static class TypedStateBuilderExtensions { - public static ITypedStateBuilder AddOnEntryActivity(this ITypedStateBuilder builder, string activityName, StateActionActivityInitializationBuilder parametersBuilder = null) - => (builder as IStateBuilder).AddOnEntryActivity(activityName, parametersBuilder) as ITypedStateBuilder; + public static ITypedStateBuilder AddOnEntryActivity(this ITypedStateBuilder builder, string activityName, StateActionActivityInitializationBuilder parametersBuilder = null, IntegratedActivityBuildAction buildAction = null) + => (builder as IStateBuilder).AddOnEntryActivity(activityName, parametersBuilder, buildAction) as ITypedStateBuilder; - public static ITypedStateBuilder AddOnEntryActivity(this ITypedStateBuilder builder, StateActionActivityInitializationBuilder parametersBuilder = null) + public static ITypedStateBuilder AddOnEntryActivity(this ITypedStateBuilder builder, StateActionActivityInitializationBuilder parametersBuilder = null, IntegratedActivityBuildAction buildAction = null) where TActivity : Activity - => (builder as IStateBuilder).AddOnEntryActivity(parametersBuilder) as ITypedStateBuilder; + => (builder as IStateBuilder).AddOnEntryActivity(parametersBuilder, buildAction) as ITypedStateBuilder; - public static ITypedStateBuilder AddOnExitActivity(this ITypedStateBuilder builder, string activityName, StateActionActivityInitializationBuilder parametersBuilder = null) - => (builder as IStateBuilder).AddOnExitActivity(activityName, parametersBuilder) as ITypedStateBuilder; + public static ITypedStateBuilder AddOnExitActivity(this ITypedStateBuilder builder, string activityName, StateActionActivityInitializationBuilder parametersBuilder = null, IntegratedActivityBuildAction buildAction = null) + => (builder as IStateBuilder).AddOnExitActivity(activityName, parametersBuilder, buildAction) as ITypedStateBuilder; - public static ITypedStateBuilder AddOnExitActivity(this ITypedStateBuilder builder, StateActionActivityInitializationBuilder parametersBuilder = null) + public static ITypedStateBuilder AddOnExitActivity(this ITypedStateBuilder builder, StateActionActivityInitializationBuilder parametersBuilder = null, IntegratedActivityBuildAction buildAction = null) where TActivity : Activity - => (builder as IStateBuilder).AddOnExitActivity(parametersBuilder) as ITypedStateBuilder; - - public static ITypedStateBuilder AddOnDoActivity(this ITypedStateBuilder builder, string activityName, StateActionActivityInitializationBuilder parametersBuilder = null) - => (builder as IStateBuilder).AddOnDoActivity(activityName, parametersBuilder) as ITypedStateBuilder; - - public static ITypedStateBuilder AddOnDoActivity(this ITypedStateBuilder builder, StateActionActivityInitializationBuilder parametersBuilder = null) - where TActivity : Activity - => (builder as IStateBuilder).AddOnDoActivity(parametersBuilder) as ITypedStateBuilder; + => (builder as IStateBuilder).AddOnExitActivity(parametersBuilder, buildAction) as ITypedStateBuilder; } } diff --git a/Core/Stateflows/Activities/StateMachines/Interfaces/IIntegratedActivityBuilder.cs b/Core/Stateflows/Activities/StateMachines/Interfaces/IIntegratedActivityBuilder.cs new file mode 100644 index 00000000..7b412c67 --- /dev/null +++ b/Core/Stateflows/Activities/StateMachines/Interfaces/IIntegratedActivityBuilder.cs @@ -0,0 +1,10 @@ +using Stateflows.Common; + +namespace Stateflows.Activities.StateMachines.Interfaces +{ + public interface IIntegratedActivityBuilder + { + IIntegratedActivityBuilder AddSubscription() + where TNotification : Notification, new(); + } +} diff --git a/Core/Stateflows/Activities/Utils/IEnumerableExtensions.cs b/Core/Stateflows/Activities/Utils/IEnumerableExtensions.cs index a88eccc4..31d13bf5 100644 --- a/Core/Stateflows/Activities/Utils/IEnumerableExtensions.cs +++ b/Core/Stateflows/Activities/Utils/IEnumerableExtensions.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Collections.Generic; -namespace Stateflows.Activities.Utils +namespace Stateflows.Utils { public static class IEnumerableExtensions { @@ -23,5 +23,24 @@ public static TTarget First(this IEnumerable source) { return (TTarget)source.First(i => i is TTarget); } + + public static IEnumerable> Partition(this IEnumerable sequence, int size) + { + List partition = new List(size); + foreach (var item in sequence) + { + partition.Add(item); + if (partition.Count == size) + { + yield return partition; + partition = new List(size); + } + } + + if (partition.Count > 0) + { + yield return partition; + } + } } } diff --git a/Core/Stateflows/Common/Attributes/BehaviorAttribute.cs b/Core/Stateflows/Common/Attributes/BehaviorAttribute.cs index dabd1f76..df3a81f4 100644 --- a/Core/Stateflows/Common/Attributes/BehaviorAttribute.cs +++ b/Core/Stateflows/Common/Attributes/BehaviorAttribute.cs @@ -3,7 +3,7 @@ namespace Stateflows.Common.Attributes { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class BehaviorAttribute : Attribute + public abstract class BehaviorAttribute : Attribute { public string Name { get; set; } diff --git a/Core/Stateflows/Common/Classes/Behavior.cs b/Core/Stateflows/Common/Classes/Behavior.cs index ba207d4c..91467b14 100644 --- a/Core/Stateflows/Common/Classes/Behavior.cs +++ b/Core/Stateflows/Common/Classes/Behavior.cs @@ -1,6 +1,9 @@ using System; using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; using Stateflows.Common.Utilities; +using Stateflows.Common.Subscription; namespace Stateflows.Common.Classes { @@ -8,21 +11,43 @@ internal class Behavior : IBehavior { public BehaviorId Id { get; } - private StateflowsEngine Engine { get; } - - private IServiceProvider ServiceProvider { get; } + private readonly StateflowsEngine engine; + private readonly IServiceProvider serviceProvider; + private readonly NotificationsHub subscriptionHub; + private readonly Dictionary>> handlers = new Dictionary>>(); public Behavior(StateflowsEngine engine, IServiceProvider serviceProvider, BehaviorId id) { - Engine = engine; - ServiceProvider = serviceProvider; + this.engine = engine; + this.serviceProvider = serviceProvider; + subscriptionHub = serviceProvider.GetRequiredService(); + subscriptionHub.OnPublish += SubscriptionHub_OnPublish; Id = id; } + private void SubscriptionHub_OnPublish(Notification notification) + { + if (notification.SenderId != Id) + { + return; + } + + lock (handlers) + { + if (handlers.TryGetValue(notification.Name, out var notificationHandlers)) + { + foreach (var handler in notificationHandlers) + { + handler.Invoke(notification); + } + } + } + } + public async Task SendAsync(TEvent @event) where TEvent : Event, new() { - var holder = Engine.EnqueueEvent(Id, @event, ServiceProvider); + var holder = engine.EnqueueEvent(Id, @event, serviceProvider); await holder.Handled.WaitOneAsync(); return new SendResult(@event, holder.Status, holder.Validation); @@ -31,10 +56,52 @@ public async Task SendAsync(TEvent @event) public async Task> RequestAsync(Request request) where TResponse : Response, new() { - var holder = Engine.EnqueueEvent(Id, request, ServiceProvider); + var holder = engine.EnqueueEvent(Id, request, serviceProvider); await holder.Handled.WaitOneAsync(); return new RequestResult(request, holder.Status, holder.Validation); } + + public Task WatchAsync(Action handler) + where TNotification : Notification, new() + { + lock (handlers) + { + var notificationName = EventInfo.Name; + if (!handlers.TryGetValue(notificationName, out var notificationHandlers)) + { + notificationHandlers = new List>(); + handlers.Add(notificationName, notificationHandlers); + } + + notificationHandlers.Add(n => handler(n as TNotification)); + } + + return Task.CompletedTask; + } + + public Task UnwatchAsync() + where TNotification : Notification, new() + { + lock (handlers) + { + var notificationName = EventInfo.Name; + handlers.Remove(notificationName); + } + + return Task.CompletedTask; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + => subscriptionHub.OnPublish -= SubscriptionHub_OnPublish; + + ~Behavior() + => Dispose(false); } } diff --git a/Core/Stateflows/Common/Classes/ContextValues.cs b/Core/Stateflows/Common/Classes/ContextValues.cs index 621b8620..203c2a97 100644 --- a/Core/Stateflows/Common/Classes/ContextValues.cs +++ b/Core/Stateflows/Common/Classes/ContextValues.cs @@ -16,30 +16,43 @@ public ContextValues(Dictionary values) public void Set(string key, T value) { - Values[key] = StateflowsJsonConverter.SerializePolymorphicObject(value); + lock (Values) + { + Values[key] = StateflowsJsonConverter.SerializePolymorphicObject(value); + } } public bool IsSet(string key) { - return Values.ContainsKey(key); + bool result; + + lock (Values) + { + result = Values.ContainsKey(key); + } + + return result; } public bool TryGet(string key, out T value) { value = default; - if (Values.TryGetValue(key, out var data)) + lock (Values) { - var type = typeof(T); - var deserializedData = type.IsPrimitive - ? ParseStringToTypedValue(data) - : StateflowsJsonConverter.DeserializeObject(data); - - if (deserializedData is T t) + if (Values.TryGetValue(key, out var data)) { - value = t; + var type = typeof(T); + var deserializedData = type.IsPrimitive + ? ParseStringToTypedValue(data) + : StateflowsJsonConverter.DeserializeObject(data); - return true; + if (deserializedData is T t) + { + value = t; + + return true; + } } } @@ -48,16 +61,19 @@ public bool TryGet(string key, out T value) public T GetOrDefault(string key, T defaultValue) { - if (Values.TryGetValue(key, out var data)) + lock (Values) { - var type = typeof(T); - var deserializedData = type.IsPrimitive - ? ParseStringToTypedValue(data) - : StateflowsJsonConverter.DeserializeObject(data); - - if (deserializedData is T t) + if (Values.TryGetValue(key, out var data)) { - return t; + var type = typeof(T); + var deserializedData = type.IsPrimitive + ? ParseStringToTypedValue(data) + : StateflowsJsonConverter.DeserializeObject(data); + + if (deserializedData is T t) + { + return t; + } } } @@ -66,12 +82,18 @@ public T GetOrDefault(string key, T defaultValue) public void Remove(string key) { - Values.Remove(key); + lock (Values) + { + Values.Remove(key); + } } public void Clear() { - Values.Clear(); + lock (Values) + { + Values.Clear(); + } } private static T ParseStringToTypedValue(string value) diff --git a/Core/Stateflows/Common/Context/Classes/BaseContext.cs b/Core/Stateflows/Common/Context/Classes/BaseContext.cs index 15999e6b..ce0d1517 100644 --- a/Core/Stateflows/Common/Context/Classes/BaseContext.cs +++ b/Core/Stateflows/Common/Context/Classes/BaseContext.cs @@ -1,7 +1,5 @@ using System; -using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; -using Stateflows.Common.Interfaces; namespace Stateflows.Common.Context.Classes { diff --git a/Core/Stateflows/Common/Context/Classes/BehaviorContext.cs b/Core/Stateflows/Common/Context/Classes/BehaviorContext.cs index 69c8cce2..3a40aece 100644 --- a/Core/Stateflows/Common/Context/Classes/BehaviorContext.cs +++ b/Core/Stateflows/Common/Context/Classes/BehaviorContext.cs @@ -1,8 +1,10 @@ using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Stateflows.Common.Classes; using Stateflows.Common.Interfaces; +using Stateflows.Common.Subscription; using Stateflows.Common.Context.Interfaces; -using Microsoft.Extensions.DependencyInjection; namespace Stateflows.Common.Context.Classes { @@ -10,6 +12,10 @@ internal class BehaviorContext : BaseContext, IBehaviorContext { public BehaviorId Id => Context.Id; + private BehaviorSubscriber subscriber; + private BehaviorSubscriber Subscriber + => subscriber ??= new BehaviorSubscriber(Id, Context, this, ServiceProvider.GetRequiredService()); + public BehaviorContext(StateflowsContext context, IServiceProvider serviceProvider) : base(context, serviceProvider) { @@ -26,5 +32,17 @@ public BehaviorContext(StateflowsContext context, IServiceProvider serviceProvid _ = behavior.SendAsync(@event); } } + + public void Publish(TNotification notification) + where TNotification : Notification, new() + => _ = Subscriber.PublishAsync(Id, notification); + + public Task> SubscribeAsync(BehaviorId behaviorId) + where TNotification : Notification, new() + => _ = Subscriber.SubscribeAsync(behaviorId); + + public Task> UnsubscribeAsync(BehaviorId behaviorId) + where TNotification : Notification, new() + => _ = Subscriber.UnsubscribeAsync(behaviorId); } } diff --git a/Core/Stateflows/Common/Context/Interfaces/IBehaviorContext.cs b/Core/Stateflows/Common/Context/Interfaces/IBehaviorContext.cs index e495c10c..a79dbe9b 100644 --- a/Core/Stateflows/Common/Context/Interfaces/IBehaviorContext.cs +++ b/Core/Stateflows/Common/Context/Interfaces/IBehaviorContext.cs @@ -1,8 +1,9 @@ -using Stateflows.Common.Interfaces; +using System.Threading.Tasks; +using Stateflows.Common.Interfaces; namespace Stateflows.Common.Context.Interfaces { - public interface IBehaviorContext + public interface IBehaviorContext : ISubscriptions { BehaviorId Id { get; } @@ -10,5 +11,8 @@ public interface IBehaviorContext void Send(TEvent @event) where TEvent : Event, new(); + + void Publish(TNotification notification) + where TNotification : Notification, new(); } } diff --git a/Core/Stateflows/Common/Engine/StateflowsEngine.cs b/Core/Stateflows/Common/Engine/StateflowsEngine.cs index c85f54bc..25275c9d 100644 --- a/Core/Stateflows/Common/Engine/StateflowsEngine.cs +++ b/Core/Stateflows/Common/Engine/StateflowsEngine.cs @@ -10,7 +10,6 @@ using Stateflows.Common.Classes; using Stateflows.Common.Interfaces; using Stateflows.Common.Extensions; -using Stateflows.Common.Context; namespace Stateflows.Common { @@ -41,6 +40,8 @@ public StateflowsEngine(IServiceProvider serviceProvider) public EventHolder EnqueueEvent(BehaviorId id, Event @event, IServiceProvider serviceProvider) { + @event.SentAt = DateTime.Now; + var token = new EventHolder(id, @event, serviceProvider); EventQueue.Enqueue(token); @@ -81,6 +82,7 @@ public async Task ProcessEventAsync(BehaviorId id, TEvent @ if (response != null) { response.SenderId = id; + response.SentAt = DateTime.Now; } } } diff --git a/Core/Stateflows/Common/Events/ExitEvent.cs b/Core/Stateflows/Common/Events/ExitEvent.cs deleted file mode 100644 index f0a1bd65..00000000 --- a/Core/Stateflows/Common/Events/ExitEvent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Stateflows.Common.Events -{ -#pragma warning disable S2094 // Classes should not be empty - public sealed class ExitEvent : Event -#pragma warning restore S2094 // Classes should not be empty - { } -} diff --git a/Core/Stateflows/Common/Initializer/InitializationToken.cs b/Core/Stateflows/Common/Initializer/AutoInitializationToken.cs similarity index 60% rename from Core/Stateflows/Common/Initializer/InitializationToken.cs rename to Core/Stateflows/Common/Initializer/AutoInitializationToken.cs index f276e134..0640d422 100644 --- a/Core/Stateflows/Common/Initializer/InitializationToken.cs +++ b/Core/Stateflows/Common/Initializer/AutoInitializationToken.cs @@ -1,12 +1,12 @@ namespace Stateflows.Common.Initializer { - internal class InitializationToken + internal class AutoInitializationToken { public BehaviorClass BehaviorClass { get; private set; } - public InitializationRequestFactoryAsync InitializationRequestFactory { get; } + public AutoInitializationRequestFactoryAsync InitializationRequestFactory { get; } - public InitializationToken(BehaviorClass behaviorClass, InitializationRequestFactoryAsync initializationRequestFactoryAsync) + public AutoInitializationToken(BehaviorClass behaviorClass, AutoInitializationRequestFactoryAsync initializationRequestFactoryAsync) { BehaviorClass = behaviorClass; InitializationRequestFactory = initializationRequestFactoryAsync; diff --git a/Core/Stateflows/Common/Initializer/BehaviorClassesInitializations.cs b/Core/Stateflows/Common/Initializer/BehaviorClassesInitializations.cs index d663845a..68d6954f 100644 --- a/Core/Stateflows/Common/Initializer/BehaviorClassesInitializations.cs +++ b/Core/Stateflows/Common/Initializer/BehaviorClassesInitializations.cs @@ -8,11 +8,31 @@ internal class BehaviorClassesInitializations { public static readonly BehaviorClassesInitializations Instance = new BehaviorClassesInitializations(); - public readonly List InitializationTokens = new List(); + public readonly List DefaultInstanceInitializationTokens = new List(); - private static readonly InitializationRequestFactoryAsync DefaultFactory = (serviceProvider, behaviorClass) => Task.FromResult(new InitializationRequest()); + private static readonly DefaultInstanceInitializationRequestFactoryAsync DefaultDefaultInstanceFactory = (serviceProvider, behaviorClass) => Task.FromResult(new InitializationRequest()); - public void Initialize(BehaviorClass behaviorClass, InitializationRequestFactoryAsync initializationRequestFactory = null) - => InitializationTokens.Add(new InitializationToken(behaviorClass, initializationRequestFactory ?? DefaultFactory)); + public void AddDefaultInstanceInitialization(BehaviorClass behaviorClass, DefaultInstanceInitializationRequestFactoryAsync initializationRequestFactory = null) + => DefaultInstanceInitializationTokens.Add(new DefaultInstanceInitializationToken(behaviorClass, initializationRequestFactory ?? DefaultDefaultInstanceFactory)); + + public readonly List AutoInitializationTokens = new List(); + + private static readonly AutoInitializationRequestFactoryAsync DefaultAutoFactory = (serviceProvider, behaviorClass) => Task.FromResult(new InitializationRequest()); + + public void AddAutoInitialization(BehaviorClass behaviorClass, AutoInitializationRequestFactoryAsync initializationRequestFactory = null) + => AutoInitializationTokens.Add(new AutoInitializationToken(behaviorClass, initializationRequestFactory ?? DefaultAutoFactory)); + + public void RefreshEnvironment() + { + foreach (var token in DefaultInstanceInitializationTokens) + { + token.RefreshEnvironment(); + } + + foreach (var token in AutoInitializationTokens) + { + token.RefreshEnvironment(); + } + } } } diff --git a/Core/Stateflows/Common/Initializer/DefaultInstanceInitializationToken.cs b/Core/Stateflows/Common/Initializer/DefaultInstanceInitializationToken.cs new file mode 100644 index 00000000..3f0b0e09 --- /dev/null +++ b/Core/Stateflows/Common/Initializer/DefaultInstanceInitializationToken.cs @@ -0,0 +1,20 @@ +namespace Stateflows.Common.Initializer +{ + internal class DefaultInstanceInitializationToken + { + public BehaviorClass BehaviorClass { get; private set; } + + public DefaultInstanceInitializationRequestFactoryAsync InitializationRequestFactory { get; } + + public DefaultInstanceInitializationToken(BehaviorClass behaviorClass, DefaultInstanceInitializationRequestFactoryAsync initializationRequestFactoryAsync) + { + BehaviorClass = behaviorClass; + InitializationRequestFactory = initializationRequestFactoryAsync; + } + + public void RefreshEnvironment() + { + BehaviorClass = new BehaviorClass(BehaviorClass.Type, BehaviorClass.Name); + } + } +} \ No newline at end of file diff --git a/Core/Stateflows/Common/Initializer/InitializationRequestFactory.cs b/Core/Stateflows/Common/Initializer/InitializationRequestFactory.cs index a9185f5d..292d26cc 100644 --- a/Core/Stateflows/Common/Initializer/InitializationRequestFactory.cs +++ b/Core/Stateflows/Common/Initializer/InitializationRequestFactory.cs @@ -3,5 +3,9 @@ namespace Stateflows.Common.Initializer { - public delegate Task InitializationRequestFactoryAsync(IServiceProvider serviceProvider, BehaviorClass behaviorClass); + + public delegate Task DefaultInstanceInitializationRequestFactoryAsync(IServiceProvider serviceProvider, BehaviorClass behaviorClass); +#nullable enable + public delegate Task AutoInitializationRequestFactoryAsync(IServiceProvider serviceProvider, BehaviorId behaviorId); +#nullable disable } \ No newline at end of file diff --git a/Core/Stateflows/Common/Initializer/ThreadInitializer.cs b/Core/Stateflows/Common/Initializer/ThreadInitializer.cs index 8a6355bf..9a196073 100644 --- a/Core/Stateflows/Common/Initializer/ThreadInitializer.cs +++ b/Core/Stateflows/Common/Initializer/ThreadInitializer.cs @@ -43,7 +43,7 @@ public async Task StartAsync(CancellationToken cancellationToken) private async Task InitiateBehaviors() { - var tokens = BehaviorClassesInitializations.Instance.InitializationTokens; + var tokens = BehaviorClassesInitializations.Instance.DefaultInstanceInitializationTokens; await Task.WhenAll( tokens.Select(async token => diff --git a/Core/Stateflows/Common/Interfaces/IBehaviorInterceptor.cs b/Core/Stateflows/Common/Interfaces/IBehaviorInterceptor.cs index e8db8a96..491e9f9c 100644 --- a/Core/Stateflows/Common/Interfaces/IBehaviorInterceptor.cs +++ b/Core/Stateflows/Common/Interfaces/IBehaviorInterceptor.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using Stateflows.Common.Context.Interfaces; namespace Stateflows.Common diff --git a/Core/Stateflows/Common/Interfaces/INotificationsHub.cs b/Core/Stateflows/Common/Interfaces/INotificationsHub.cs new file mode 100644 index 00000000..ef03da19 --- /dev/null +++ b/Core/Stateflows/Common/Interfaces/INotificationsHub.cs @@ -0,0 +1,16 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace Stateflows.Common.Interfaces +{ + public interface INotificationsHub + { + Task PublishAsync(TNotification notification) + where TNotification : Notification, new(); + + event Action OnPublish; + + public Dictionary> Notifications { get; } + } +} diff --git a/Core/Stateflows/Common/Scheduler/ScheduleExecutor.cs b/Core/Stateflows/Common/Scheduler/ScheduleExecutor.cs index fb171be0..bde0b3ca 100644 --- a/Core/Stateflows/Common/Scheduler/ScheduleExecutor.cs +++ b/Core/Stateflows/Common/Scheduler/ScheduleExecutor.cs @@ -4,8 +4,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Stateflows.Common.Interfaces; -using Newtonsoft.Json.Linq; -using System.Collections.Generic; namespace Stateflows.Common.Scheduler { @@ -53,12 +51,13 @@ public async Task ExecuteAsync() foreach (var context in contexts) { - if (Locator.TryLocateBehavior(context.Id, out var behavior)) + var timeEvents = context.PendingTimeEvents.Values.Where(timeEvent => timeEvent.TriggerTime < DateTime.Now).ToArray(); + + if (timeEvents.Any() && Locator.TryLocateBehavior(context.Id, out var behavior)) { - var timeEvents = context.PendingTimeEvents.Values.Where(timeEvent => timeEvent.TriggerTime < DateTime.Now).ToArray(); foreach (var timeEvent in timeEvents) { - await behavior.SendAsync(timeEvent); + _ = behavior.SendAsync(timeEvent); } } } diff --git a/Core/Stateflows/Common/Scheduler/ThreadScheduler.cs b/Core/Stateflows/Common/Scheduler/ThreadScheduler.cs index 69046720..5d5bc0d2 100644 --- a/Core/Stateflows/Common/Scheduler/ThreadScheduler.cs +++ b/Core/Stateflows/Common/Scheduler/ThreadScheduler.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; -using Stateflows.Common.Tenant; namespace Stateflows.Common.Scheduler { @@ -49,7 +48,7 @@ private async Task TimingLoop(CancellationToken cancellationToken) try { - _ = Executor.ExecuteByTenantsAsync(() => HandleTimeEvents()); + await Executor.ExecuteByTenantsAsync(() => HandleTimeEvents()); } catch (Exception e) { @@ -63,18 +62,17 @@ private async Task TimingLoop(CancellationToken cancellationToken) private async Task HandleTimeEvents() { - using (var scope = ServiceProvider.CreateScope()) + using var scope = ServiceProvider.CreateScope(); + + try { - try - { - var runner = scope.ServiceProvider.GetRequiredService(); + var runner = scope.ServiceProvider.GetRequiredService(); - await runner.ExecuteAsync(); - } - catch (Exception e) - { - Logger.LogError(LogTemplates.ExceptionLogTemplate, typeof(ThreadScheduler).FullName, nameof(HandleTimeEvents), e.GetType().Name, e.Message); - } + await runner.ExecuteAsync(); + } + catch (Exception e) + { + Logger.LogError(LogTemplates.ExceptionLogTemplate, typeof(ThreadScheduler).FullName, nameof(HandleTimeEvents), e.GetType().Name, e.Message); } } diff --git a/Core/Stateflows/Common/Subscription/BehaviorSubscriber.cs b/Core/Stateflows/Common/Subscription/BehaviorSubscriber.cs new file mode 100644 index 00000000..362a75f3 --- /dev/null +++ b/Core/Stateflows/Common/Subscription/BehaviorSubscriber.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations; +using Stateflows.Common.Context; + +namespace Stateflows.Common.Subscription +{ + internal class BehaviorSubscriber + { + private readonly StateflowsContext context; + private readonly BehaviorId subscriberBehaviorId; + private readonly IBehaviorLocator behaviorLocator; + private readonly NotificationsHub subscriptionHub; + public BehaviorSubscriber(BehaviorId behaviorId, StateflowsContext context, IBehaviorLocator behaviorLocator, NotificationsHub subscriptionHub) + { + this.context = context; + this.subscriberBehaviorId = behaviorId; + this.behaviorLocator = behaviorLocator; + this.subscriptionHub = subscriptionHub; + } + + public async Task PublishAsync(BehaviorId behaviorId, TNotification notification) + where TNotification : Notification, new() + { + notification.SenderId = behaviorId; + notification.SentAt = DateTime.Now; + + if (context.Subscribers.TryGetValue(EventInfo.Name, out var behaviorIds)) + { + await Task.WhenAll( + behaviorIds.Select( + behaviorId => behaviorLocator.TryLocateBehavior(behaviorId, out var behavior) + ? behavior.SendAsync(notification) + : Task.CompletedTask + ) + ); + } + + await subscriptionHub.PublishAsync(notification); + } + + public Task> SubscribeAsync(BehaviorId behaviorId) + where TNotification : Notification, new() + { + var request = new SubscriptionRequest() { BehaviorId = subscriberBehaviorId }; + + request.NotificationNames.Add(EventInfo.Name); + + return behaviorLocator.TryLocateBehavior(behaviorId, out var behavior) + ? behavior.RequestAsync(request) + : Task.FromResult( + new RequestResult(request, EventStatus.Undelivered, new EventValidation(true, Array.Empty())) + ); + } + + public Task> UnsubscribeAsync(BehaviorId behaviorId) + where TNotification : Notification, new() + { + var request = new UnsubscriptionRequest() { BehaviorId = subscriberBehaviorId }; + + request.NotificationNames.Add(EventInfo.Name); + + return behaviorLocator.TryLocateBehavior(behaviorId, out var behavior) + ? behavior.RequestAsync(request) + : Task.FromResult( + new RequestResult(request, EventStatus.Undelivered, new EventValidation(true, Array.Empty())) + ); + } + } +} diff --git a/Core/Stateflows/Common/Subscription/NotificationsHub.cs b/Core/Stateflows/Common/Subscription/NotificationsHub.cs new file mode 100644 index 00000000..da22fd7d --- /dev/null +++ b/Core/Stateflows/Common/Subscription/NotificationsHub.cs @@ -0,0 +1,95 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Extensions.Hosting; +using Stateflows.Common.Interfaces; +using System.Diagnostics; + +namespace Stateflows.Common.Subscription +{ + internal class NotificationsHub : IHostedService, INotificationsHub + { + private readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); + + private readonly Dictionary> notifications = new Dictionary>(); + + public Dictionary> Notifications + => notifications; + + public event Action OnPublish; + + public Task PublishAsync(TNotification notification) + where TNotification : Notification, new() + { + lock (Notifications) + { + if (!Notifications.TryGetValue(notification.SenderId, out var behaviorNotifications)) + { + behaviorNotifications = new List(); + Notifications.Add(notification.SenderId, behaviorNotifications); + } + + behaviorNotifications.Add(notification); + } + + _ = Task.Run(() => OnPublish.Invoke(notification)); + + return Task.CompletedTask; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _ = Task.Run(() => TimingLoop(CancellationTokenSource.Token)); + + return Task.CompletedTask; + } + + private DateTime GetCurrentTick() + => new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, 0); + + private async Task TimingLoop(CancellationToken cancellationToken) + { + var lastTick = GetCurrentTick(); + + while (!cancellationToken.IsCancellationRequested) + { + var diffInSeconds = (DateTime.Now - lastTick).TotalSeconds; + + if (diffInSeconds >= 60) + { + lastTick = GetCurrentTick(); + + lock (Notifications) + { + var date = DateTime.Now.AddMinutes(-1); + foreach (var behaviorNotifications in Notifications.Values) + { + behaviorNotifications.RemoveAll(notification => notification.SentAt <= date); + } + + var emptyIds = Notifications + .Where(x => !x.Value.Any()) + .Select(x => x.Key) + .ToArray(); + + foreach (var id in emptyIds) + { + Notifications.Remove(id); + } + } + } + + await Task.Delay(1000); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + CancellationTokenSource.Cancel(); + + return Task.CompletedTask; + } + } +} diff --git a/Core/Stateflows/Common/Tenant/TenantExecutor.cs b/Core/Stateflows/Common/Tenant/TenantExecutor.cs index 0cf656f0..5e44fe15 100644 --- a/Core/Stateflows/Common/Tenant/TenantExecutor.cs +++ b/Core/Stateflows/Common/Tenant/TenantExecutor.cs @@ -1,6 +1,7 @@ using System; -using System.Linq; +using System.Diagnostics; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Stateflows.Common.Engine; using Stateflows.Common.Interfaces; @@ -26,17 +27,14 @@ public async Task ExecuteByTenantsAsync(Func tenantAction) { var tenantIds = await TenantsProvider.GetAllTenantsAsync(); - await Task.WhenAll( - tenantIds.Select( - tenantId => ExecuteByTenantAsync(tenantId, tenantAction) - ) - ); + foreach (var tenantId in tenantIds) + { + await ExecuteByTenantAsync(tenantId, tenantAction); + } } - public Task ExecuteByTenantAsync(string tenantId, Func tenantAction) + public async Task ExecuteByTenantAsync(string tenantId, Func tenantAction) { - Task result = null; - TenantAccessor.CurrentTenantId = tenantId; if (Interceptor.BeforeExecute(tenantId)) @@ -45,7 +43,7 @@ public Task ExecuteByTenantAsync(string tenantId, Func tenantAction) { try { - result = tenantAction(); + await tenantAction(); } catch (Exception e) { @@ -58,8 +56,6 @@ public Task ExecuteByTenantAsync(string tenantId, Func tenantAction) TenantAccessor.CurrentTenantId = null; } } - - return result ?? Task.CompletedTask; } } } diff --git a/Core/Stateflows/DependencyInjection.cs b/Core/Stateflows/DependencyInjection.cs index 32394991..f8b67325 100644 --- a/Core/Stateflows/DependencyInjection.cs +++ b/Core/Stateflows/DependencyInjection.cs @@ -17,7 +17,7 @@ using ActivityTracer = Stateflows.Activities.Engine.Tracer; using StateMachineTracer = Stateflows.StateMachines.Engine.Tracer; using Stateflows.StateMachines; -using Stateflows.Common.Trace; +using Stateflows.Common.Subscription; namespace Stateflows { @@ -32,6 +32,9 @@ internal static IStateflowsBuilder EnsureStateflowServices(this IStateflowsBuild .ServiceCollection .AddSingleton() .AddHostedService(provider => provider.GetService()) + .AddSingleton() + .AddHostedService(provider => provider.GetService()) + .AddSingleton(provider => provider.GetService()) .AddHostedService() .AddHostedService() .AddTransient() @@ -72,9 +75,16 @@ public static IServiceCollection AddStateflows(this IServiceCollection services, return services; } - public static IStateflowsBuilder AddDefaultInstance(this IStateflowsBuilder stateflowsBuilder, BehaviorClass behaviorClass, InitializationRequestFactoryAsync initializationRequestFactoryAsync = null) + public static IStateflowsBuilder AddDefaultInstance(this IStateflowsBuilder stateflowsBuilder, BehaviorClass behaviorClass, DefaultInstanceInitializationRequestFactoryAsync initializationRequestFactoryAsync = null) { - BehaviorClassesInitializations.Instance.Initialize(behaviorClass, initializationRequestFactoryAsync); + BehaviorClassesInitializations.Instance.AddDefaultInstanceInitialization(behaviorClass, initializationRequestFactoryAsync); + + return stateflowsBuilder; + } + + public static IStateflowsBuilder AddAutoInitialization(this IStateflowsBuilder stateflowsBuilder, BehaviorClass behaviorClass, AutoInitializationRequestFactoryAsync initializationRequestFactoryAsync = null) + { + BehaviorClassesInitializations.Instance.AddAutoInitialization(behaviorClass, initializationRequestFactoryAsync); return stateflowsBuilder; } diff --git a/Core/Stateflows/StateMachines/Context/Classes/BaseContext.cs b/Core/Stateflows/StateMachines/Context/Classes/BaseContext.cs index bcf94df5..df3fa194 100644 --- a/Core/Stateflows/StateMachines/Context/Classes/BaseContext.cs +++ b/Core/Stateflows/StateMachines/Context/Classes/BaseContext.cs @@ -5,7 +5,7 @@ namespace Stateflows.StateMachines.Context.Classes { - internal class BaseContext : IStateflowsContextProvider + internal class BaseContext : IStateflowsContextProvider, IBehaviorLocator { public BaseContext(RootContext context) { diff --git a/Core/Stateflows/StateMachines/Context/Classes/EmbeddedBehaviorStatus.cs b/Core/Stateflows/StateMachines/Context/Classes/EmbeddedBehaviorStatus.cs new file mode 100644 index 00000000..158e57af --- /dev/null +++ b/Core/Stateflows/StateMachines/Context/Classes/EmbeddedBehaviorStatus.cs @@ -0,0 +1,18 @@ +using System; +using System.Linq; + +namespace Stateflows.StateMachines.Context.Classes +{ + public class EmbeddedBehaviorStatus + { + public bool ShouldSerializeExpectedEvents() + => ExpectedEvents.Any(); + + public string[] ExpectedEvents { get; set; } = Array.Empty(); + + public bool ShouldSerializeStatesStack() + => StatesStack.Any(); + + public string[] StatesStack { get; set; } = Array.Empty(); + } +} diff --git a/Core/Stateflows/StateMachines/Context/Classes/RootContext.cs b/Core/Stateflows/StateMachines/Context/Classes/RootContext.cs index aca95a57..4b283eae 100644 --- a/Core/Stateflows/StateMachines/Context/Classes/RootContext.cs +++ b/Core/Stateflows/StateMachines/Context/Classes/RootContext.cs @@ -49,6 +49,28 @@ public List DeferredEvents } } + private Dictionary embeddedBehaviorStatuses = null; + public Dictionary EmbeddedBehaviorStatuses + { + get + { + if (embeddedBehaviorStatuses == null) + { + if (!Context.Values.TryGetValue(Constants.EmbeddedBehaviorStatuses, out var stateContextsObj)) + { + embeddedBehaviorStatuses = new Dictionary(); + Context.Values[Constants.EmbeddedBehaviorStatuses] = embeddedBehaviorStatuses; + } + else + { + embeddedBehaviorStatuses = stateContextsObj as Dictionary; + } + } + + return embeddedBehaviorStatuses; + } + } + private Dictionary stateValues = null; public Dictionary StateValues { @@ -130,11 +152,7 @@ public void ClearEvent() ? EventsStack.Peek() : null; - public string SourceState { get; set; } = string.Empty; - - public string TargetState { get; set; } = string.Empty; - - public bool ForceConsumed { get; set; } = false; + public EventStatus? ForceStatus { get; set; } = null; public async Task Send(TEvent @event) where TEvent : Event, new() diff --git a/Core/Stateflows/StateMachines/Context/Classes/StateMachineContext.cs b/Core/Stateflows/StateMachines/Context/Classes/StateMachineContext.cs index f95c5a68..0e300830 100644 --- a/Core/Stateflows/StateMachines/Context/Classes/StateMachineContext.cs +++ b/Core/Stateflows/StateMachines/Context/Classes/StateMachineContext.cs @@ -1,14 +1,24 @@ -using Stateflows.Common; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Stateflows.Common; using Stateflows.Common.Classes; using Stateflows.Common.Interfaces; +using Stateflows.Common.Subscription; +using Stateflows.Common.Context.Interfaces; using Stateflows.StateMachines.Inspection.Interfaces; namespace Stateflows.StateMachines.Context.Classes { internal class StateMachineContext : BaseContext, IStateMachineInspectionContext { + BehaviorId IBehaviorContext.Id => Context.Id; + public StateMachineId Id => Context.Id; + private BehaviorSubscriber subscriber; + private BehaviorSubscriber Subscriber + => subscriber ??= new BehaviorSubscriber(Id, Context.Context, this, Context.Executor.ServiceProvider.GetRequiredService()); + public StateMachineContext(RootContext context) : base(context) { Values = new ContextValues(Context.GlobalValues); @@ -21,5 +31,17 @@ public StateMachineContext(RootContext context) : base(context) public void Send(TEvent @event) where TEvent : Event, new() => _ = Context.Send(@event); + + public void Publish(TNotification notification) + where TNotification : Notification, new() + => _ = Subscriber.PublishAsync(Id, notification); + + public Task> SubscribeAsync(BehaviorId behaviorId) + where TNotification : Notification, new() + => Subscriber.SubscribeAsync(behaviorId); + + public Task> UnsubscribeAsync(BehaviorId behaviorId) + where TNotification : Notification, new() + => Subscriber.UnsubscribeAsync(behaviorId); } } diff --git a/Core/Stateflows/StateMachines/Context/Classes/StateValues.cs b/Core/Stateflows/StateMachines/Context/Classes/StateValues.cs index ff8729b7..6bd41803 100644 --- a/Core/Stateflows/StateMachines/Context/Classes/StateValues.cs +++ b/Core/Stateflows/StateMachines/Context/Classes/StateValues.cs @@ -11,10 +11,10 @@ public bool ShouldSerializeValues() public Dictionary Values { get; set; } = new Dictionary(); - public bool ShouldSerializeSubmachineId() - => SubmachineId != null; + public bool ShouldSerializeBehaviorId() + => BehaviorId != null; - public StateMachineId? SubmachineId { get; set; } = null; + public BehaviorId? BehaviorId { get; set; } = null; public bool ShouldSerializeTimeEventIds() => TimeEventIds.Any(); diff --git a/Core/Stateflows/StateMachines/Context/Interfaces/IStateMachineContext.cs b/Core/Stateflows/StateMachines/Context/Interfaces/IStateMachineContext.cs index 03ec1245..f8f907b9 100644 --- a/Core/Stateflows/StateMachines/Context/Interfaces/IStateMachineContext.cs +++ b/Core/Stateflows/StateMachines/Context/Interfaces/IStateMachineContext.cs @@ -1,15 +1,9 @@ -using Stateflows.Common; -using Stateflows.Common.Interfaces; +using Stateflows.Common.Context.Interfaces; namespace Stateflows.StateMachines.Context.Interfaces { - public interface IStateMachineContext + public interface IStateMachineContext : IBehaviorContext { - StateMachineId Id { get; } - - IContextValues Values { get; } - - void Send(TEvent @event) - where TEvent : Event, new(); + new StateMachineId Id { get; } } } diff --git a/Core/Stateflows/StateMachines/Engine/Executor.cs b/Core/Stateflows/StateMachines/Engine/Executor.cs index d50f05ba..d27afae1 100644 --- a/Core/Stateflows/StateMachines/Engine/Executor.cs +++ b/Core/Stateflows/StateMachines/Engine/Executor.cs @@ -13,6 +13,8 @@ using Stateflows.StateMachines.Registration; using Stateflows.StateMachines.Context.Classes; using Stateflows.StateMachines.Context.Interfaces; +using Stateflows.Common.Initializer; +using Stateflows.Activities.Engine; namespace Stateflows.StateMachines.Engine { @@ -20,6 +22,8 @@ internal sealed class Executor : IDisposable { public readonly Graph Graph; + public bool StateHasChanged = false; + public StateMachinesRegister Register { get; set; } public IServiceProvider ServiceProvider => Scope.ServiceProvider; @@ -65,6 +69,7 @@ public void RebuildVerticesStack() while (Context.StatesStack.Count > i) { Context.StatesStack.RemoveAt(i); + StateHasChanged = true; } break; @@ -78,7 +83,11 @@ public IEnumerable GetStateStack() => VerticesStack.Select(vertex => vertex.Name).ToArray(); public Task HydrateAsync() - => Inspector.AfterHydrateAsync(new StateMachineActionContext(Context)); + { + RebuildVerticesStack(); + + return Inspector.AfterHydrateAsync(new StateMachineActionContext(Context)); + } public Task DehydrateAsync() => Inspector.BeforeDehydrateAsync(new StateMachineActionContext(Context)); @@ -116,7 +125,7 @@ public async Task InitializeAsync(InitializationRequest @event) return false; } - public async Task ExitAsync() + public async Task ExitAsync() { Debug.Assert(Context != null, $"Context is unavailable. Is state machine '{Graph.Name}' hydrated?"); @@ -130,17 +139,25 @@ public async Task ExitAsync() await DoFinalizeStateMachineAsync(); Context.StatesStack.Clear(); + StateHasChanged = true; + + return true; } + + return false; } - public void Reset() + public void Reset(bool keepVersion) { Debug.Assert(Context != null, $"Context is unavailable. Is state machine '{Graph.Name}' hydrated?"); if (Initialized) { Context.Context.Values.Clear(); - Context.Context.Version = 0; + if (!keepVersion) + { + Context.Context.Version = 0; + } } } @@ -155,6 +172,7 @@ private async Task DoInitializeCascadeAsync(Vertex vertex) await DoEntryAsync(vertex); Context.StatesStack.Add(vertex.Identifier); + StateHasChanged = true; vertex = vertex.InitialVertex; } @@ -175,11 +193,19 @@ private async Task DoInitializeCascadeAsync2(Vertex vertex) } Context.StatesStack.Add(vertex.Identifier); + StateHasChanged = true; vertex = vertex.InitialVertex; } } + public IEnumerable GetExpectedEventNames() + => GetExpectedEvents() + .Where(type => !type.IsSubclassOf(typeof(TimeEvent))) + .Where(type => type != typeof(CompletionEvent)) + .Select(type => type.GetEventName()) + .ToArray(); + public IEnumerable GetExpectedEvents() { var currentStack = VerticesStack.ToList(); @@ -190,7 +216,8 @@ public IEnumerable GetExpectedEvents() return currentStack .SelectMany(vertex => vertex.Edges.Values) .Select(edge => edge.TriggerType) - .Distinct(); + .Distinct() + .ToArray(); } else { @@ -214,6 +241,8 @@ private List GetNestedVertices(Vertex vertex) public async Task ProcessAsync(TEvent @event) where TEvent : Event, new() { + StateHasChanged = false; + var result = EventStatus.Rejected; if (Initialized) @@ -276,9 +305,6 @@ private async Task DoProcessAsync(TEvent @event) { foreach (var edge in vertex.OrderedEdges) { - Context.SourceState = edge.SourceName; - Context.TargetState = edge.TargetName; - if (@event.Triggers(edge) && await DoGuardAsync(edge)) { await DoConsumeAsync(edge); @@ -450,6 +476,7 @@ private async Task DoConsumeAsync(Edge edge) { await DoExitAsync(vertex); Context.StatesStack.RemoveAt(Context.StatesStack.Count - 1); + StateHasChanged = true; } } @@ -467,6 +494,7 @@ private async Task DoConsumeAsync(Edge edge) if (vertex != enteringVertices.Last()) { Context.StatesStack.Add(vertex.Identifier); + StateHasChanged = true; } } diff --git a/Core/Stateflows/StateMachines/Engine/Plugins/Submachines.cs b/Core/Stateflows/StateMachines/Engine/Plugins/Behaviors.cs similarity index 52% rename from Core/Stateflows/StateMachines/Engine/Plugins/Submachines.cs rename to Core/Stateflows/StateMachines/Engine/Plugins/Behaviors.cs index d75260c5..dd27709b 100644 --- a/Core/Stateflows/StateMachines/Engine/Plugins/Submachines.cs +++ b/Core/Stateflows/StateMachines/Engine/Plugins/Behaviors.cs @@ -1,40 +1,39 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; using Stateflows.Common; -using Stateflows.Common.Events; -using Stateflows.StateMachines.Events; using Stateflows.StateMachines.Context.Classes; using Stateflows.StateMachines.Context.Interfaces; -using System; namespace Stateflows.StateMachines.Engine { - internal class Submachines : IStateMachinePlugin + internal class Behaviors : IStateMachinePlugin { - private StateMachineId GetSubmachineId(string submachineName, StateMachineId hostId, string stateName) - => new StateMachineId(submachineName, $"__submachine:{hostId.Name}:{hostId.Instance}:{stateName}"); - - public async Task AfterStateEntryAsync(IStateActionContext context) + public Task AfterStateEntryAsync(IStateActionContext context) { var vertex = (context as StateActionContext).Vertex; var stateValues = (context as IRootContext).Context.GetStateValues(vertex.Name); - if (vertex.SubmachineName != null) + if (vertex.BehaviorName != null) { - var submachineId = GetSubmachineId( - vertex.SubmachineName, - context.StateMachine.Id, - context.CurrentState.Name - ); + var behaviorId = vertex.GetBehaviorId(context.StateMachine.Id); - if (context.TryLocateStateMachine(submachineId, out var stateMachine)) + if (context.TryLocateBehavior(behaviorId, out var behavior)) { - stateValues.SubmachineId = submachineId; - var initializationRequest = vertex.SubmachineInitializationBuilder?.Invoke(context) ?? new InitializationRequest(); - await stateMachine.InitializeAsync(initializationRequest); + stateValues.BehaviorId = behaviorId; + + if (vertex.BehaviorSubscriptions.Any()) + { + _ = behavior.SendAsync(vertex.GetSubscriptionRequest(context.StateMachine.Id)); + } + + var initializationRequest = vertex.BehaviorInitializationBuilder?.Invoke(context) ?? new InitializationRequest(); + _ = behavior.InitializeAsync(initializationRequest); } } + + return Task.CompletedTask; } public Task AfterStateExitAsync(IStateActionContext context) @@ -58,83 +57,39 @@ public Task AfterTransitionEffectAsync(ITransitionContext context) public Task AfterTransitionGuardAsync(IGuardContext context, bool guardResult) => Task.CompletedTask; - private CurrentStateResponse SubmachineState = null; - - public async Task BeforeProcessEventAsync(IEventContext context) - { - var rootContext = (context as IRootContext).Context; - var stateName = rootContext.StatesStack.LastOrDefault(); - if ( - stateName != null && - rootContext.Executor.Graph.AllVertices.TryGetValue(stateName, out var vertex) && - vertex.SubmachineName != null - ) - { - var stateValues = rootContext.GetStateValues(stateName); - - if ( - stateValues.SubmachineId.HasValue && - context.TryLocateStateMachine(stateValues.SubmachineId.Value, out var stateMachine) - ) - { - var consumed = false; - if (context.Event.Name == EventInfo.Name) - { - var @event = new CurrentStateRequest(); - var result = await stateMachine.SendAsync(@event); - if (result.Status == EventStatus.Consumed && @event.Response != null) - { - SubmachineState = @event.Response; - } - } - else - { - var result = await stateMachine.SendAsync(context.Event); - consumed = result.Status == EventStatus.Consumed; - } - - (context as IRootContext).Context.ForceConsumed = consumed; - - return !consumed; - } - } - - return true; - } + public Task BeforeProcessEventAsync(IEventContext context) + => Task.FromResult(true); public Task AfterProcessEventAsync(IEventContext context) - { - if (context.Event.Name == EventInfo.Name && SubmachineState != null) - { - var currentState = (context.Event as CurrentStateRequest).Response; - currentState.StatesStack = currentState.StatesStack.Concat(SubmachineState.StatesStack); - currentState.ExpectedEvents = currentState.ExpectedEvents.Concat(SubmachineState.ExpectedEvents); - SubmachineState = null; - } - - return Task.CompletedTask; - } + => Task.CompletedTask; public Task BeforeStateEntryAsync(IStateActionContext context) => Task.CompletedTask; - public async Task BeforeStateExitAsync(IStateActionContext context) + public Task BeforeStateExitAsync(IStateActionContext context) { var vertex = (context as StateActionContext).Vertex; - if (vertex.SubmachineName != null) + if (vertex.BehaviorName != null) { var stateValues = (context as IRootContext).Context.GetStateValues(vertex.Name); if ( - stateValues.SubmachineId.HasValue && - context.TryLocateStateMachine(stateValues.SubmachineId.Value, out var stateMachine) + stateValues.BehaviorId.HasValue && + context.TryLocateBehavior(stateValues.BehaviorId.Value, out var behavior) ) { - await stateMachine.SendAsync(new ExitEvent()); - stateValues.SubmachineId = null; + if (vertex.BehaviorSubscriptions.Any()) + { + _ = behavior.SendAsync(vertex.GetUnsubscriptionRequest(context.StateMachine.Id)); + } + + _ = behavior.SendAsync(new FinalizationRequest()); + stateValues.BehaviorId = null; } } + + return Task.CompletedTask; } public Task BeforeStateInitializeAsync(IStateActionContext context) diff --git a/Core/Stateflows/StateMachines/Engine/Plugins/Notifications.cs b/Core/Stateflows/StateMachines/Engine/Plugins/Notifications.cs new file mode 100644 index 00000000..01409d9d --- /dev/null +++ b/Core/Stateflows/StateMachines/Engine/Plugins/Notifications.cs @@ -0,0 +1,131 @@ +using System; +using System.Threading.Tasks; +using Stateflows.Common; +using Stateflows.StateMachines.Events; +using Stateflows.StateMachines.Extensions; +using Stateflows.StateMachines.Context.Interfaces; +using System.Diagnostics; + +namespace Stateflows.StateMachines.Engine +{ + internal class Notifications : IStateMachinePlugin + { + public Task AfterStateEntryAsync(IStateActionContext context) + => Task.CompletedTask; + + public Task AfterStateExitAsync(IStateActionContext context) + => Task.CompletedTask; + + public Task AfterStateInitializeAsync(IStateActionContext context) + => Task.CompletedTask; + + public Task AfterStateFinalizeAsync(IStateActionContext context) + => Task.CompletedTask; + + public Task AfterStateMachineInitializeAsync(IStateMachineInitializationContext context) + { + var executor = context.StateMachine.GetExecutor(); + var notification = new BehaviorStatusNotification() + { + BehaviorStatus = BehaviorStatus.Initialized, + ExpectedEvents = executor.GetExpectedEventNames() + }; + + context.StateMachine.Publish(notification); + + return Task.CompletedTask; + } + + public Task AfterStateMachineFinalizeAsync(IStateMachineActionContext context) + { + var notification = new BehaviorStatusNotification() { BehaviorStatus = BehaviorStatus.Finalized }; + + context.StateMachine.Publish(notification); + + return Task.CompletedTask; + } + + public Task AfterTransitionEffectAsync(ITransitionContext context) + => Task.CompletedTask; + + public Task AfterTransitionGuardAsync(IGuardContext context, bool guardResult) + => Task.CompletedTask; + + public Task BeforeProcessEventAsync(IEventContext context) + => Task.FromResult(true); + + public Task AfterProcessEventAsync(IEventContext context) + { + var executor = context.StateMachine.GetExecutor(); + if (executor.StateHasChanged) + { + var notification = new CurrentStateNotification() + { + BehaviorStatus = executor.BehaviorStatus, + StatesStack = executor.GetStateStack(), + ExpectedEvents = executor.GetExpectedEventNames(), + }; + + context.StateMachine.Publish(notification); + + Debug.WriteLine($"--> current state notification published {DateTime.Now}"); + } + + return Task.CompletedTask; + } + + public Task BeforeStateEntryAsync(IStateActionContext context) + => Task.CompletedTask; + + public Task BeforeStateExitAsync(IStateActionContext context) + => Task.CompletedTask; + + public Task BeforeStateInitializeAsync(IStateActionContext context) + => Task.CompletedTask; + + public Task BeforeStateFinalizeAsync(IStateActionContext context) + => Task.CompletedTask; + + public Task BeforeStateMachineInitializeAsync(IStateMachineInitializationContext context) + => Task.CompletedTask; + + public Task BeforeStateMachineFinalizeAsync(IStateMachineActionContext context) + => Task.CompletedTask; + + public Task BeforeTransitionEffectAsync(ITransitionContext context) + => Task.CompletedTask; + + public Task BeforeTransitionGuardAsync(IGuardContext context) + => Task.CompletedTask; + + public Task AfterHydrateAsync(IStateMachineActionContext context) + => Task.CompletedTask; + + public Task BeforeDehydrateAsync(IStateMachineActionContext context) + => Task.CompletedTask; + + public Task OnStateMachineInitializationExceptionAsync(IStateMachineInitializationContext context, Exception exception) + => Task.CompletedTask; + + public Task OnStateMachineFinalizationExceptionAsync(IStateMachineActionContext context, Exception exception) + => Task.CompletedTask; + + public Task OnTransitionGuardExceptionAsync(IGuardContext context, Exception exception) + => Task.CompletedTask; + + public Task OnTransitionEffectExceptionAsync(ITransitionContext context, Exception exception) + => Task.CompletedTask; + + public Task OnStateInitializationExceptionAsync(IStateActionContext context, Exception exception) + => Task.CompletedTask; + + public Task OnStateFinalizationExceptionAsync(IStateActionContext context, Exception exception) + => Task.CompletedTask; + + public Task OnStateEntryExceptionAsync(IStateActionContext context, Exception exception) + => Task.CompletedTask; + + public Task OnStateExitExceptionAsync(IStateActionContext context, Exception exception) + => Task.CompletedTask; + } +} diff --git a/Core/Stateflows/StateMachines/Engine/Processor.cs b/Core/Stateflows/StateMachines/Engine/Processor.cs index 1bb87379..152e4075 100644 --- a/Core/Stateflows/StateMachines/Engine/Processor.cs +++ b/Core/Stateflows/StateMachines/Engine/Processor.cs @@ -11,6 +11,7 @@ using Stateflows.StateMachines.Models; using Stateflows.Common.Extensions; using System.ComponentModel.DataAnnotations; +using Stateflows.Common.Initializer; namespace Stateflows.StateMachines.Engine { @@ -73,9 +74,13 @@ public async Task ProcessEventAsync(BehaviorId id, TEvent @ { ev.Headers.AddRange(@event.Headers); + executor.Context.SetEvent(ev); + var status = await ExecuteBehaviorAsync(ev, result, stateflowsContext, graph, executor); results.Add(new RequestResult(ev, ev.GetResponse(), status, new EventValidation(true, new List()))); + + executor.Context.ClearEvent(); } compoundRequest.Respond(new CompoundResponse() @@ -91,8 +96,6 @@ public async Task ProcessEventAsync(BehaviorId id, TEvent @ await executor.DehydrateAsync(); await storage.DehydrateAsync(executor.Context.Context); - - executor.Context.ClearEvent(); } return result; @@ -100,14 +103,25 @@ public async Task ProcessEventAsync(BehaviorId id, TEvent @ private async Task ExecuteBehaviorAsync(TEvent @event, EventStatus result, StateflowsContext stateflowsContext, Graph graph, Executor executor) where TEvent : Event, new() { - executor.RebuildVerticesStack(); - - executor.Context.SetEvent(@event); - var eventContext = new EventContext(executor.Context); if (await executor.Inspector.BeforeProcessEventAsync(eventContext)) { + if (!executor.Initialized) + { + var token = BehaviorClassesInitializations.Instance.AutoInitializationTokens.Find(token => token.BehaviorClass == executor.Context.Id.StateMachineClass); + + InitializationRequest initializationRequest; + if (token != null && (initializationRequest = await token.InitializationRequestFactory.Invoke(executor.ServiceProvider, executor.Context.Id)) != null) + { + executor.Context.SetEvent(initializationRequest); + + await executor.InitializeAsync(initializationRequest); + + executor.Context.ClearEvent(); + } + } + result = await TryHandleEventAsync(eventContext); if (result != EventStatus.Consumed) @@ -119,9 +133,9 @@ public async Task ProcessEventAsync(BehaviorId id, TEvent @ } else { - if (executor.Context.ForceConsumed) + if (executor.Context.ForceStatus != null) { - result = EventStatus.Consumed; + result = (EventStatus)executor.Context.ForceStatus; } } @@ -131,14 +145,12 @@ public async Task ProcessEventAsync(BehaviorId id, TEvent @ stateflowsContext.Version = stateflowsContext.Status switch { - BehaviorStatus.NotInitialized => 0, + BehaviorStatus.NotInitialized => stateflowsContext.Version, BehaviorStatus.Initialized => graph.Version, BehaviorStatus.Finalized => graph.Version, _ => 0 }; - executor.Context.ClearEvent(); - return result; } } diff --git a/Core/Stateflows/StateMachines/EventHandlers/BehaviorStatusHandler.cs b/Core/Stateflows/StateMachines/EventHandlers/BehaviorStatusRequestHandler.cs similarity index 61% rename from Core/Stateflows/StateMachines/EventHandlers/BehaviorStatusHandler.cs rename to Core/Stateflows/StateMachines/EventHandlers/BehaviorStatusRequestHandler.cs index 53ecde38..324760f0 100644 --- a/Core/Stateflows/StateMachines/EventHandlers/BehaviorStatusHandler.cs +++ b/Core/Stateflows/StateMachines/EventHandlers/BehaviorStatusRequestHandler.cs @@ -1,12 +1,14 @@ using System; +using System.Linq; using System.Threading.Tasks; using Stateflows.Common; +using Stateflows.StateMachines.Events; using Stateflows.StateMachines.Extensions; using Stateflows.StateMachines.Inspection.Interfaces; namespace Stateflows.StateMachines.EventHandlers { - internal class BehaviorStatusHandler : IStateMachineEventHandler + internal class BehaviorStatusRequestHandler : IStateMachineEventHandler { public Type EventType => typeof(BehaviorStatusRequest); @@ -15,7 +17,13 @@ public Task TryHandleEventAsync(IEventInspectionContext typeof(CurrentStateRequest); @@ -22,11 +22,7 @@ public Task TryHandleEventAsync(IEventInspectionContext !type.IsSubclassOf(typeof(TimeEvent))) - .Where(type => type != typeof(CompletionEvent)) - .Select(type => type.GetEventName()) - .ToArray(), + ExpectedEvents = executor.GetExpectedEventNames(), BehaviorStatus = executor.BehaviorStatus }; diff --git a/Core/Stateflows/StateMachines/EventHandlers/ExitHandler.cs b/Core/Stateflows/StateMachines/EventHandlers/FinalizationHandler.cs similarity index 58% rename from Core/Stateflows/StateMachines/EventHandlers/ExitHandler.cs rename to Core/Stateflows/StateMachines/EventHandlers/FinalizationHandler.cs index 31cd2224..620c0e35 100644 --- a/Core/Stateflows/StateMachines/EventHandlers/ExitHandler.cs +++ b/Core/Stateflows/StateMachines/EventHandlers/FinalizationHandler.cs @@ -1,24 +1,23 @@ using System; using System.Threading.Tasks; using Stateflows.Common; -using Stateflows.Common.Events; using Stateflows.StateMachines.Extensions; using Stateflows.StateMachines.Inspection.Interfaces; namespace Stateflows.StateMachines.EventHandlers { - internal class ExitHandler : IStateMachineEventHandler + internal class FinalizationHandler : IStateMachineEventHandler { - public Type EventType => typeof(ExitEvent); + public Type EventType => typeof(FinalizationRequest); public async Task TryHandleEventAsync(IEventInspectionContext context) where TEvent : Event, new() { - if (context.Event is ExitEvent) + if (context.Event is FinalizationRequest request) { - var executor = context.StateMachine.GetExecutor(); + var finalized = await context.StateMachine.GetExecutor().ExitAsync(); - await executor.ExitAsync(); + request.Respond(new FinalizationResponse() { FinalizationSuccessful = finalized }); return EventStatus.Consumed; } diff --git a/Core/Stateflows/StateMachines/EventHandlers/NotificationsHandler.cs b/Core/Stateflows/StateMachines/EventHandlers/NotificationsHandler.cs new file mode 100644 index 00000000..bb12c929 --- /dev/null +++ b/Core/Stateflows/StateMachines/EventHandlers/NotificationsHandler.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using Stateflows.Common; +using Stateflows.StateMachines.Inspection.Interfaces; + +namespace Stateflows.StateMachines.EventHandlers +{ + internal class NotificationsHandler : IStateMachineEventHandler + { + public Type EventType => typeof(NotificationsRequest); + + public Task TryHandleEventAsync(IEventInspectionContext context) + where TEvent : Event, new() + => Task.FromResult(context.Event is NotificationsRequest + ? EventStatus.Consumed + : EventStatus.NotConsumed + ); + } +} diff --git a/Core/Stateflows/StateMachines/EventHandlers/ResetHandler.cs b/Core/Stateflows/StateMachines/EventHandlers/ResetHandler.cs index 6bdff91c..fd353871 100644 --- a/Core/Stateflows/StateMachines/EventHandlers/ResetHandler.cs +++ b/Core/Stateflows/StateMachines/EventHandlers/ResetHandler.cs @@ -15,7 +15,7 @@ public Task TryHandleEventAsync(IEventInspectionContext typeof(SubscriptionRequest); + + public Task TryHandleEventAsync(IEventInspectionContext context) + where TEvent : Event, new() + { + if (context.Event is SubscriptionRequest request) + { + var result = context.StateMachine.GetExecutor().Context.Context.AddSubscribers(request.BehaviorId, request.NotificationNames); + + request.Respond(new SubscriptionResponse() { SubscriptionSuccessful = result }); + + return Task.FromResult(EventStatus.Consumed); + } + + return Task.FromResult(EventStatus.NotConsumed); + } + } +} diff --git a/Core/Stateflows/StateMachines/EventHandlers/UnsubscriptionHandler.cs b/Core/Stateflows/StateMachines/EventHandlers/UnsubscriptionHandler.cs new file mode 100644 index 00000000..2c85114d --- /dev/null +++ b/Core/Stateflows/StateMachines/EventHandlers/UnsubscriptionHandler.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using Stateflows.Common; +using Stateflows.StateMachines.Extensions; +using Stateflows.StateMachines.Inspection.Interfaces; + +namespace Stateflows.StateMachines.EventHandlers +{ + internal class UnsubscriptionHandler : IStateMachineEventHandler + { + public Type EventType => typeof(UnsubscriptionRequest); + + public Task TryHandleEventAsync(IEventInspectionContext context) + where TEvent : Event, new() + { + if (context.Event is UnsubscriptionRequest request) + { + var result = context.StateMachine.GetExecutor().Context.Context.RemoveSubscribers(request.BehaviorId, request.NotificationNames); + + request.Respond(new UnsubscriptionResponse() { UnsubscriptionSuccessful = result }); + + return Task.FromResult(EventStatus.Consumed); + } + + return Task.FromResult(EventStatus.NotConsumed); + } + } +} diff --git a/Core/Stateflows/StateMachines/Helpers/TransitionEffect.cs b/Core/Stateflows/StateMachines/Helpers/TransitionEffect.cs index 33b2333b..92770219 100644 --- a/Core/Stateflows/StateMachines/Helpers/TransitionEffect.cs +++ b/Core/Stateflows/StateMachines/Helpers/TransitionEffect.cs @@ -7,7 +7,7 @@ namespace Stateflows.StateMachines { public static class TransitionEffect { - public static Task Empty(ITransitionContext context) + public static Task Empty(ITransitionContext _) where TEvent : Event, new() => Task.CompletedTask; diff --git a/Core/Stateflows/StateMachines/Helpers/TransitionGuard.cs b/Core/Stateflows/StateMachines/Helpers/TransitionGuard.cs index 876e7a4b..cfea5749 100644 --- a/Core/Stateflows/StateMachines/Helpers/TransitionGuard.cs +++ b/Core/Stateflows/StateMachines/Helpers/TransitionGuard.cs @@ -11,7 +11,7 @@ public static Task Empty(IGuardContext context) where TEvent : Event, new() => Allow(context); - public static Task Allow(IGuardContext context) + public static Task Allow(IGuardContext _) where TEvent : Event, new() => Task.FromResult(true); diff --git a/Core/Stateflows/StateMachines/Inspection/Classes/StateInspection.cs b/Core/Stateflows/StateMachines/Inspection/Classes/StateInspection.cs index 9465c6bb..b51dbcea 100644 --- a/Core/Stateflows/StateMachines/Inspection/Classes/StateInspection.cs +++ b/Core/Stateflows/StateMachines/Inspection/Classes/StateInspection.cs @@ -32,7 +32,7 @@ public StateInspection(Executor executor, Vertex vertex) private IEnumerable transitions; public IEnumerable Transitions - => transitions ??= Vertex.Edges.Values.Select(e => new TransitionInspection(Executor, e)); + => transitions ??= Vertex.Edges.Values.Select(e => new TransitionInspection(Executor, e)).ToArray(); private List actions; diff --git a/Core/Stateflows/StateMachines/Inspection/Classes/StateMachineInspection.cs b/Core/Stateflows/StateMachines/Inspection/Classes/StateMachineInspection.cs index 43ac2b94..c1b25e72 100644 --- a/Core/Stateflows/StateMachines/Inspection/Classes/StateMachineInspection.cs +++ b/Core/Stateflows/StateMachines/Inspection/Classes/StateMachineInspection.cs @@ -26,9 +26,29 @@ public StateMachineInspection(Executor executor) public IEnumerable States => states ??= Executor.Graph.Vertices.Values.Select(v => new StateInspection(Executor, v)).ToArray(); + public IEnumerable CurrentStatesStack + { + get + { + var result = new List(); + IEnumerable statesSet = States; + foreach (var vertex in Executor.VerticesStack) + { + var state = statesSet.First(s => s.Name == vertex.Name); + if (state != null) + { + result.Add(state); + statesSet = state.States; + } + } + return result; + } + } + public IActionInspection Initialize { get; set; } public IActionInspection Finalize { get; set; } + public bool StateHasChanged => Executor.StateHasChanged; } } diff --git a/Core/Stateflows/StateMachines/Inspection/Interfaces/IStateMachineInspection.cs b/Core/Stateflows/StateMachines/Inspection/Interfaces/IStateMachineInspection.cs index 0babc295..6509b9d3 100644 --- a/Core/Stateflows/StateMachines/Inspection/Interfaces/IStateMachineInspection.cs +++ b/Core/Stateflows/StateMachines/Inspection/Interfaces/IStateMachineInspection.cs @@ -8,6 +8,10 @@ public interface IStateMachineInspection IEnumerable States { get; } + IEnumerable CurrentStatesStack { get; } + IActionInspection Initialize { get; } + + bool StateHasChanged { get; } } } diff --git a/Core/Stateflows/StateMachines/Models/Graph.cs b/Core/Stateflows/StateMachines/Models/Graph.cs index 30e20ce7..f3ef11c6 100644 --- a/Core/Stateflows/StateMachines/Models/Graph.cs +++ b/Core/Stateflows/StateMachines/Models/Graph.cs @@ -95,7 +95,7 @@ public void Build() } else { - throw new TransitionDefinitionException($"Transition target state '{edge.TargetName}' is not registered in state machine '{Name}'", Class); + throw new TransitionDefinitionException($"Transition target state '{edge.TargetName}' is not registered", Class); } //var vertices = edge.Source.Parent?.Vertices ?? Vertices; @@ -117,14 +117,15 @@ public void Build() { var siblings = edge.Source.Edges.Values.Any(e => !e.IsElse && - e.Trigger == edge.Trigger && - e.Type == edge.Type + e.Trigger == edge.Trigger + //&& + //e.Type == edge.Type ); if (!siblings) { throw new TransitionDefinitionException( - $"Can't register else transition outgoing from state '{edge.SourceName}' in state machine '{Name}': there are no other transitions coming out from this state with same type and trigger", + $"Can't register else transition outgoing from state '{edge.SourceName}': there are no other transitions coming out from this state with same type and trigger", Class ); } diff --git a/Core/Stateflows/StateMachines/Models/Vertex.cs b/Core/Stateflows/StateMachines/Models/Vertex.cs index 6fa208d8..ae37b465 100644 --- a/Core/Stateflows/StateMachines/Models/Vertex.cs +++ b/Core/Stateflows/StateMachines/Models/Vertex.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using Stateflows.Common; using Stateflows.Common.Models; using Stateflows.StateMachines.Interfaces; using Stateflows.StateMachines.Registration; @@ -44,8 +46,30 @@ internal class Vertex public Dictionary Vertices { get; set; } = new Dictionary(); public List DeferredEvents { get; set; } = new List(); - public Dictionary SubmachineInitialValues { get; set; } - public StateActionInitializationBuilder SubmachineInitializationBuilder { get; set; } - public string SubmachineName { get; set; } + public StateActionInitializationBuilder BehaviorInitializationBuilder { get; set; } + public string BehaviorName { get; set; } + public string BehaviorType { get; set; } + public List BehaviorSubscriptions { get; set; } = new List(); + public List GetBehaviorSubscriptionNames() + => BehaviorSubscriptions + .Select(t => EventInfo.GetName(t)) + .ToList(); + + public SubscriptionRequest GetSubscriptionRequest(StateMachineId hostId) + => new SubscriptionRequest() + { + BehaviorId = hostId, + NotificationNames = GetBehaviorSubscriptionNames() + }; + + public UnsubscriptionRequest GetUnsubscriptionRequest(StateMachineId hostId) + => new UnsubscriptionRequest() + { + BehaviorId = hostId, + NotificationNames = GetBehaviorSubscriptionNames() + }; + + public BehaviorId GetBehaviorId(StateMachineId hostId) + => new BehaviorId(BehaviorType, BehaviorName, $"__stateBehavior:{hostId.Name}:{hostId.Instance}:{Name}"); } } diff --git a/Core/Stateflows/StateMachines/Registration/Builders/CompositeStateBuilder.cs b/Core/Stateflows/StateMachines/Registration/Builders/CompositeStateBuilder.cs index 08d2f7a3..0b7af0ef 100644 --- a/Core/Stateflows/StateMachines/Registration/Builders/CompositeStateBuilder.cs +++ b/Core/Stateflows/StateMachines/Registration/Builders/CompositeStateBuilder.cs @@ -127,22 +127,22 @@ public IInitializedCompositeStateBuilder AddElseTransition(string target } [DebuggerHidden] - public IInitializedCompositeStateBuilder AddDefaultTransition(string targetVertexName, TransitionBuildAction transitionBuildAction = null) - => AddTransition(targetVertexName, transitionBuildAction); + public IInitializedCompositeStateBuilder AddDefaultTransition(string targetVertexName, DefaultTransitionBuildAction transitionBuildAction = null) + => AddTransition(targetVertexName, builder => transitionBuildAction?.Invoke(builder as IDefaultTransitionBuilder)); [DebuggerHidden] - public IInitializedCompositeStateBuilder AddElseDefaultTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction = null) - => AddElseTransition(targetVertexName, transitionBuildAction); + public IInitializedCompositeStateBuilder AddElseDefaultTransition(string targetVertexName, ElseDefaultTransitionBuildAction transitionBuildAction = null) + => AddElseTransition(targetVertexName, builder => transitionBuildAction?.Invoke(builder as IElseDefaultTransitionBuilder)); [DebuggerHidden] - public IInitializedCompositeStateBuilder AddInternalTransition(TransitionBuildAction transitionBuildAction) + public IInitializedCompositeStateBuilder AddInternalTransition(InternalTransitionBuildAction transitionBuildAction) where TEvent : Event, new() - => AddTransition(Constants.DefaultTransitionTarget, transitionBuildAction); + => AddTransition(Constants.DefaultTransitionTarget, builder => transitionBuildAction?.Invoke(builder as IInternalTransitionBuilder)); [DebuggerHidden] - public IInitializedCompositeStateBuilder AddElseInternalTransition(ElseTransitionBuildAction transitionBuildAction) + public IInitializedCompositeStateBuilder AddElseInternalTransition(ElseInternalTransitionBuildAction transitionBuildAction) where TEvent : Event, new() - => AddElseTransition(Constants.DefaultTransitionTarget, transitionBuildAction); + => AddElseTransition(Constants.DefaultTransitionTarget, builder => transitionBuildAction?.Invoke(builder as IElseInternalTransitionBuilder)); [DebuggerHidden] ITypedInitializedCompositeStateBuilder IStateTransitions.AddTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) @@ -153,20 +153,20 @@ ITypedInitializedCompositeStateBuilder IStateTransitions AddElseTransition(targetVertexName, transitionBuildAction) as ITypedInitializedCompositeStateBuilder; [DebuggerHidden] - ITypedInitializedCompositeStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) + ITypedInitializedCompositeStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, DefaultTransitionBuildAction transitionBuildAction) => AddDefaultTransition(targetVertexName, transitionBuildAction) as ITypedInitializedCompositeStateBuilder; [DebuggerHidden] - ITypedInitializedCompositeStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) + ITypedInitializedCompositeStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseDefaultTransitionBuildAction transitionBuildAction) => AddElseDefaultTransition(targetVertexName, transitionBuildAction) as ITypedInitializedCompositeStateBuilder; [DebuggerHidden] - ITypedInitializedCompositeStateBuilder IStateTransitions.AddInternalTransition(TransitionBuildAction transitionBuildAction) - => AddInternalTransition(transitionBuildAction) as ITypedInitializedCompositeStateBuilder; + ITypedInitializedCompositeStateBuilder IStateTransitions.AddInternalTransition(InternalTransitionBuildAction transitionBuildAction) + => AddInternalTransition(builder => transitionBuildAction?.Invoke(builder as IInternalTransitionBuilder)) as ITypedInitializedCompositeStateBuilder; [DebuggerHidden] - ITypedInitializedCompositeStateBuilder IStateTransitions.AddElseInternalTransition(ElseTransitionBuildAction transitionBuildAction) - => AddElseInternalTransition(transitionBuildAction) as ITypedInitializedCompositeStateBuilder; + ITypedInitializedCompositeStateBuilder IStateTransitions.AddElseInternalTransition(ElseInternalTransitionBuildAction transitionBuildAction) + => AddElseInternalTransition(builder => transitionBuildAction?.Invoke(builder as IElseInternalTransitionBuilder)) as ITypedInitializedCompositeStateBuilder; #endregion #region AddState @@ -254,20 +254,20 @@ ICompositeStateBuilder IStateTransitions.AddElseTransiti => AddElseTransition(targetVertexName, transitionBuildAction) as ICompositeStateBuilder; [DebuggerHidden] - ICompositeStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) + ICompositeStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, DefaultTransitionBuildAction transitionBuildAction) => AddDefaultTransition(targetVertexName, transitionBuildAction) as ICompositeStateBuilder; [DebuggerHidden] - ICompositeStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) + ICompositeStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseDefaultTransitionBuildAction transitionBuildAction) => AddElseDefaultTransition(targetVertexName, transitionBuildAction) as ICompositeStateBuilder; [DebuggerHidden] - ICompositeStateBuilder IStateTransitions.AddInternalTransition(TransitionBuildAction transitionBuildAction) - => AddInternalTransition(transitionBuildAction) as ICompositeStateBuilder; + ICompositeStateBuilder IStateTransitions.AddInternalTransition(InternalTransitionBuildAction transitionBuildAction) + => AddInternalTransition(builder => transitionBuildAction?.Invoke(builder as IInternalTransitionBuilder)) as ICompositeStateBuilder; [DebuggerHidden] - ICompositeStateBuilder IStateTransitions.AddElseInternalTransition(ElseTransitionBuildAction transitionBuildAction) - => AddElseInternalTransition(transitionBuildAction) as ICompositeStateBuilder; + ICompositeStateBuilder IStateTransitions.AddElseInternalTransition(ElseInternalTransitionBuildAction transitionBuildAction) + => AddElseInternalTransition(builder => transitionBuildAction?.Invoke(builder as IElseInternalTransitionBuilder)) as ICompositeStateBuilder; [DebuggerHidden] ICompositeStateBuilder IStateUtils.AddDeferredEvent() @@ -290,20 +290,20 @@ ITypedCompositeStateBuilder IStateTransitions.AddEl => AddElseTransition(targetVertexName, transitionBuildAction) as ITypedCompositeStateBuilder; [DebuggerHidden] - ITypedCompositeStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) + ITypedCompositeStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, DefaultTransitionBuildAction transitionBuildAction) => AddDefaultTransition(targetVertexName, transitionBuildAction) as ITypedCompositeStateBuilder; [DebuggerHidden] - ITypedCompositeStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) + ITypedCompositeStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseDefaultTransitionBuildAction transitionBuildAction) => AddElseDefaultTransition(targetVertexName, transitionBuildAction) as ITypedCompositeStateBuilder; [DebuggerHidden] - ITypedCompositeStateBuilder IStateTransitions.AddInternalTransition(TransitionBuildAction transitionBuildAction) - => AddInternalTransition(transitionBuildAction) as ITypedCompositeStateBuilder; + ITypedCompositeStateBuilder IStateTransitions.AddInternalTransition(InternalTransitionBuildAction transitionBuildAction) + => AddInternalTransition(builder => transitionBuildAction?.Invoke(builder as IInternalTransitionBuilder)) as ITypedCompositeStateBuilder; [DebuggerHidden] - ITypedCompositeStateBuilder IStateTransitions.AddElseInternalTransition(ElseTransitionBuildAction transitionBuildAction) - => AddElseInternalTransition(transitionBuildAction) as ITypedCompositeStateBuilder; + ITypedCompositeStateBuilder IStateTransitions.AddElseInternalTransition(ElseInternalTransitionBuildAction transitionBuildAction) + => AddElseInternalTransition(builder => transitionBuildAction?.Invoke(builder as IElseInternalTransitionBuilder)) as ITypedCompositeStateBuilder; [DebuggerHidden] IFinalizedCompositeStateBuilder IStateEntry.AddOnEntry(Func actionAsync) @@ -326,20 +326,20 @@ IFinalizedCompositeStateBuilder IStateTransitions AddElseTransition(targetVertexName, transitionBuildAction) as IFinalizedCompositeStateBuilder; [DebuggerHidden] - IFinalizedCompositeStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) + IFinalizedCompositeStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, DefaultTransitionBuildAction transitionBuildAction) => AddDefaultTransition(targetVertexName, transitionBuildAction) as IFinalizedCompositeStateBuilder; [DebuggerHidden] - IFinalizedCompositeStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) + IFinalizedCompositeStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseDefaultTransitionBuildAction transitionBuildAction) => AddElseDefaultTransition(targetVertexName, transitionBuildAction) as IFinalizedCompositeStateBuilder; [DebuggerHidden] - IFinalizedCompositeStateBuilder IStateTransitions.AddInternalTransition(TransitionBuildAction transitionBuildAction) - => AddInternalTransition(transitionBuildAction) as IFinalizedCompositeStateBuilder; + IFinalizedCompositeStateBuilder IStateTransitions.AddInternalTransition(InternalTransitionBuildAction transitionBuildAction) + => AddInternalTransition(builder => transitionBuildAction?.Invoke(builder as IInternalTransitionBuilder)) as IFinalizedCompositeStateBuilder; [DebuggerHidden] - IFinalizedCompositeStateBuilder IStateTransitions.AddElseInternalTransition(ElseTransitionBuildAction transitionBuildAction) - => AddElseInternalTransition(transitionBuildAction) as IFinalizedCompositeStateBuilder; + IFinalizedCompositeStateBuilder IStateTransitions.AddElseInternalTransition(ElseInternalTransitionBuildAction transitionBuildAction) + => AddElseInternalTransition(builder => transitionBuildAction?.Invoke(builder as IElseInternalTransitionBuilder)) as IFinalizedCompositeStateBuilder; [DebuggerHidden] ITypedFinalizedCompositeStateBuilder IStateUtils.AddDeferredEvent() @@ -354,20 +354,20 @@ ITypedFinalizedCompositeStateBuilder IStateTransitions AddElseTransition(targetVertexName, transitionBuildAction) as ITypedFinalizedCompositeStateBuilder; [DebuggerHidden] - ITypedFinalizedCompositeStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) + ITypedFinalizedCompositeStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, DefaultTransitionBuildAction transitionBuildAction) => AddDefaultTransition(targetVertexName, transitionBuildAction) as ITypedFinalizedCompositeStateBuilder; [DebuggerHidden] - ITypedFinalizedCompositeStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) + ITypedFinalizedCompositeStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseDefaultTransitionBuildAction transitionBuildAction) => AddElseDefaultTransition(targetVertexName, transitionBuildAction) as ITypedFinalizedCompositeStateBuilder; [DebuggerHidden] - ITypedFinalizedCompositeStateBuilder IStateTransitions.AddInternalTransition(TransitionBuildAction transitionBuildAction) - => AddInternalTransition(transitionBuildAction) as ITypedFinalizedCompositeStateBuilder; + ITypedFinalizedCompositeStateBuilder IStateTransitions.AddInternalTransition(InternalTransitionBuildAction transitionBuildAction) + => AddInternalTransition(builder => transitionBuildAction?.Invoke(builder as IInternalTransitionBuilder)) as ITypedFinalizedCompositeStateBuilder; [DebuggerHidden] - ITypedFinalizedCompositeStateBuilder IStateTransitions.AddElseInternalTransition(ElseTransitionBuildAction transitionBuildAction) - => AddElseInternalTransition(transitionBuildAction) as ITypedFinalizedCompositeStateBuilder; + ITypedFinalizedCompositeStateBuilder IStateTransitions.AddElseInternalTransition(ElseInternalTransitionBuildAction transitionBuildAction) + => AddElseInternalTransition(builder => transitionBuildAction?.Invoke(builder as IElseInternalTransitionBuilder)) as ITypedFinalizedCompositeStateBuilder; [DebuggerHidden] IFinalizedCompositeStateBuilder ICompositeStateEvents.AddOnInitialize(Func actionAsync) diff --git a/Core/Stateflows/StateMachines/Registration/Builders/StateBuilder.cs b/Core/Stateflows/StateMachines/Registration/Builders/StateBuilder.cs index 7650e187..4b5d74f2 100644 --- a/Core/Stateflows/StateMachines/Registration/Builders/StateBuilder.cs +++ b/Core/Stateflows/StateMachines/Registration/Builders/StateBuilder.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Stateflows.Common; -using Stateflows.Common.Events; using Stateflows.Common.Registration; using Stateflows.StateMachines.Events; using Stateflows.StateMachines.Models; @@ -19,11 +18,12 @@ namespace Stateflows.StateMachines.Registration.Builders { internal partial class StateBuilder : IStateBuilder, - ISubmachineStateBuilder, + IBehaviorStateBuilder, ITypedStateBuilder, - ISubmachineTypedStateBuilder, + IBehaviorTypedStateBuilder, IInternal, - IBehaviorBuilder + IBehaviorBuilder, + IEmbeddedBehaviorBuilder { public Vertex Vertex { get; } @@ -144,7 +144,7 @@ public IStateBuilder AddOnExit(Func actionAsync) if (typeof(TEvent) == typeof(CompletionEvent)) throw new DeferralDefinitionException(EventInfo.Name, "Completion event cannot be deferred.", Vertex.Graph.Class); - if (typeof(TEvent) == typeof(ExitEvent)) + if (typeof(TEvent) == typeof(FinalizationRequest)) throw new DeferralDefinitionException(EventInfo.Name, "Exit event cannot be deferred.", Vertex.Graph.Class); if (typeof(TEvent).IsSubclassOf(typeof(TimeEvent))) @@ -204,25 +204,25 @@ public IStateBuilder AddTransition(string targetVertexName, TransitionBu [DebuggerHidden] public IStateBuilder AddElseTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction = null) where TEvent : Event, new() - => AddTransitionInternal(targetVertexName, true, builder => transitionBuildAction?.Invoke(builder)); + => AddTransitionInternal(targetVertexName, true, builder => transitionBuildAction?.Invoke(builder as IElseTransitionBuilder)); [DebuggerHidden] - public IStateBuilder AddDefaultTransition(string targetVertexName, TransitionBuildAction transitionBuildAction = null) - => AddTransition(targetVertexName, transitionBuildAction); + public IStateBuilder AddDefaultTransition(string targetVertexName, DefaultTransitionBuildAction transitionBuildAction = null) + => AddTransition(targetVertexName, builder => transitionBuildAction?.Invoke(builder as IDefaultTransitionBuilder)); [DebuggerHidden] - public IStateBuilder AddElseDefaultTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction = null) - => AddElseTransition(targetVertexName, transitionBuildAction); + public IStateBuilder AddElseDefaultTransition(string targetVertexName, ElseDefaultTransitionBuildAction transitionBuildAction = null) + => AddElseTransition(targetVertexName, builder => transitionBuildAction?.Invoke(builder as IElseDefaultTransitionBuilder)); [DebuggerHidden] - public IStateBuilder AddInternalTransition(TransitionBuildAction transitionBuildAction = null) + public IStateBuilder AddInternalTransition(InternalTransitionBuildAction transitionBuildAction) where TEvent : Event, new() - => AddTransition(Constants.DefaultTransitionTarget, transitionBuildAction); + => AddTransition(Constants.DefaultTransitionTarget, builder => transitionBuildAction?.Invoke(builder as IInternalTransitionBuilder)); [DebuggerHidden] - public IStateBuilder AddElseInternalTransition(ElseTransitionBuildAction transitionBuildAction = null) + public IStateBuilder AddElseInternalTransition(ElseInternalTransitionBuildAction transitionBuildAction) where TEvent : Event, new() - => AddElseTransition(Constants.DefaultTransitionTarget, transitionBuildAction); + => AddElseTransition(Constants.DefaultTransitionTarget, builder => transitionBuildAction?.Invoke(builder as IElseInternalTransitionBuilder)); [DebuggerHidden] ITypedStateBuilder IStateTransitions.AddTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) @@ -233,20 +233,20 @@ ITypedStateBuilder IStateTransitions.AddElseTransition AddElseTransition(targetVertexName, transitionBuildAction) as ITypedStateBuilder; [DebuggerHidden] - ITypedStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) + ITypedStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, DefaultTransitionBuildAction transitionBuildAction) => AddDefaultTransition(targetVertexName, transitionBuildAction) as ITypedStateBuilder; [DebuggerHidden] - ITypedStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) + ITypedStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseDefaultTransitionBuildAction transitionBuildAction) => AddElseDefaultTransition(targetVertexName, transitionBuildAction) as ITypedStateBuilder; [DebuggerHidden] - ITypedStateBuilder IStateTransitions.AddInternalTransition(TransitionBuildAction transitionBuildAction) - => AddInternalTransition(transitionBuildAction) as ITypedStateBuilder; + ITypedStateBuilder IStateTransitions.AddInternalTransition(InternalTransitionBuildAction transitionBuildAction) + => AddInternalTransition(builder => transitionBuildAction?.Invoke(builder)) as ITypedStateBuilder; [DebuggerHidden] - ITypedStateBuilder IStateTransitions.AddElseInternalTransition(ElseTransitionBuildAction transitionBuildAction) - => AddElseInternalTransition(transitionBuildAction) as ITypedStateBuilder; + ITypedStateBuilder IStateTransitions.AddElseInternalTransition(ElseInternalTransitionBuildAction transitionBuildAction) + => AddElseInternalTransition(builder => transitionBuildAction?.Invoke(builder)) as ITypedStateBuilder; [DebuggerHidden] ITypedStateBuilder IStateUtils.AddDeferredEvent() @@ -255,82 +255,128 @@ ITypedStateBuilder IStateUtils.AddDeferredEvent() #region Submachine [DebuggerHidden] - public ISubmachineStateBuilder AddSubmachine(string submachineName, StateActionInitializationBuilder initializationBuilder = null) + public IBehaviorStateBuilder AddSubmachine(string submachineName, EmbeddedBehaviorBuildAction buildAction, StateActionInitializationBuilder initializationBuilder = null) { - Vertex.SubmachineName = submachineName; - Vertex.SubmachineInitializationBuilder = initializationBuilder; + Vertex.BehaviorType = BehaviorType.StateMachine; + Vertex.BehaviorName = submachineName; + Vertex.BehaviorInitializationBuilder = initializationBuilder; + + buildAction?.Invoke(this); return this; } [DebuggerHidden] - ISubmachineTypedStateBuilder IStateSubmachine.AddSubmachine(string submachineName, StateActionInitializationBuilder initializationBuilder) - => AddSubmachine(submachineName, initializationBuilder) as ISubmachineTypedStateBuilder; + IBehaviorTypedStateBuilder IStateSubmachine.AddSubmachine(string submachineName, EmbeddedBehaviorBuildAction buildAction, StateActionInitializationBuilder initializationBuilder) + => AddSubmachine(submachineName, buildAction, initializationBuilder) as IBehaviorTypedStateBuilder; + #endregion + #region DoActivity [DebuggerHidden] - ISubmachineStateBuilder IStateEntry.AddOnEntry(Func actionAsync) - => AddOnEntry(actionAsync) as ISubmachineStateBuilder; + public IBehaviorStateBuilder AddDoActivity(string doActivityName, EmbeddedBehaviorBuildAction buildAction = null, StateActionInitializationBuilder initializationBuilder = null) + { + Vertex.BehaviorType = BehaviorType.Activity; + Vertex.BehaviorName = doActivityName; + Vertex.BehaviorInitializationBuilder = initializationBuilder; + + buildAction?.Invoke(this); + + return this; + } [DebuggerHidden] - ISubmachineStateBuilder IStateExit.AddOnExit(Func actionAsync) - => AddOnExit(actionAsync) as ISubmachineStateBuilder; + IBehaviorTypedStateBuilder IStateDoActivity.AddDoActivity(string doActivityName, EmbeddedBehaviorBuildAction buildAction, StateActionInitializationBuilder initializationBuilder) + => AddDoActivity(doActivityName, buildAction, initializationBuilder) as IBehaviorTypedStateBuilder; + #endregion [DebuggerHidden] - ISubmachineStateBuilder IStateUtils.AddDeferredEvent() - => AddDeferredEvent() as ISubmachineStateBuilder; + IBehaviorStateBuilder IStateEntry.AddOnEntry(Func actionAsync) + => AddOnEntry(actionAsync) as IBehaviorStateBuilder; [DebuggerHidden] - ISubmachineStateBuilder IStateTransitions.AddTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) - => AddTransition(targetVertexName, transitionBuildAction) as ISubmachineStateBuilder; + IBehaviorStateBuilder IStateExit.AddOnExit(Func actionAsync) + => AddOnExit(actionAsync) as IBehaviorStateBuilder; [DebuggerHidden] - ISubmachineStateBuilder IStateTransitions.AddElseTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) - => AddElseTransition(targetVertexName, transitionBuildAction) as ISubmachineStateBuilder; + IBehaviorStateBuilder IStateUtils.AddDeferredEvent() + => AddDeferredEvent() as IBehaviorStateBuilder; [DebuggerHidden] - ISubmachineStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) - => AddDefaultTransition(targetVertexName, transitionBuildAction) as ISubmachineStateBuilder; + IBehaviorStateBuilder IStateTransitions.AddTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) + => AddTransition(targetVertexName, transitionBuildAction) as IBehaviorStateBuilder; [DebuggerHidden] - ISubmachineStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) - => AddElseDefaultTransition(targetVertexName, transitionBuildAction) as ISubmachineStateBuilder; + IBehaviorStateBuilder IStateTransitions.AddElseTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) + => AddElseTransition(targetVertexName, transitionBuildAction) as IBehaviorStateBuilder; [DebuggerHidden] - ISubmachineStateBuilder IStateTransitions.AddInternalTransition(TransitionBuildAction transitionBuildAction) - => AddInternalTransition(transitionBuildAction) as ISubmachineStateBuilder; + IBehaviorStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, DefaultTransitionBuildAction transitionBuildAction) + => AddDefaultTransition(targetVertexName, transitionBuildAction) as IBehaviorStateBuilder; [DebuggerHidden] - ISubmachineStateBuilder IStateTransitions.AddElseInternalTransition(ElseTransitionBuildAction transitionBuildAction) - => AddElseInternalTransition(transitionBuildAction) as ISubmachineStateBuilder; + IBehaviorStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseDefaultTransitionBuildAction transitionBuildAction) + => AddElseDefaultTransition(targetVertexName, transitionBuildAction) as IBehaviorStateBuilder; [DebuggerHidden] - ISubmachineTypedStateBuilder IStateUtils.AddDeferredEvent() - => AddDeferredEvent() as ISubmachineTypedStateBuilder; + IBehaviorStateBuilder IStateTransitions.AddInternalTransition(InternalTransitionBuildAction transitionBuildAction) + => AddInternalTransition(builder => transitionBuildAction?.Invoke(builder)) as IBehaviorStateBuilder; [DebuggerHidden] - ISubmachineTypedStateBuilder IStateTransitions.AddTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) - => AddTransition(targetVertexName, transitionBuildAction) as ISubmachineTypedStateBuilder; + IBehaviorStateBuilder IStateTransitions.AddElseInternalTransition(ElseInternalTransitionBuildAction transitionBuildAction) + => AddElseInternalTransition(builder => transitionBuildAction?.Invoke(builder)) as IBehaviorStateBuilder; [DebuggerHidden] - ISubmachineTypedStateBuilder IStateTransitions.AddElseTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) - => AddElseTransition(targetVertexName, transitionBuildAction) as ISubmachineTypedStateBuilder; + IBehaviorTypedStateBuilder IStateUtils.AddDeferredEvent() + => AddDeferredEvent() as IBehaviorTypedStateBuilder; [DebuggerHidden] - ISubmachineTypedStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) - => AddDefaultTransition(targetVertexName, transitionBuildAction) as ISubmachineTypedStateBuilder; + IBehaviorTypedStateBuilder IStateTransitions.AddTransition(string targetVertexName, TransitionBuildAction transitionBuildAction) + => AddTransition(targetVertexName, transitionBuildAction) as IBehaviorTypedStateBuilder; [DebuggerHidden] - ISubmachineTypedStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) - => AddElseDefaultTransition(targetVertexName, transitionBuildAction) as ISubmachineTypedStateBuilder; + IBehaviorTypedStateBuilder IStateTransitions.AddElseTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction) + => AddElseTransition(targetVertexName, transitionBuildAction) as IBehaviorTypedStateBuilder; [DebuggerHidden] - ISubmachineTypedStateBuilder IStateTransitions.AddInternalTransition(TransitionBuildAction transitionBuildAction) - => AddInternalTransition(transitionBuildAction) as ISubmachineTypedStateBuilder; + IBehaviorTypedStateBuilder IStateTransitions.AddDefaultTransition(string targetVertexName, DefaultTransitionBuildAction transitionBuildAction) + => AddDefaultTransition(targetVertexName, transitionBuildAction) as IBehaviorTypedStateBuilder; [DebuggerHidden] - ISubmachineTypedStateBuilder IStateTransitions.AddElseInternalTransition(ElseTransitionBuildAction transitionBuildAction) - => AddElseInternalTransition(transitionBuildAction) as ISubmachineTypedStateBuilder; - #endregion + IBehaviorTypedStateBuilder IStateTransitions.AddElseDefaultTransition(string targetVertexName, ElseDefaultTransitionBuildAction transitionBuildAction) + => AddElseDefaultTransition(targetVertexName, transitionBuildAction) as IBehaviorTypedStateBuilder; + + [DebuggerHidden] + IBehaviorTypedStateBuilder IStateTransitions.AddInternalTransition(InternalTransitionBuildAction transitionBuildAction) + => AddInternalTransition(transitionBuildAction) as IBehaviorTypedStateBuilder; + + [DebuggerHidden] + IBehaviorTypedStateBuilder IStateTransitions.AddElseInternalTransition(ElseInternalTransitionBuildAction transitionBuildAction) + => AddElseInternalTransition(transitionBuildAction) as IBehaviorTypedStateBuilder; + + public IEmbeddedBehaviorBuilder AddForwardedEvent(ForwardedEventBuildAction buildAction = null) + where TEvent : Event, new() + => AddInternalTransition(b => + { + b.AddEffect(c => + { + if (c.TryLocateBehavior(Vertex.GetBehaviorId(c.StateMachine.Id), out var behavior)) + { + _ = behavior.SendAsync(c.Event); + } + + return Task.CompletedTask; + }); + + buildAction?.Invoke(b as IForwardedEventBuilder); + }) as IEmbeddedBehaviorBuilder; + + public IEmbeddedBehaviorBuilder AddSubscription() + where TNotification : Notification, new() + { + Vertex.BehaviorSubscriptions.Add(typeof(TNotification)); + + return this; + } } } diff --git a/Core/Stateflows/StateMachines/Registration/Builders/TransitionBuilder.cs b/Core/Stateflows/StateMachines/Registration/Builders/TransitionBuilder.cs index 0e29bee0..53e7ea49 100644 --- a/Core/Stateflows/StateMachines/Registration/Builders/TransitionBuilder.cs +++ b/Core/Stateflows/StateMachines/Registration/Builders/TransitionBuilder.cs @@ -1,16 +1,26 @@ using System; using System.Threading.Tasks; using Stateflows.Common; +using Stateflows.Common.Registration; using Stateflows.StateMachines.Models; using Stateflows.StateMachines.Context.Classes; using Stateflows.StateMachines.Context.Interfaces; using Stateflows.StateMachines.Registration.Interfaces; using Stateflows.StateMachines.Registration.Extensions; -using Stateflows.Common.Registration; +using Stateflows.StateMachines.Registration.Interfaces.Base; +using Stateflows.StateMachines.Events; namespace Stateflows.StateMachines.Registration.Builders { - internal class TransitionBuilder : ITransitionBuilder, IBehaviorBuilder + internal class TransitionBuilder : + ITransitionBuilder, + IElseTransitionBuilder, + IInternalTransitionBuilder, + IElseInternalTransitionBuilder, + IDefaultTransitionBuilder, + IElseDefaultTransitionBuilder, + IBehaviorBuilder, + IForwardedEventBuilder where TEvent : Event, new() { public Edge Edge; @@ -72,5 +82,29 @@ public ITransitionBuilder AddEffect(Func, Tas return this; } + + IInternalTransitionBuilder IEffect>.AddEffect(Func, Task> effectAsync) + => AddEffect(effectAsync) as IInternalTransitionBuilder; + + IInternalTransitionBuilder IGuard>.AddGuard(Func, Task> guardAsync) + => AddGuard(guardAsync) as IInternalTransitionBuilder; + + IElseTransitionBuilder IEffect>.AddEffect(Func, Task> effectAsync) + => AddEffect(effectAsync) as IElseTransitionBuilder; + + IElseInternalTransitionBuilder IEffect>.AddEffect(Func, Task> effectAsync) + => AddEffect(effectAsync) as IElseInternalTransitionBuilder; + + IDefaultTransitionBuilder IEffect.AddEffect(Func, Task> effectAsync) + => AddEffect(c => effectAsync(c as ITransitionContext)) as IDefaultTransitionBuilder; + + IDefaultTransitionBuilder IGuard.AddGuard(Func, Task> guardAsync) + => AddGuard(c => guardAsync(c as IGuardContext)) as IDefaultTransitionBuilder; + + IElseDefaultTransitionBuilder IEffect.AddEffect(Func, Task> effectAsync) + => AddEffect(c => effectAsync(c as ITransitionContext)) as IElseDefaultTransitionBuilder; + + IForwardedEventBuilder IGuard>.AddGuard(Func, Task> guardAsync) + => AddGuard(guardAsync) as IForwardedEventBuilder; } } diff --git a/Core/Stateflows/StateMachines/Registration/Constants.cs b/Core/Stateflows/StateMachines/Registration/Constants.cs index 4ebb7f41..122d8501 100644 --- a/Core/Stateflows/StateMachines/Registration/Constants.cs +++ b/Core/Stateflows/StateMachines/Registration/Constants.cs @@ -8,6 +8,7 @@ internal static class Constants public static readonly string StateValues = nameof(StateValues); public static readonly string GlobalValues = nameof(GlobalValues); public static readonly string DeferredEvents = nameof(DeferredEvents); + public static readonly string EmbeddedBehaviorStatuses = nameof(EmbeddedBehaviorStatuses); public static readonly string TimeEventIds = nameof(TimeEventIds); public static readonly string SubmachineId = nameof(SubmachineId); public static readonly string Entry = nameof(Entry); @@ -17,5 +18,6 @@ internal static class Constants public static readonly string Guard = nameof(Guard); public static readonly string Effect = nameof(Effect); public static readonly string Do = nameof(Do); + public static readonly string Redirect = nameof(Redirect); } } \ No newline at end of file diff --git a/Core/Stateflows/StateMachines/Registration/Extensions/Delegates.cs b/Core/Stateflows/StateMachines/Registration/Extensions/Delegates.cs new file mode 100644 index 00000000..4d8de3ea --- /dev/null +++ b/Core/Stateflows/StateMachines/Registration/Extensions/Delegates.cs @@ -0,0 +1,8 @@ +using Stateflows.Common; +using Stateflows.StateMachines.Context.Interfaces; + +namespace Stateflows.StateMachines.Registration.Extensions +{ + public delegate BehaviorId BehaviorIdBuilder(IEventContext context) + where TEvent : Event, new(); +} \ No newline at end of file diff --git a/Core/Stateflows/StateMachines/Registration/Extensions/ITransitionContextExtensions.cs b/Core/Stateflows/StateMachines/Registration/Extensions/ITransitionContextExtensions.cs new file mode 100644 index 00000000..2409c665 --- /dev/null +++ b/Core/Stateflows/StateMachines/Registration/Extensions/ITransitionContextExtensions.cs @@ -0,0 +1,12 @@ +using Stateflows.Common; +using Stateflows.StateMachines.Context.Interfaces; + +namespace Stateflows.StateMachines.Registration.Extensions +{ + internal static class ITransitionContextExtensions + { + public static bool TryLocateBehavior(this ITransitionContext context, BehaviorId behaviorId, out IBehavior behavior) + where TEvent : Event, new() + => context.TryLocateBehavior(behaviorId, out behavior); + } +} diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IEffect.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IEffect.cs new file mode 100644 index 00000000..a6b86e67 --- /dev/null +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IEffect.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; +using Stateflows.Common; +using Stateflows.StateMachines.Context.Interfaces; + +namespace Stateflows.StateMachines.Registration.Interfaces.Base +{ + public interface IEffect + where TEvent : Event, new() + { + TReturn AddEffect(Func, Task> effectAsync); + } +} diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IGuard.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IGuard.cs new file mode 100644 index 00000000..914c478b --- /dev/null +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IGuard.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; +using Stateflows.Common; +using Stateflows.StateMachines.Context.Interfaces; + +namespace Stateflows.StateMachines.Registration.Interfaces.Base +{ + public interface IGuard + where TEvent : Event, new() + { + TReturn AddGuard(Func, Task> guardAsync); + } +} diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IStateDoActivity.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IStateDoActivity.cs new file mode 100644 index 00000000..db91b81e --- /dev/null +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IStateDoActivity.cs @@ -0,0 +1,7 @@ +namespace Stateflows.StateMachines.Registration.Interfaces.Base +{ + public interface IStateDoActivity + { + TReturn AddDoActivity(string doActivityName, EmbeddedBehaviorBuildAction buildAction = null, StateActionInitializationBuilder initializationBuilder = null); + } +} diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IStateSubmachine.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IStateSubmachine.cs index f80dcb39..3ec7e730 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IStateSubmachine.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IStateSubmachine.cs @@ -2,6 +2,6 @@ { public interface IStateSubmachine { - TReturn AddSubmachine(string submachineName, StateActionInitializationBuilder initializationBuilder = null); + TReturn AddSubmachine(string submachineName, EmbeddedBehaviorBuildAction buildAction, StateActionInitializationBuilder initializationBuilder = null); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IStateTransitions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IStateTransitions.cs index 49a05365..14a981af 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IStateTransitions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Base/IStateTransitions.cs @@ -1,5 +1,4 @@ using Stateflows.Common; -using Stateflows.StateMachines.Events; namespace Stateflows.StateMachines.Registration.Interfaces.Base { @@ -9,9 +8,9 @@ public interface IStateTransitions TReturn AddTransition(string targetVertexName, TransitionBuildAction transitionBuildAction = null) where TEvent : Event, new(); - TReturn AddDefaultTransition(string targetVertexName, TransitionBuildAction transitionBuildAction = null); + TReturn AddDefaultTransition(string targetVertexName, DefaultTransitionBuildAction transitionBuildAction = null); - TReturn AddInternalTransition(TransitionBuildAction transitionBuildAction) + TReturn AddInternalTransition(InternalTransitionBuildAction transitionBuildAction) where TEvent : Event, new(); #endregion @@ -19,9 +18,9 @@ TReturn AddInternalTransition(TransitionBuildAction transitionBu TReturn AddElseTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction = null) where TEvent : Event, new(); - TReturn AddElseDefaultTransition(string targetVertexName, ElseTransitionBuildAction transitionBuildAction = null); + TReturn AddElseDefaultTransition(string targetVertexName, ElseDefaultTransitionBuildAction transitionBuildAction = null); - TReturn AddElseInternalTransition(ElseTransitionBuildAction transitionBuildAction) + TReturn AddElseInternalTransition(ElseInternalTransitionBuildAction transitionBuildAction) where TEvent : Event, new(); #endregion } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Delegates.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Delegates.cs index 87f5fbf1..39730b4b 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Delegates.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Delegates.cs @@ -19,9 +19,24 @@ namespace Stateflows.StateMachines.Registration.Interfaces public delegate void TransitionBuildAction(ITransitionBuilder builder) where TEvent : Event, new(); + public delegate void InternalTransitionBuildAction(IInternalTransitionBuilder builder) + where TEvent : Event, new(); + + public delegate void DefaultTransitionBuildAction(IDefaultTransitionBuilder builder); + public delegate void ElseTransitionBuildAction(IElseTransitionBuilder builder) where TEvent : Event, new(); + public delegate void ElseInternalTransitionBuildAction(IElseInternalTransitionBuilder builder) + where TEvent : Event, new(); + + public delegate void ElseDefaultTransitionBuildAction(IElseDefaultTransitionBuilder builder); + + public delegate void EmbeddedBehaviorBuildAction(IEmbeddedBehaviorBuilder builder); + + public delegate void ForwardedEventBuildAction(IForwardedEventBuilder builder) + where TEvent : Event, new(); + public delegate IStateMachineObserver StateMachineObserverFactory(IServiceProvider serviceProvider); public delegate IStateMachineInterceptor StateMachineInterceptorFactory(IServiceProvider serviceProvider); diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/CompositeStateBuilderElseDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/CompositeStateBuilderElseDefaultTransitionTypedExtensions.cs index 2222b1ef..917854b8 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/CompositeStateBuilderElseDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/CompositeStateBuilderElseDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static ICompositeStateBuilder AddElseDefaultTransition(t where TElseTransition : ElseTransition => builder.AddElseTransition(targetVertexName); - public static ICompositeStateBuilder AddElseDefaultTransition(this ICompositeStateBuilder builder, ElseTransitionBuildAction transitionBuildAction = null) + public static ICompositeStateBuilder AddElseDefaultTransition(this ICompositeStateBuilder builder, ElseDefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddElseDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/CompositeStateBuilderElsePayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/CompositeStateBuilderElsePayloadExtensions.cs index 1ac985ca..05d9b098 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/CompositeStateBuilderElsePayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/CompositeStateBuilderElsePayloadExtensions.cs @@ -8,7 +8,7 @@ public static class CompositeStateBuilderElsePayloadExtensions public static ICompositeStateBuilder AddElseDataTransition(this ICompositeStateBuilder builder, string targetVertexName, ElseTransitionBuildAction> transitionBuildAction = null) => builder.AddElseTransition>(targetVertexName, transitionBuildAction); - public static ICompositeStateBuilder AddElseInternalDataTransition(this ICompositeStateBuilder builder, ElseTransitionBuildAction> transitionBuildAction = null) + public static ICompositeStateBuilder AddElseInternalDataTransition(this ICompositeStateBuilder builder, ElseInternalTransitionBuildAction> transitionBuildAction = null) => builder.AddElseInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/FinalizedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/FinalizedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs index fd7c615d..1d2439c4 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/FinalizedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/FinalizedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static IFinalizedCompositeStateBuilder AddElseDefaultTransition => builder.AddElseTransition(targetVertexName); - public static IFinalizedCompositeStateBuilder AddElseDefaultTransition(this IFinalizedCompositeStateBuilder builder, ElseTransitionBuildAction transitionBuildAction = null) + public static IFinalizedCompositeStateBuilder AddElseDefaultTransition(this IFinalizedCompositeStateBuilder builder, ElseDefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddElseDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/FinalizedCompositeStateBuilderElsePayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/FinalizedCompositeStateBuilderElsePayloadExtensions.cs index 25884a15..09720a57 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/FinalizedCompositeStateBuilderElsePayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/FinalizedCompositeStateBuilderElsePayloadExtensions.cs @@ -8,7 +8,7 @@ public static class FinalizedCompositeStateBuilderElsePayloadExtensions public static IFinalizedCompositeStateBuilder AddElseDataTransition(this IFinalizedCompositeStateBuilder builder, string targetVertexName, ElseTransitionBuildAction> transitionBuildAction = null) => builder.AddElseTransition>(targetVertexName, transitionBuildAction); - public static IFinalizedCompositeStateBuilder AddElseInternalDataTransition(this IFinalizedCompositeStateBuilder builder, ElseTransitionBuildAction> transitionBuildAction = null) + public static IFinalizedCompositeStateBuilder AddElseInternalDataTransition(this IFinalizedCompositeStateBuilder builder, ElseInternalTransitionBuildAction> transitionBuildAction = null) => builder.AddElseInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/InitializedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/InitializedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs index e837659a..228d1dff 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/InitializedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/InitializedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static IInitializedCompositeStateBuilder AddElseDefaultTransition => builder.AddElseTransition(targetVertexName); - public static IInitializedCompositeStateBuilder AddElseDefaultTransition(this IInitializedCompositeStateBuilder builder, ElseTransitionBuildAction transitionBuildAction = null) + public static IInitializedCompositeStateBuilder AddElseDefaultTransition(this IInitializedCompositeStateBuilder builder, ElseDefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddElseDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/InitializedCompositeStateBuilderElsePayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/InitializedCompositeStateBuilderElsePayloadExtensions.cs index 7bfe71fa..761055ae 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/InitializedCompositeStateBuilderElsePayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/ElseTransitions/InitializedCompositeStateBuilderElsePayloadExtensions.cs @@ -8,7 +8,7 @@ public static class InitializedCompositeStateBuilderElsePayloadExtensions public static IInitializedCompositeStateBuilder AddElseDataTransition(this IInitializedCompositeStateBuilder builder, string targetVertexName, ElseTransitionBuildAction> transitionBuildAction = null) => builder.AddElseTransition>(targetVertexName, transitionBuildAction); - public static IInitializedCompositeStateBuilder AddElseInternalDataTransition(this IInitializedCompositeStateBuilder builder, ElseTransitionBuildAction> transitionBuildAction = null) + public static IInitializedCompositeStateBuilder AddElseInternalDataTransition(this IInitializedCompositeStateBuilder builder, ElseInternalTransitionBuildAction> transitionBuildAction = null) => builder.AddElseInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/CompositeStateBuilderDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/CompositeStateBuilderDefaultTransitionTypedExtensions.cs index dc656623..9ad12368 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/CompositeStateBuilderDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/CompositeStateBuilderDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static ICompositeStateBuilder AddDefaultTransition(this ICom where TTransition : Transition => builder.AddTransition(targetVertexName); - public static ICompositeStateBuilder AddDefaultTransition(this ICompositeStateBuilder builder, TransitionBuildAction transitionBuildAction = null) + public static ICompositeStateBuilder AddDefaultTransition(this ICompositeStateBuilder builder, DefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/CompositeStateBuilderPayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/CompositeStateBuilderPayloadExtensions.cs index 40ddd320..acda6a8e 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/CompositeStateBuilderPayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/CompositeStateBuilderPayloadExtensions.cs @@ -8,7 +8,7 @@ public static class CompositeStateBuilderPayloadExtensions public static ICompositeStateBuilder AddDataTransition(this ICompositeStateBuilder builder, string targetVertexName, TransitionBuildAction> transitionBuildAction = null) => builder.AddTransition>(targetVertexName, transitionBuildAction); - public static ICompositeStateBuilder AddInternalDataTransition(this ICompositeStateBuilder builder, TransitionBuildAction> transitionBuildAction = null) + public static ICompositeStateBuilder AddInternalDataTransition(this ICompositeStateBuilder builder, InternalTransitionBuildAction> transitionBuildAction = null) => builder.AddInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/FinalizedCompositeStateBuilderDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/FinalizedCompositeStateBuilderDefaultTransitionTypedExtensions.cs index ad449e8f..a3af570e 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/FinalizedCompositeStateBuilderDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/FinalizedCompositeStateBuilderDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static IFinalizedCompositeStateBuilder AddDefaultTransition( where TTransition : Transition => builder.AddTransition(targetVertexName); - public static IFinalizedCompositeStateBuilder AddDefaultTransition(this IFinalizedCompositeStateBuilder builder, TransitionBuildAction transitionBuildAction = null) + public static IFinalizedCompositeStateBuilder AddDefaultTransition(this IFinalizedCompositeStateBuilder builder, DefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/FinalizedCompositeStateBuilderPayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/FinalizedCompositeStateBuilderPayloadExtensions.cs index 1893f7b0..b7ad5b02 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/FinalizedCompositeStateBuilderPayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/FinalizedCompositeStateBuilderPayloadExtensions.cs @@ -8,7 +8,7 @@ public static class FinalizedCompositeStateBuilderPayloadExtensions public static IFinalizedCompositeStateBuilder AddDataTransition(this IFinalizedCompositeStateBuilder builder, string targetVertexName, TransitionBuildAction> transitionBuildAction = null) => builder.AddTransition>(targetVertexName, transitionBuildAction); - public static IFinalizedCompositeStateBuilder AddInternalDataTransition(this IFinalizedCompositeStateBuilder builder, TransitionBuildAction> transitionBuildAction = null) + public static IFinalizedCompositeStateBuilder AddInternalDataTransition(this IFinalizedCompositeStateBuilder builder, InternalTransitionBuildAction> transitionBuildAction = null) => builder.AddInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/InitializedCompositeStateBuilderDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/InitializedCompositeStateBuilderDefaultTransitionTypedExtensions.cs index 1a02296a..6839a39a 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/InitializedCompositeStateBuilderDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/InitializedCompositeStateBuilderDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static IInitializedCompositeStateBuilder AddDefaultTransition => builder.AddTransition(targetVertexName); - public static IInitializedCompositeStateBuilder AddDefaultTransition(this IInitializedCompositeStateBuilder builder, TransitionBuildAction transitionBuildAction = null) + public static IInitializedCompositeStateBuilder AddDefaultTransition(this IInitializedCompositeStateBuilder builder, DefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/InitializedCompositeStateBuilderPayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/InitializedCompositeStateBuilderPayloadExtensions.cs index 029364a0..17e57077 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/InitializedCompositeStateBuilderPayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/CompositeStateBuilder/Transitions/InitializedCompositeStateBuilderPayloadExtensions.cs @@ -8,7 +8,7 @@ public static class InitializedCompositeStateBuilderPayloadExtensions public static IInitializedCompositeStateBuilder AddDataTransition(this IInitializedCompositeStateBuilder builder, string targetVertexName, TransitionBuildAction> transitionBuildAction = null) => builder.AddTransition>(targetVertexName, transitionBuildAction); - public static IInitializedCompositeStateBuilder AddInternalDataTransition(this IInitializedCompositeStateBuilder builder, TransitionBuildAction> transitionBuildAction = null) + public static IInitializedCompositeStateBuilder AddInternalDataTransition(this IInitializedCompositeStateBuilder builder, InternalTransitionBuildAction> transitionBuildAction = null) => builder.AddInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/ElseTransitions/StateBuilderElseDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/ElseTransitions/StateBuilderElseDefaultTransitionTypedExtensions.cs index 755d0df0..fddfb891 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/ElseTransitions/StateBuilderElseDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/ElseTransitions/StateBuilderElseDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static IStateBuilder AddElseDefaultTransition(this IStat where TElseTransition : ElseTransition => builder.AddElseTransition(targetVertexName); - public static IStateBuilder AddElseDefaultTransition(this IStateBuilder builder, ElseTransitionBuildAction transitionBuildAction = null) + public static IStateBuilder AddElseDefaultTransition(this IStateBuilder builder, ElseDefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddElseDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/ElseTransitions/StateBuilderElsePayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/ElseTransitions/StateBuilderElsePayloadExtensions.cs index 15e5bb20..68bcbe83 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/ElseTransitions/StateBuilderElsePayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/ElseTransitions/StateBuilderElsePayloadExtensions.cs @@ -8,7 +8,7 @@ public static class StateBuilderElsePayloadExtensions public static IStateBuilder AddElseDataTransition(this IStateBuilder builder, string targetVertexName, ElseTransitionBuildAction> transitionBuildAction = null) => builder.AddElseTransition>(targetVertexName, transitionBuildAction); - public static IStateBuilder AddElseInternalDataTransition(this IStateBuilder builder, ElseTransitionBuildAction> transitionBuildAction = null) + public static IStateBuilder AddElseInternalDataTransition(this IStateBuilder builder, ElseInternalTransitionBuildAction> transitionBuildAction = null) => builder.AddElseInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Submachine/StateBuilderSubmachineTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Submachine/StateBuilderSubmachineTypedExtensions.cs index 15caa1f4..14a6e54f 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Submachine/StateBuilderSubmachineTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Submachine/StateBuilderSubmachineTypedExtensions.cs @@ -4,8 +4,8 @@ namespace Stateflows.StateMachines.Typed { public static class StateBuilderSubmachineTypedExtensions { - public static ISubmachineStateBuilder AddSubmachine(this IStateBuilder builder, StateActionInitializationBuilder initializationBuilder = null) + public static IBehaviorStateBuilder AddSubmachine(this IStateBuilder builder, EmbeddedBehaviorBuildAction buildAction, StateActionInitializationBuilder initializationBuilder = null) where TStateMachine : StateMachine - => builder.AddSubmachine(StateMachineInfo.Name, initializationBuilder); + => builder.AddSubmachine(StateMachineInfo.Name, buildAction, initializationBuilder); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Transitions/StateBuilderDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Transitions/StateBuilderDefaultTransitionTypedExtensions.cs index 9ef1a040..c73918a3 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Transitions/StateBuilderDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Transitions/StateBuilderDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static IStateBuilder AddDefaultTransition(this IStateBuilder where TTransition : Transition => builder.AddTransition(targetVertexName); - public static IStateBuilder AddDefaultTransition(this IStateBuilder builder, TransitionBuildAction transitionBuildAction = null) + public static IStateBuilder AddDefaultTransition(this IStateBuilder builder, DefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Transitions/StateBuilderPayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Transitions/StateBuilderPayloadExtensions.cs index f32b7e43..7c331fff 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Transitions/StateBuilderPayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/StateBuilder/Transitions/StateBuilderPayloadExtensions.cs @@ -8,7 +8,7 @@ public static class StateBuilderPayloadExtensions public static IStateBuilder AddDataTransition(this IStateBuilder builder, string targetVertexName, TransitionBuildAction> transitionBuildAction = null) => builder.AddTransition>(targetVertexName, transitionBuildAction); - public static IStateBuilder AddInternalDataTransition(this IStateBuilder builder, TransitionBuildAction> transitionBuildAction = null) + public static IStateBuilder AddInternalDataTransition(this IStateBuilder builder, InternalTransitionBuildAction> transitionBuildAction = null) => builder.AddInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TransitionBuilder/TransitionBuilderSyncExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TransitionBuilder/TransitionBuilderSyncExtensions.cs index 4170d4cc..4136376f 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TransitionBuilder/TransitionBuilderSyncExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TransitionBuilder/TransitionBuilderSyncExtensions.cs @@ -1,7 +1,10 @@ using System; using System.Diagnostics; +using Stateflows.Activities.Extensions; +using System.Threading.Tasks; using Stateflows.Common; using Stateflows.StateMachines.Context.Interfaces; +using Stateflows.StateMachines.Events; using Stateflows.StateMachines.Registration.Builders; using Stateflows.StateMachines.Registration.Extensions; using Stateflows.StateMachines.Registration.Interfaces; @@ -33,5 +36,52 @@ public static IElseTransitionBuilder AddEffect(this IElseTransit .AddStateMachineInvocationContext((builder as TransitionBuilder).Edge.Graph) .ToAsync() ); + + + [DebuggerHidden] + public static IInternalTransitionBuilder AddGuard(this IInternalTransitionBuilder builder, Func, bool> guard) + where TEvent : Event, new() + => builder.AddGuard(guard + .AddStateMachineInvocationContext((builder as TransitionBuilder).Edge.Graph) + .ToAsync() + ); + + [DebuggerHidden] + public static IInternalTransitionBuilder AddEffect(this IInternalTransitionBuilder builder, Action> effect) + where TEvent : Event, new() + => builder.AddEffect(effect + .AddStateMachineInvocationContext((builder as TransitionBuilder).Edge.Graph) + .ToAsync() + ); + + [DebuggerHidden] + public static IElseInternalTransitionBuilder AddEffect(this IElseInternalTransitionBuilder builder, Action> effect) + where TEvent : Event, new() + => builder.AddEffect(effect + .AddStateMachineInvocationContext((builder as TransitionBuilder).Edge.Graph) + .ToAsync() + ); + + + [DebuggerHidden] + public static IDefaultTransitionBuilder AddGuard(this IDefaultTransitionBuilder builder, Func, bool> guard) + => builder.AddGuard(guard + .AddStateMachineInvocationContext((builder as TransitionBuilder).Edge.Graph) + .ToAsync() + ); + + [DebuggerHidden] + public static IDefaultTransitionBuilder AddEffect(this IDefaultTransitionBuilder builder, Action> effect) + => builder.AddEffect(effect + .AddStateMachineInvocationContext((builder as TransitionBuilder).Edge.Graph) + .ToAsync() + ); + + [DebuggerHidden] + public static IElseDefaultTransitionBuilder AddEffect(this IElseDefaultTransitionBuilder builder, Action> effect) + => builder.AddEffect(effect + .AddStateMachineInvocationContext((builder as TransitionBuilder).Edge.Graph) + .ToAsync() + ); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs index fb22f7cf..5c239ee1 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static ITypedCompositeStateBuilder AddElseDefaultTransition => builder.AddElseTransition(targetVertexName); - public static ITypedCompositeStateBuilder AddElseDefaultTransition(this ITypedCompositeStateBuilder builder, ElseTransitionBuildAction transitionBuildAction = null) + public static ITypedCompositeStateBuilder AddElseDefaultTransition(this ITypedCompositeStateBuilder builder, ElseDefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddElseDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedCompositeStateBuilderElsePayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedCompositeStateBuilderElsePayloadExtensions.cs index 8fd498d9..9dbd7ca9 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedCompositeStateBuilderElsePayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedCompositeStateBuilderElsePayloadExtensions.cs @@ -8,7 +8,7 @@ public static class TypedCompositeStateBuilderElsePayloadExtensions public static ITypedCompositeStateBuilder AddElseDataTransition(this ITypedCompositeStateBuilder builder, string targetVertexName, ElseTransitionBuildAction> transitionBuildAction = null) => builder.AddElseTransition>(targetVertexName, transitionBuildAction); - public static ITypedCompositeStateBuilder AddElseInternalDataTransition(this ITypedCompositeStateBuilder builder, ElseTransitionBuildAction> transitionBuildAction = null) + public static ITypedCompositeStateBuilder AddElseInternalDataTransition(this ITypedCompositeStateBuilder builder, ElseInternalTransitionBuildAction> transitionBuildAction = null) => builder.AddElseInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedFinalizedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedFinalizedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs index c6c9ac84..c009850e 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedFinalizedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedFinalizedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static ITypedFinalizedCompositeStateBuilder AddElseDefaultTransition => builder.AddElseTransition(targetVertexName); - public static ITypedFinalizedCompositeStateBuilder AddElseDefaultTransition(this ITypedFinalizedCompositeStateBuilder builder, ElseTransitionBuildAction transitionBuildAction = null) + public static ITypedFinalizedCompositeStateBuilder AddElseDefaultTransition(this ITypedFinalizedCompositeStateBuilder builder, ElseDefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddElseDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedFinalizedCompositeStateBuilderElsePayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedFinalizedCompositeStateBuilderElsePayloadExtensions.cs index 5716cad3..730e69dd 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedFinalizedCompositeStateBuilderElsePayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedFinalizedCompositeStateBuilderElsePayloadExtensions.cs @@ -8,7 +8,7 @@ public static class TypedFinalizedCompositeStateBuilderElsePayloadExtensions public static ITypedFinalizedCompositeStateBuilder AddElseDataTransition(this ITypedFinalizedCompositeStateBuilder builder, string targetVertexName, ElseTransitionBuildAction> transitionBuildAction = null) => builder.AddElseTransition>(targetVertexName, transitionBuildAction); - public static ITypedFinalizedCompositeStateBuilder AddElseInternalDataTransition(this ITypedFinalizedCompositeStateBuilder builder, ElseTransitionBuildAction> transitionBuildAction = null) + public static ITypedFinalizedCompositeStateBuilder AddElseInternalDataTransition(this ITypedFinalizedCompositeStateBuilder builder, ElseInternalTransitionBuildAction> transitionBuildAction = null) => builder.AddElseInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedInitializedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedInitializedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs index 3dd2c292..6937d095 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedInitializedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedInitializedCompositeStateBuilderElseDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static ITypedInitializedCompositeStateBuilder AddElseDefaultTransition => builder.AddElseTransition(targetVertexName); - public static ITypedInitializedCompositeStateBuilder AddElseDefaultTransition(this ITypedInitializedCompositeStateBuilder builder, ElseTransitionBuildAction transitionBuildAction = null) + public static ITypedInitializedCompositeStateBuilder AddElseDefaultTransition(this ITypedInitializedCompositeStateBuilder builder, ElseDefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddElseDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedInitializedCompositeStateBuilderElsePayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedInitializedCompositeStateBuilderElsePayloadExtensions.cs index 49ae6708..2c313d41 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedInitializedCompositeStateBuilderElsePayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/ElseTransitions/TypedInitializedCompositeStateBuilderElsePayloadExtensions.cs @@ -8,7 +8,7 @@ public static class TypedInitializedCompositeStateBuilderElsePayloadExtensions public static ITypedInitializedCompositeStateBuilder AddElseDataTransition(this ITypedInitializedCompositeStateBuilder builder, string targetVertexName, ElseTransitionBuildAction> transitionBuildAction = null) => builder.AddElseTransition>(targetVertexName, transitionBuildAction); - public static ITypedInitializedCompositeStateBuilder AddElseInternalDataTransition(this ITypedInitializedCompositeStateBuilder builder, ElseTransitionBuildAction> transitionBuildAction = null) + public static ITypedInitializedCompositeStateBuilder AddElseInternalDataTransition(this ITypedInitializedCompositeStateBuilder builder, ElseInternalTransitionBuildAction> transitionBuildAction = null) => builder.AddElseInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateBuilderDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateBuilderDefaultTransitionTypedExtensions.cs index 0d5a9b4a..24497502 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateBuilderDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateBuilderDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static ITypedInitializedCompositeStateBuilder AddDefaultTransition => builder.AddTransition(targetVertexName); - public static ITypedInitializedCompositeStateBuilder AddDefaultTransition(this ITypedInitializedCompositeStateBuilder builder, TransitionBuildAction transitionBuildAction = null) + public static ITypedInitializedCompositeStateBuilder AddDefaultTransition(this ITypedInitializedCompositeStateBuilder builder, DefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateBuilderPayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateBuilderPayloadExtensions.cs index 4afe1319..1eaeffd5 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateBuilderPayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateBuilderPayloadExtensions.cs @@ -8,7 +8,7 @@ public static class TypedCompositeStateBuilderPayloadExtensions public static ITypedCompositeStateBuilder AddDataTransition(this ITypedCompositeStateBuilder builder, string targetVertexName, TransitionBuildAction> transitionBuildAction = null) => builder.AddTransition>(targetVertexName, transitionBuildAction); - public static ITypedCompositeStateBuilder AddInternalDataTransition(this ITypedCompositeStateBuilder builder, TransitionBuildAction> transitionBuildAction = null) + public static ITypedCompositeStateBuilder AddInternalDataTransition(this ITypedCompositeStateBuilder builder, InternalTransitionBuildAction> transitionBuildAction = null) => builder.AddInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateInitialBuilderDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateInitialBuilderDefaultTransitionTypedExtensions.cs index be5ff189..90df138b 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateInitialBuilderDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedCompositeStateInitialBuilderDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static ITypedCompositeStateBuilder AddDefaultTransition(this where TTransition : Transition => builder.AddTransition(targetVertexName); - public static ITypedCompositeStateBuilder AddDefaultTransition(this ITypedCompositeStateBuilder builder, TransitionBuildAction transitionBuildAction = null) + public static ITypedCompositeStateBuilder AddDefaultTransition(this ITypedCompositeStateBuilder builder, DefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedFinalizedCompositeStateBuilderDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedFinalizedCompositeStateBuilderDefaultTransitionTypedExtensions.cs index 550e4f49..fb708b02 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedFinalizedCompositeStateBuilderDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedFinalizedCompositeStateBuilderDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static ITypedFinalizedCompositeStateBuilder AddDefaultTransition => builder.AddTransition(targetVertexName); - public static ITypedFinalizedCompositeStateBuilder AddDefaultTransition(this ITypedFinalizedCompositeStateBuilder builder, TransitionBuildAction transitionBuildAction = null) + public static ITypedFinalizedCompositeStateBuilder AddDefaultTransition(this ITypedFinalizedCompositeStateBuilder builder, DefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedFinalizedCompositeStateBuilderPayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedFinalizedCompositeStateBuilderPayloadExtensions.cs index 217ce79a..5d1c59c5 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedFinalizedCompositeStateBuilderPayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedFinalizedCompositeStateBuilderPayloadExtensions.cs @@ -8,7 +8,7 @@ public static class TypedFinalizedCompositeStateBuilderPayloadExtensions public static ITypedFinalizedCompositeStateBuilder AddDataTransition(this ITypedFinalizedCompositeStateBuilder builder, string targetVertexName, TransitionBuildAction> transitionBuildAction = null) => builder.AddTransition>(targetVertexName, transitionBuildAction); - public static ITypedFinalizedCompositeStateBuilder AddInternalDataTransition(this ITypedFinalizedCompositeStateBuilder builder, TransitionBuildAction> transitionBuildAction = null) + public static ITypedFinalizedCompositeStateBuilder AddInternalDataTransition(this ITypedFinalizedCompositeStateBuilder builder, InternalTransitionBuildAction> transitionBuildAction = null) => builder.AddInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedInitializedCompositeStateBuilderPayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedInitializedCompositeStateBuilderPayloadExtensions.cs index 503888c6..c52d7641 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedInitializedCompositeStateBuilderPayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedCompositeStateBuilder/Transitions/TypedInitializedCompositeStateBuilderPayloadExtensions.cs @@ -8,7 +8,7 @@ public static class TypedInitializedCompositeStateBuilderPayloadExtensions public static ITypedInitializedCompositeStateBuilder AddDataTransition(this ITypedInitializedCompositeStateBuilder builder, string targetVertexName, TransitionBuildAction> transitionBuildAction = null) => builder.AddTransition>(targetVertexName, transitionBuildAction); - public static ITypedInitializedCompositeStateBuilder AddInternalDataTransition(this ITypedInitializedCompositeStateBuilder builder, TransitionBuildAction> transitionBuildAction = null) + public static ITypedInitializedCompositeStateBuilder AddInternalDataTransition(this ITypedInitializedCompositeStateBuilder builder, InternalTransitionBuildAction> transitionBuildAction = null) => builder.AddInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/ElseTransitions/TypedStateBuilderElseDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/ElseTransitions/TypedStateBuilderElseDefaultTransitionTypedExtensions.cs index 0dd260e4..3be0a607 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/ElseTransitions/TypedStateBuilderElseDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/ElseTransitions/TypedStateBuilderElseDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static ITypedStateBuilder AddElseDefaultTransition(this where TElseTransition : ElseTransition => builder.AddElseTransition(targetVertexName); - public static ITypedStateBuilder AddElseDefaultTransition(this ITypedStateBuilder builder, ElseTransitionBuildAction transitionBuildAction = null) + public static ITypedStateBuilder AddElseDefaultTransition(this ITypedStateBuilder builder, ElseDefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddElseDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/ElseTransitions/TypedStateBuilderElsePayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/ElseTransitions/TypedStateBuilderElsePayloadExtensions.cs index 98159dc2..2eb541ad 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/ElseTransitions/TypedStateBuilderElsePayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/ElseTransitions/TypedStateBuilderElsePayloadExtensions.cs @@ -8,7 +8,7 @@ public static class TypedStateBuilderElsePayloadExtensions public static ITypedStateBuilder AddElseDataTransition(this ITypedStateBuilder builder, string targetVertexName, ElseTransitionBuildAction> transitionBuildAction = null) => builder.AddElseTransition>(targetVertexName, transitionBuildAction); - public static ITypedStateBuilder AddElseInternalDataTransition(this ITypedStateBuilder builder, ElseTransitionBuildAction> transitionBuildAction = null) + public static ITypedStateBuilder AddElseInternalDataTransition(this ITypedStateBuilder builder, ElseInternalTransitionBuildAction> transitionBuildAction = null) => builder.AddElseInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Submachine/TypedStateBuilderSubmachineTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Submachine/TypedStateBuilderSubmachineTypedExtensions.cs index 3111dae2..2b746029 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Submachine/TypedStateBuilderSubmachineTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Submachine/TypedStateBuilderSubmachineTypedExtensions.cs @@ -4,8 +4,8 @@ namespace Stateflows.StateMachines.Typed { public static class TypedStateBuilderSubmachineTypedExtensions { - public static ISubmachineTypedStateBuilder AddSubmachine(this ITypedStateBuilder builder, StateActionInitializationBuilder initializationBuilder = null) + public static IBehaviorTypedStateBuilder AddSubmachine(this ITypedStateBuilder builder, EmbeddedBehaviorBuildAction buildAction, StateActionInitializationBuilder initializationBuilder = null) where TStateMachine : StateMachine - => builder.AddSubmachine(StateMachineInfo.Name, initializationBuilder); + => builder.AddSubmachine(StateMachineInfo.Name, buildAction, initializationBuilder); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Transitions/TypedStateBuilderDefaultTransitionTypedExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Transitions/TypedStateBuilderDefaultTransitionTypedExtensions.cs index 9f5e409d..6cac38a5 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Transitions/TypedStateBuilderDefaultTransitionTypedExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Transitions/TypedStateBuilderDefaultTransitionTypedExtensions.cs @@ -14,7 +14,7 @@ public static ITypedStateBuilder AddDefaultTransition(this ITypedSt where TTransition : Transition => builder.AddTransition(targetVertexName); - public static ITypedStateBuilder AddDefaultTransition(this ITypedStateBuilder builder, TransitionBuildAction transitionBuildAction = null) + public static ITypedStateBuilder AddDefaultTransition(this ITypedStateBuilder builder, DefaultTransitionBuildAction transitionBuildAction = null) where TTargetState : BaseState => builder.AddDefaultTransition(StateInfo.Name, transitionBuildAction); } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Transitions/TypedStateBuilderPayloadExtensions.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Transitions/TypedStateBuilderPayloadExtensions.cs index f7472428..ba151a36 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Transitions/TypedStateBuilderPayloadExtensions.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/Extensions/TypedStateBuilder/Transitions/TypedStateBuilderPayloadExtensions.cs @@ -8,7 +8,7 @@ public static class TypedStateBuilderPayloadExtensions public static ITypedStateBuilder AddDataTransition(this ITypedStateBuilder builder, string targetVertexName, TransitionBuildAction> transitionBuildAction = null) => builder.AddTransition>(targetVertexName, transitionBuildAction); - public static ITypedStateBuilder AddInternalDataTransition(this ITypedStateBuilder builder, TransitionBuildAction> transitionBuildAction = null) + public static ITypedStateBuilder AddInternalDataTransition(this ITypedStateBuilder builder, InternalTransitionBuildAction> transitionBuildAction = null) => builder.AddInternalTransition>(transitionBuildAction); } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/IDefaultTransitionBuilder.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/IDefaultTransitionBuilder.cs new file mode 100644 index 00000000..f1bb7305 --- /dev/null +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/IDefaultTransitionBuilder.cs @@ -0,0 +1,10 @@ +using Stateflows.StateMachines.Events; +using Stateflows.StateMachines.Registration.Interfaces.Base; + +namespace Stateflows.StateMachines.Registration.Interfaces +{ + public interface IDefaultTransitionBuilder : + IEffect, + IGuard + { } +} diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/IElseDefaultTransitionBuilder.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/IElseDefaultTransitionBuilder.cs new file mode 100644 index 00000000..6950d9e6 --- /dev/null +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/IElseDefaultTransitionBuilder.cs @@ -0,0 +1,9 @@ +using Stateflows.StateMachines.Events; +using Stateflows.StateMachines.Registration.Interfaces.Base; + +namespace Stateflows.StateMachines.Registration.Interfaces +{ + public interface IElseDefaultTransitionBuilder : + IEffect + { } +} diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/IElseInternalTransitionBuilder.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/IElseInternalTransitionBuilder.cs new file mode 100644 index 00000000..9c56bb85 --- /dev/null +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/IElseInternalTransitionBuilder.cs @@ -0,0 +1,10 @@ +using Stateflows.Common; +using Stateflows.StateMachines.Registration.Interfaces.Base; + +namespace Stateflows.StateMachines.Registration.Interfaces +{ + public interface IElseInternalTransitionBuilder : + IEffect> + where TEvent : Event, new() + { } +} diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/IElseTransitionBuilder.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/IElseTransitionBuilder.cs index c5d8d5bf..b59f6c0c 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/IElseTransitionBuilder.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/IElseTransitionBuilder.cs @@ -1,13 +1,10 @@ -using System; -using System.Threading.Tasks; -using Stateflows.Common; -using Stateflows.StateMachines.Context.Interfaces; +using Stateflows.Common; +using Stateflows.StateMachines.Registration.Interfaces.Base; namespace Stateflows.StateMachines.Registration.Interfaces { - public interface IElseTransitionBuilder + public interface IElseTransitionBuilder : + IEffect> where TEvent : Event, new() - { - ITransitionBuilder AddEffect(Func, Task> effectAsync); - } + { } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/IEmbeddedBehaviorBuilder.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/IEmbeddedBehaviorBuilder.cs new file mode 100644 index 00000000..1a5fbb1a --- /dev/null +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/IEmbeddedBehaviorBuilder.cs @@ -0,0 +1,13 @@ +using Stateflows.Common; + +namespace Stateflows.StateMachines.Registration.Interfaces +{ + public interface IEmbeddedBehaviorBuilder + { + IEmbeddedBehaviorBuilder AddForwardedEvent(ForwardedEventBuildAction buildAction = null) + where TEvent : Event, new(); + + IEmbeddedBehaviorBuilder AddSubscription() + where TNotification : Notification, new(); + } +} diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/IForwardedEventBuilder.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/IForwardedEventBuilder.cs new file mode 100644 index 00000000..ce864a60 --- /dev/null +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/IForwardedEventBuilder.cs @@ -0,0 +1,10 @@ +using Stateflows.Common; +using Stateflows.StateMachines.Registration.Interfaces.Base; + +namespace Stateflows.StateMachines.Registration.Interfaces +{ + public interface IForwardedEventBuilder : + IGuard> + where TEvent : Event, new() + { } +} diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/IInternalTransitionBuilder.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/IInternalTransitionBuilder.cs new file mode 100644 index 00000000..549db98b --- /dev/null +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/IInternalTransitionBuilder.cs @@ -0,0 +1,11 @@ +using Stateflows.Common; +using Stateflows.StateMachines.Registration.Interfaces.Base; + +namespace Stateflows.StateMachines.Registration.Interfaces +{ + public interface IInternalTransitionBuilder : + IEffect>, + IGuard> + where TEvent : Event, new() + { } +} diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/IStateBuilder.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/IStateBuilder.cs index ce2d8f33..116ef0bd 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/IStateBuilder.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/IStateBuilder.cs @@ -6,23 +6,25 @@ public interface IStateBuilder : IStateEvents, IStateUtils, IStateTransitions, - IStateSubmachine + IStateSubmachine, + IStateDoActivity { } - public interface ISubmachineStateBuilder : - IStateEvents, - IStateUtils, - IStateTransitions + public interface IBehaviorStateBuilder : + IStateEvents, + IStateUtils, + IStateTransitions { } public interface ITypedStateBuilder : IStateUtils, IStateTransitions, - IStateSubmachine + IStateSubmachine, + IStateDoActivity { } - public interface ISubmachineTypedStateBuilder : - IStateUtils, - IStateTransitions + public interface IBehaviorTypedStateBuilder : + IStateUtils, + IStateTransitions { } } diff --git a/Core/Stateflows/StateMachines/Registration/Interfaces/ITransitionBuilder.cs b/Core/Stateflows/StateMachines/Registration/Interfaces/ITransitionBuilder.cs index 86663175..236aa5ed 100644 --- a/Core/Stateflows/StateMachines/Registration/Interfaces/ITransitionBuilder.cs +++ b/Core/Stateflows/StateMachines/Registration/Interfaces/ITransitionBuilder.cs @@ -1,13 +1,11 @@ -using System; -using System.Threading.Tasks; -using Stateflows.Common; -using Stateflows.StateMachines.Context.Interfaces; +using Stateflows.Common; +using Stateflows.StateMachines.Registration.Interfaces.Base; namespace Stateflows.StateMachines.Registration.Interfaces { - public interface ITransitionBuilder : IElseTransitionBuilder + public interface ITransitionBuilder : + IEffect>, + IGuard> where TEvent : Event, new() - { - ITransitionBuilder AddGuard(Func, Task> guardAsync); - } + { } } diff --git a/Core/Stateflows/StateMachines/Registration/StateMachinesRegister.cs b/Core/Stateflows/StateMachines/Registration/StateMachinesRegister.cs index a5acb985..1187a100 100644 --- a/Core/Stateflows/StateMachines/Registration/StateMachinesRegister.cs +++ b/Core/Stateflows/StateMachines/Registration/StateMachinesRegister.cs @@ -51,7 +51,7 @@ private bool IsNewestVersion(string stateMachineName, int version) return result; } - //[DebuggerHidden] + [DebuggerHidden] public void AddStateMachine(string stateMachineName, int version, StateMachineBuildAction buildAction) { var key = $"{stateMachineName}.{version}"; @@ -74,7 +74,7 @@ public void AddStateMachine(string stateMachineName, int version, StateMachineBu } } - //[DebuggerHidden] + [DebuggerHidden] public void AddStateMachine(string stateMachineName, int version, Type stateMachineType) { var key = $"{stateMachineName}.{version}"; @@ -103,14 +103,16 @@ public void AddStateMachine(string stateMachineName, int version, Type stateMach } } - //[DebuggerHidden] + [DebuggerHidden] public void AddStateMachine(string stateMachineName, int version = 1) where TStateMachine : StateMachine => AddStateMachine(stateMachineName, version, typeof(TStateMachine)); + [DebuggerHidden] public void AddGlobalInterceptor(StateMachineInterceptorFactory interceptorFactory) => GlobalInterceptorFactories.Add(interceptorFactory); + [DebuggerHidden] public void AddGlobalInterceptor() where TInterceptor : class, IStateMachineInterceptor { @@ -118,9 +120,11 @@ public void AddGlobalInterceptor() AddGlobalInterceptor(serviceProvider => serviceProvider.GetRequiredService()); } + [DebuggerHidden] public void AddGlobalExceptionHandler(StateMachineExceptionHandlerFactory exceptionHandlerFactory) => GlobalExceptionHandlerFactories.Add(exceptionHandlerFactory); + [DebuggerHidden] public void AddGlobalExceptionHandler() where TExceptionHandler : class, IStateMachineExceptionHandler { @@ -128,9 +132,11 @@ public void AddGlobalExceptionHandler() AddGlobalExceptionHandler(serviceProvider => serviceProvider.GetRequiredService()); } + [DebuggerHidden] public void AddGlobalObserver(StateMachineObserverFactory observerFactory) => GlobalObserverFactories.Add(observerFactory); + [DebuggerHidden] public void AddGlobalObserver() where TObserver : class, IStateMachineObserver { diff --git a/Core/Stateflows/StateMachines/StateMachinesDependencyInjection.cs b/Core/Stateflows/StateMachines/StateMachinesDependencyInjection.cs index 2952aea8..dd0ce751 100644 --- a/Core/Stateflows/StateMachines/StateMachinesDependencyInjection.cs +++ b/Core/Stateflows/StateMachines/StateMachinesDependencyInjection.cs @@ -25,7 +25,7 @@ public static IStateflowsBuilder AddStateMachines(this IStateflowsBuilder statef } [DebuggerHidden] - public static IStateflowsBuilder AddDefaultInstance(this IStateflowsBuilder stateflowsBuilder, InitializationRequestFactoryAsync initializationRequestFactoryAsync = null) + public static IStateflowsBuilder AddDefaultInstance(this IStateflowsBuilder stateflowsBuilder, DefaultInstanceInitializationRequestFactoryAsync initializationRequestFactoryAsync = null) where TStateMachine : StateMachine => stateflowsBuilder.AddDefaultInstance(new StateMachineClass(StateMachineInfo.Name).BehaviorClass, initializationRequestFactoryAsync); @@ -40,16 +40,20 @@ private static StateMachinesRegister EnsureStateMachinesServices(this IStateflow .EnsureStateflowServices() .ServiceCollection .AddScoped() - .AddScoped() + .AddScoped() .AddScoped() + .AddScoped() .AddSingleton(register) .AddSingleton() .AddTransient() .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() ; } diff --git a/Examples/Blazor/Server/Blazor.Server/Pages/Activities.razor b/Examples/Blazor/Server/Blazor.Server/Pages/Activities.razor index 601a0e7c..68e0e344 100644 --- a/Examples/Blazor/Server/Blazor.Server/Pages/Activities.razor +++ b/Examples/Blazor/Server/Blazor.Server/Pages/Activities.razor @@ -49,7 +49,7 @@ @code { private string? plantUmlUrl = null; - private string? activityName = "activity1"; + private string? activityName = "activity3"; private string? ActivityName { get => activityName; @@ -102,7 +102,7 @@ { if (locator.TryLocateActivity(GetCurrentId(), out var activity)) { - message = (await activity.InitializeAsync()).Response?.InitializationSuccessful ?? false + message = (await activity.InitializeAsync(new X.InitializationRequest1() { Foo = "bar" })).Response?.InitializationSuccessful ?? false ? "Activity located and initialized" : "Activity located, it's already initialized"; diff --git a/Examples/Blazor/Server/Blazor.Server/Pages/StateMachines.razor b/Examples/Blazor/Server/Blazor.Server/Pages/StateMachines.razor index 8a5ed924..5d34854f 100644 --- a/Examples/Blazor/Server/Blazor.Server/Pages/StateMachines.razor +++ b/Examples/Blazor/Server/Blazor.Server/Pages/StateMachines.razor @@ -1,4 +1,5 @@ @page "/state-machines" +@using Examples.Common @inject IStateMachineLocator locator @inject ISystem system @@ -72,6 +73,65 @@ private string message = ""; + private IStateMachine? _stateMachine = null; + + private async Task GetStateMachineAsync() + { + var instance = $"{stateMachineInstance}"; + if (_stateMachine?.Id == GetCurrentId()) + { + return _stateMachine; + } + + if (_stateMachine != null) + { + await _stateMachine.UnwatchCurrentStateAsync(); + await _stateMachine.UnwatchPlantUmlAsync(); + _stateMachine.Dispose(); + } + + + + if (locator.TryLocateStateMachine(GetCurrentId(), out _stateMachine)) + { + await _stateMachine.WatchAsync(async n => + { + message = n.TheresSomethingHappeningHere; + + await InvokeAsync(() => StateHasChanged()); + }); + + await _stateMachine.WatchCurrentStateAsync(async n => + { + Console.WriteLine($"current state changed: {instance}"); + expectedEvents = n.ExpectedEvents; + message = n.BehaviorStatus switch + { + BehaviorStatus.NotInitialized => "State Machine located, it's not initialized", + BehaviorStatus.Initialized => "State Machine located, it's initialized", + BehaviorStatus.Finalized => "State Machine located, it's finalized", + _ => "" + }; + + await InvokeAsync(() => StateHasChanged()); + }); + + await _stateMachine.WatchPlantUmlAsync(async n => + { + plantUmlUrl = n.GetUrl(); + + await InvokeAsync(() => StateHasChanged()); + }); + } + else + { + message = "Unable to locate State Machine"; + _stateMachine = null; + } + + return _stateMachine; + } + private StateMachineId GetCurrentId() { return new StateMachineId(stateMachineName, stateMachineInstance); @@ -99,21 +159,21 @@ plantUmlUrl = (await stateMachine.GetPlantUmlAsync()).Response.GetUrl(); } - private async Task SystemRequest() - { - var classes = await system.GetAvailableBehaviorClassesAsync(); - var instances = await system.GetBehaviorInstancesAsync(); - } - private async Task Initialize() { - if (locator.TryLocateStateMachine(GetCurrentId(), out var stateMachine)) + var sm = await GetStateMachineAsync(); + if (sm != null) { - var msg = (await stateMachine.InitializeAsync()).Response?.InitializationSuccessful ?? false + var result = await sm.InitializeAsync(); + + var msg = result.Response?.InitializationSuccessful ?? false ? "State Machine located and initialized" : "State Machine located, it's already initialized"; - await RefreshSMInfo(stateMachine); + if (!result.Response?.InitializationSuccessful ?? false) + { + await RefreshSMInfo(sm); + } message = msg; } @@ -125,28 +185,20 @@ private async Task GetStatus() { - if (locator.TryLocateStateMachine(GetCurrentId(), out var stateMachine)) - { - await RefreshSMInfo(stateMachine); - } - else + var sm = await GetStateMachineAsync(); + if (sm != null) { - message = "Unable to locate State Machine"; + await RefreshSMInfo(sm); } } private async Task Reset() { - if (locator.TryLocateStateMachine(GetCurrentId(), out var stateMachine)) + var sm = await GetStateMachineAsync(); + if (sm != null) { - var response = (await stateMachine.ResetAsync())?.Response; + var response = (await sm.ResetAsync())?.Response; message = "State Machine resetted"; - - await RefreshSMInfo(stateMachine); - } - else - { - message = "Unable to locate State Machine"; } } @@ -163,16 +215,15 @@ { var ev = System.Activator.CreateInstance(eventType) as Event; - if (locator.TryLocateStateMachine(GetCurrentId(), out var stateMachine)) + var sm = await GetStateMachineAsync(); + if (sm != null) { - var result = await stateMachine.SendAsync(ev); + var result = await sm.SendAsync(ev); if (!(result.Validation?.IsValid ?? true)) { message = "Event invalid"; } - - await RefreshSMInfo(stateMachine); } } } diff --git a/Examples/Blazor/Server/Blazor.Server/Program.cs b/Examples/Blazor/Server/Blazor.Server/Program.cs index bd5f0d1c..f84d5ae8 100644 --- a/Examples/Blazor/Server/Blazor.Server/Program.cs +++ b/Examples/Blazor/Server/Blazor.Server/Program.cs @@ -2,6 +2,7 @@ using Blazor.Server.Data; using Examples.Common; using Stateflows; +using Stateflows.Utils; using Stateflows.Common; using Stateflows.Common.Data; using Stateflows.StateMachines; @@ -15,7 +16,9 @@ using Stateflows.Activities.Registration.Interfaces; using Stateflows.StateMachines.Attributes; using Examples.Storage; -using TestNamespace; +using X; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; var builder = WebApplication.CreateBuilder(args); @@ -29,184 +32,80 @@ builder.Services.AddStateflows(b => b .AddPlantUml() - .AddActivities(b => b - .AddActivity("sync0", b => b - .AddInitial(b => b - .AddControlFlow("1") - ) - .AddStructuredActivity("1", b => b - .AddAcceptEventAction("time1", async c => c.Output(c.Event), b => b - .AddFlow() + //.AddStorage() + + .AddTracing() + + .AddStateMachines(b => b + .AddStateMachine("stateMachine1", b => b + .AddInitialState("state1", b => b + .AddTransition("state2") + .AddInternalTransition(b => b + .AddEffect(c => c.Event.Respond(new ExampleResponse() { ResponseData = "Example response data" })) ) - .AddFinal() - .AddControlFlow("2") ) - .AddStructuredActivity("2", b => b - .AddTimeEventAction("time2", async c => { }) + .AddState("state2", b => b + .AddOnEntry(c => + { + c.StateMachine.Publish(new SomeNotification()); + }) + .AddTransition("state1") ) - .AddTimeEventAction("time3", async c => { }) + ) - //.AddTimeEventAction("time", async c => { }, b => b - // .AddControlFlow("merge") - //) - //.AddMerge("merge", b => b - // .AddControlFlow("sync_table_1") - //) - //.AddAction("sync_table_1", async c => - //{ - // /* implementation */ - // Debug.WriteLine("table 1 synced"); - //}) + .AddStateMachine("stateMachine2", b => b + .AddInitialState("state1", b => b + .AddOnEntry(async c => + { + await c.StateMachine.SubscribeAsync(new StateMachineId("stateMachine1", "x")); + }) + .AddTransition("state2") + ) + .AddState("state2") ) ) - .AddDefaultInstance(new ActivityClass("sync0")) - - .AddStorage() - - .AddTracing() - .AddActivities(b => b - .AddActivity("activity1", b => b - .AddOnInitialize(async c => + .AddActivity("activity2", b => b + .AddOnInitialize(async c => { - Debug.WriteLine("initialize"); + Debug.WriteLine(c.InitializationRequest.Foo); + return true; }) - - .AddInitial(b => b - .AddControlFlow("1") - ) - .AddAction("1", - async c => - { - c.OutputRange((new[] { 1, 2, 3, 4, 5 }).ToTokens()); - c.OutputRange((new[] { "1", "2", "3" }).ToTokens()); - }, - b => b.AddDataFlow("2") - ) - .AddAction("2", - async c => - { - Debug.WriteLine("action 2"); - } - ) - .AddAcceptEventAction("event", - async c => - { - Debug.WriteLine(c.Event.TheresSomethingHappeningHere); - }, - b => b - .AddControlFlow("decision") + .AddControlFlow("main") ) - .AddTimeEventAction("time", - async c => + .AddStructuredActivity("main", b => b + .AddExceptionHandler(async c => { - c.Output(Random.Shared.Next(1, 5)); - c.Output(Random.Shared.Next(1, 5)); - c.Output(Random.Shared.Next(1, 5)); - c.Output(Random.Shared.Next(1, 5)); - c.Output(Random.Shared.Next(1, 5)); - c.Output(Random.Shared.Next(1, 5)); - c.Output(Random.Shared.Next(1, 5)); - c.Output(Random.Shared.Next(1, 5)); - c.Output(Random.Shared.Next(1, 5)); - c.Output(Random.Shared.Next(1, 5)); - }, - b => b - .AddDataFlow("data") - ) - .AddDataStore("data", b => b - .AddDataFlow("decision") - ) - .AddDataDecision("decision", b => b - .AddFlow("3", b => b - .AddGuard(async c => c.Token.Payload < 3) - .AddTransformation(async c => - { - Debug.WriteLine($"token going to 3: {c.Token.Payload}"); - return c.Token; - }) + Debug.WriteLine(c.Exception.Message); + }) + .AddInitial(b => b + .AddControlFlow("action1") ) - .AddFlow("4", b => b - .AddGuard(async c => c.Token.Payload < 6) - .AddTransformation(async c => - { - Debug.WriteLine($"token going to 4: {c.Token.Payload}"); - return c.Token; - }) + .AddAction( + "action1", + async c => c.Output(new Token { Payload = 42 }), + b => b + .AddFlow, Flow1>("action2") ) - .AddElseFlow("5" - , b => b.AddTransformation(async c => + .AddAction( + "action2", + async c => { - Debug.WriteLine($"token going to 5: {c.Token.Payload}"); - return c.Token; - }) + Debug.WriteLine(c.Input.OfType>().First().Payload); + throw new Exception("test"); + } ) ) - .AddAction("3", - async c => - { - Debug.WriteLine($"finish 3! {c.Input.OfType>().Count()}"); - } - ) - .AddAction("4", - async c => - { - Debug.WriteLine($"finish 4! {c.Input.OfType>().Count()}"); - } - ) - .AddAction("5", - async c => - { - Debug.WriteLine($"finish 5! {c.Input.OfType>().Count()}"); - }, - b => b.AddControlFlow() - ) - .AddFinal() ) - ) - .AddStateMachines(b => b - .AddStateMachine() - //.AddStateMachine("stateMachine1", b => b - // .AddInitialState("state1", b => b - // .AddTransition("state3", b => { } - // //.AddEffect(c => Console.Write(c.Event.Payload.Prop)) - // ) - // .AddTransition("state2") - // .AddTransition("state2", b => b - // .AddGuard(c => false) - // ) - // //.AddElseTransition("state2") - // .AddTransition("state2", b => b - // .AddEffect(c => throw new Exception("test")) - // ) - // .AddInternalTransition(b => b - // .AddEffect(c => - // { - // c.Event.Respond(new ExampleResponse()); - // c.StateMachine.Send(new TestNamespace.MyClass().ToEvent()); - // }) - // ) - // ) - // .AddState("state2", b => b - // .AddTransition("state3") - // .AddOnDoActivity("activity1") - // ) - // .AddState("state3", b => b - // .AddTransition("state1", b => b - // .AddGuard(c => Random.Shared.Next(1, 10) % 2 == 0) - // //.AddEffect(c => Debug.WriteLine("Even, going to state1")) - // ) - // .AddElseTransition("state2", b => { } - // //.AddEffect(c => Debug.WriteLine("Odd, going to state2")) - // ) - // ) - //) + .AddActivity("activity3") ) + .AddAutoInitialization(new StateMachineClass("stateMachine1")) + .SetEnvironment( builder.Environment.IsDevelopment() ? $"{StateflowsEnvironments.Development}.{Environment.MachineName}" @@ -237,91 +136,93 @@ app.Run(); -public class Action1 : Stateflows.Activities.ActionNode +namespace X { - public readonly Input> Ints; - public readonly Input> Strings; - public readonly Output> IntsOut; - - public override async Task ExecuteAsync() + public class InitializationRequest1 : InitializationRequest { - await Task.Delay(1000); - - Debug.WriteLine("ints count: " + Ints.Count().ToString() ?? "input not working"); - - IntsOut.AddRange(Ints); - - await Task.Delay(1000); + public string Foo { get; set; } } -} - -public class Action2 : Stateflows.Activities.ActionNode -{ - public readonly OptionalInput> Strings; - public readonly Output> StringsOut; - public override async Task ExecuteAsync() + public class Activity3 : Stateflows.Activities.Activity { - await Task.Delay(1000); - - Debug.WriteLine("strings count: " + Strings.Count().ToString() ?? "input not working"); - - StringsOut.AddRange(Strings); - } -} - -namespace TestNamespace -{ - public class MyClass - { - public string Prop { get; set; } - } - - [ActivityBehavior] - public class Activity1 : Stateflows.Activities.Activity - { - public override void Build(ITypedActivityBuilder builder) + private async Task MethodAsync() { - builder - .AddAction("", async c => { }, b => b - .AddControlFlow("") - ) - .AddAction("", async c => { }) - ; + return; } - } - [StateMachineBehavior(nameof(StateMachine1))] - public class StateMachine1 : Stateflows.StateMachines.StateMachine - { - public override void Build(ITypedStateMachineBuilder builder) - { - builder - .AddInitialState("1", b => b - .AddDefaultTransition() + public override void Build(ITypedActivityBuilder builder) + => builder + .AddAcceptEventAction(async c => { }, b => b + .AddControlFlow("action1") + ) + .AddAction( + "action1", + async c => c.OutputRange(Enumerable.Range(0, 100).ToTokens()), + b => b.AddFlow>("chunked") ) - .AddState() - ; - } - } -} -public class StateX : State -{ - public override Task OnEntryAsync() - { - Debug.WriteLine(Context.StateMachine.Id.Instance); + .AddParallelActivity>( + "chunked", + b => b + .AddInput(b => b + .AddFlow>("main") + ) + .AddAction( + "main", + async c => + { + var tokens = c.Input.OfType>().Select(t => $"value: {t.Payload}").ToTokens(); + c.OutputRange(tokens); + Debug.WriteLine($"{tokens.Count()}/{c.Input.Count()} tokens: {string.Join(", ", c.Input.OfType>().Take(5).Select(t => t.Payload))}..."); + await Task.Delay(1000); + }, + b => b.AddFlow>(OutputNode.Name) + ) + .AddOutput() + .AddFlow>("action2", b => b + .AddGuard(async c => + { + lock (c.Activity.LockHandle) + { + var counter = 0; + c.Activity.Values.TryGet("count", out counter); + + counter++; + c.Activity.Values.Set("count", counter); + } + + return true; + }) + ), + 17 + ) + .AddAction( + "action2", + async c => + { + Debug.WriteLine(c.Input.OfType>().Count().ToString()); + c.Activity.Values.TryGet("count", out var counter); + Debug.WriteLine($"counter: {counter}"); + //foreach (var token in c.Input.OfType>()) + //{ + // Debug.WriteLine(token.Payload); + //} + } + ); + + public override async Task OnInitializeAsync(InitializationRequest1 initializationRequest) + { + Debug.WriteLine(initializationRequest.Foo); - return Task.CompletedTask; + return true; + } } -} -public class SynchronizeDatabase : ActionNode -{ - public override Task ExecuteAsync() + public class Flow1 : Flow> { - Debug.WriteLine("działam"); - - return Task.CompletedTask; + public override async Task GuardAsync() + { + return Context.Token.Payload > 40; + } } } \ No newline at end of file diff --git a/Examples/Blazor/WebAssembly/Client/Pages/StateMachines.razor b/Examples/Blazor/WebAssembly/Client/Pages/StateMachines.razor index d1c629aa..6c3c9781 100644 --- a/Examples/Blazor/WebAssembly/Client/Pages/StateMachines.razor +++ b/Examples/Blazor/WebAssembly/Client/Pages/StateMachines.razor @@ -13,6 +13,8 @@
+ +
@if (message != "") @@ -29,7 +31,7 @@ Events expected in current state: @foreach (var eventName in expectedEvents) { - + } } @@ -69,6 +71,56 @@ private string message = ""; + private IStateMachine? _stateMachine = null; + + private async Task GetStateMachineAsync() + { + var instance = $"{stateMachineInstance}"; + if (_stateMachine?.Id == GetCurrentId()) + { + return _stateMachine; + } + + if (_stateMachine != null) + { + await _stateMachine.UnwatchCurrentStateAsync(); + await _stateMachine.UnwatchPlantUmlAsync(); + _stateMachine.Dispose(); + } + + if (locator.TryLocateStateMachine(GetCurrentId(), out _stateMachine)) + { + await _stateMachine.WatchCurrentStateAsync(async n => + { + Console.WriteLine($"current state changed: {instance}"); + expectedEvents = n.ExpectedEvents; + message = n.BehaviorStatus switch + { + BehaviorStatus.NotInitialized => "State Machine located, it's not initialized", + BehaviorStatus.Initialized => "State Machine located, it's initialized", + BehaviorStatus.Finalized => "State Machine located, it's finalized", + _ => "" + }; + + await InvokeAsync(() => StateHasChanged()); + }); + + await _stateMachine.WatchPlantUmlAsync(async n => + { + plantUmlUrl = n.GetUrl(); + + await InvokeAsync(() => StateHasChanged()); + }); + } + else + { + message = "Unable to locate State Machine"; + _stateMachine = null; + } + + return _stateMachine; + } + private StateMachineId GetCurrentId() { return new StateMachineId(stateMachineName, stateMachineInstance); @@ -83,29 +135,36 @@ private async Task RefreshSMInfo(IStateMachine stateMachine) { - expectedEvents = (await stateMachine.GetCurrentStateAsync()).Response.ExpectedEvents; + var result = await stateMachine.GetCurrentStateAsync(); + expectedEvents = result.Response?.ExpectedEvents; + message = result.Response?.BehaviorStatus switch + { + BehaviorStatus.NotInitialized => "State Machine located, it's not initialized", + BehaviorStatus.Initialized => "State Machine located, it's initialized", + BehaviorStatus.Finalized => "State Machine located, it's finalized", + _ => "" + }; plantUmlUrl = (await stateMachine.GetPlantUmlAsync()).Response.GetUrl(); } private async Task Initialize() { - if (locator.TryLocateStateMachine(GetCurrentId(), out var stateMachine)) + var sm = await GetStateMachineAsync(); + if (sm != null) { - var result = await stateMachine.InitializeAsync(); - await stateMachine.SendAsync(new CompoundRequest() - { - Events = new List() - { - new Examples.Common.SomeEvent(), - new Examples.Common.OtherEvent() - } - }); - message = result.Response?.InitializationSuccessful ?? false + var result = await sm.InitializeAsync(); + + var msg = result.Response?.InitializationSuccessful ?? false ? "State Machine located and initialized" : "State Machine located, it's already initialized"; - await RefreshSMInfo(stateMachine); + if (!result.Response?.InitializationSuccessful ?? false) + { + await RefreshSMInfo(sm); + } + + message = msg; } else { @@ -113,19 +172,47 @@ } } + private async Task GetStatus() + { + var sm = await GetStateMachineAsync(); + if (sm != null) + { + await RefreshSMInfo(sm); + } + } + + private async Task Reset() + { + var sm = await GetStateMachineAsync(); + if (sm != null) + { + var response = (await sm.ResetAsync())?.Response; + message = "State Machine resetted"; + } + } + + private string GetEventName(string eventName) + => eventName.Contains('<') + ? $"{eventName.Split('<').First().Split('.').Last()}<{eventName.Split('<').Last()}" + : eventName.Split('.').Last(); + private async Task SendEvent(string eventName) { var eventType = Type.GetType($"{eventName}, Examples.Common"); if (eventType != null) { - var @event = System.Activator.CreateInstance(eventType); + var ev = System.Activator.CreateInstance(eventType) as Event; - if (locator.TryLocateStateMachine(GetCurrentId(), out var stateMachine)) + var sm = await GetStateMachineAsync(); + if (sm != null) { - var result = await stateMachine.SendAsync(@event as Event); + var result = await sm.SendAsync(ev); - await RefreshSMInfo(stateMachine); + if (!(result.Validation?.IsValid ?? true)) + { + message = "Event invalid"; + } } } } diff --git a/Examples/Blazor/WebAssembly/Server/Program.cs b/Examples/Blazor/WebAssembly/Server/Program.cs index 42ee06aa..dff27d4f 100644 --- a/Examples/Blazor/WebAssembly/Server/Program.cs +++ b/Examples/Blazor/WebAssembly/Server/Program.cs @@ -46,6 +46,7 @@ c.SourceState.Values.Set("counter", counter + 1); }) ) + .AddTransition("state2") .AddDefaultTransition("state2", b => b .AddGuard(c => { diff --git a/Examples/Examples.Common/SomeNotification.cs b/Examples/Examples.Common/SomeNotification.cs new file mode 100644 index 00000000..53b86a89 --- /dev/null +++ b/Examples/Examples.Common/SomeNotification.cs @@ -0,0 +1,9 @@ +using Stateflows.Common; + +namespace Examples.Common +{ + public class SomeNotification : Notification + { + public string TheresSomethingHappeningHere { get; set; } = "What it is ain't exactly clear"; + } +} \ No newline at end of file diff --git a/Examples/SignalR/SignalR.Client/ClientApp/src/app/counter/counter.component.ts b/Examples/SignalR/SignalR.Client/ClientApp/src/app/counter/counter.component.ts index 52a8fa37..1351ea37 100644 --- a/Examples/SignalR/SignalR.Client/ClientApp/src/app/counter/counter.component.ts +++ b/Examples/SignalR/SignalR.Client/ClientApp/src/app/counter/counter.component.ts @@ -1,11 +1,12 @@ import { HttpClient } from '@angular/common/http'; import { Component } from '@angular/core'; import * as plantUmlEncoder from 'plantuml-encoder'; -import { StateflowsClient, StateMachineId, BehaviorStatus, EventStatus, PlantUmlRequest, PlantUmlResponse, Event, InitializationRequest } from '@stateflows/common'; +import { StateflowsClient, StateMachineId, BehaviorStatus, EventStatus, PlantUmlRequest, PlantUmlResponse, Event, InitializationRequest, CompoundRequest, IStateMachine, PlantUmlNotification, CurrentStateNotification } from '@stateflows/common'; import { UseHttp } from '@stateflows/http-client'; -import { UseSignalR } from '@stateflows/signalr-client'; +// import { UseSignalR } from '@stateflows/signalr-client'; class OtherEvent extends Event { public $type: string = "Examples.Common.OtherEvent, Examples.Common"; + public name: string = "Examples.Common.OtherEvent"; //public RequiredParameter: string | null = null; } @@ -15,41 +16,46 @@ class OtherEvent extends Event { }) export class CounterComponent { public currentCount = 0; - private stateflows: StateflowsClient = new StateflowsClient(UseSignalR("https://localhost:7067/")); + private stateMachine: IStateMachine | null = null; + private stateflows: StateflowsClient = new StateflowsClient(UseHttp("https://localhost:7067/")); + // private stateflows: StateflowsClient = new StateflowsClient(UseSignalR("https://localhost:7067/", (b) => b.withAutomaticReconnect())); public url: string | null = null; constructor(private http: HttpClient) { } public async refresh() { - let sm = await this.stateflows.stateMachineLocator.locateStateMachine(new StateMachineId("stateMachine1", "x")); - let encoded = plantUmlEncoder.encode((await sm.request(new PlantUmlRequest())).Response.PlantUml); + this.stateMachine = await this.stateflows.stateMachineLocator.locateStateMachine(new StateMachineId("stateMachine1", "x")); + let encoded = plantUmlEncoder.encode((await this.stateMachine.request(new PlantUmlRequest())).response.plantUml); this.url = 'http://www.plantuml.com/plantuml/img/' + encoded; } public async go() { this.http.get('https://localhost:7067/StateMachine/stateMachine1/x/go').subscribe(() => this.refresh()); } - + public async push() { this.http.post('https://localhost:7067/StateMachine/stateMachine1/x/push', { "dolor": "sit amet" }).subscribe(() => this.refresh()); } public async incrementCounter() { - let sm = await this.stateflows.stateMachineLocator.locateStateMachine(new StateMachineId("stateMachine1", "xxxx")); - let x = await sm.getStatus(); - sm.send(new Event()); - console.log(x); - if (x.Response.BehaviorStatus == BehaviorStatus.NotInitialized) { - await sm.initialize(new InitializationRequest()); - } + if (this.stateMachine === null) { + this.stateMachine = await this.stateflows.stateMachineLocator.locateStateMachine(new StateMachineId("stateMachine1", Math.random().toString())); + + await this.stateMachine.watch("Examples.Common.SomeNotification", (n: any) => console.log('Notification:', n)); + + await this.stateMachine.watch(PlantUmlNotification.notificationName, (n: PlantUmlNotification) => { + this.url = 'http://www.plantuml.com/plantuml/img/' + plantUmlEncoder.encode(n.plantUml); + }); + await this.stateMachine.watchCurrentState((n: CurrentStateNotification) => console.log(n.statesStack)); + + let x = await this.stateMachine.getStatus(); - let result = (await sm.send(new OtherEvent())); - if (result.Status == EventStatus.Consumed) { - let encoded = plantUmlEncoder.encode((await sm.request(new PlantUmlRequest())).Response.PlantUml); - this.url = 'http://www.plantuml.com/plantuml/img/' + encoded; + if (x.response.behaviorStatus == BehaviorStatus.NotInitialized) { + await this.stateMachine.initialize(new InitializationRequest()); + } } - let system = await this.stateflows.system; + let result = (await this.stateMachine.request(new CompoundRequest([new OtherEvent()]))); this.currentCount++; } diff --git a/Examples/SignalR/SignalR.Client/Program.cs b/Examples/SignalR/SignalR.Client/Program.cs index 10e4e887..94c2575f 100644 --- a/Examples/SignalR/SignalR.Client/Program.cs +++ b/Examples/SignalR/SignalR.Client/Program.cs @@ -1,6 +1,7 @@ using Examples.Common; using SignalR.Client; using Stateflows; +using Stateflows.Activities; using Stateflows.Common; using Stateflows.StateMachines; using Stateflows.StateMachines.Sync; @@ -19,13 +20,45 @@ .AddStateMachine("stateMachine1", b => b .AddInitialState("state1", b => b .AddTransition("state2") - .AddHttpGetInternalTransition("/my-url") + .AddTransition("state2") ) .AddState("state2", b => b + .AddOnEntry(c => c.StateMachine.Publish(new SomeNotification())) .AddTransition("state1") + .AddTransition("state1") ) ) ) + + + //.AddActivities(b => b + // .AddActivity("handleVerification", b => b + // .AddAcceptEventAction>(async c => + // { + // var allDossierIds = getDossierIds(c.SelectionCriteria); + // c.OutputRange(allDossierIds.ToTokens()); + // }, + // b => b.AddFlow>("processing") + // ) + + // .AddIterativeActivity>( + // "processing", + // b => b + // .AddInput(b => b + // .AddFlow>("process") + // ) + // .AddTimeEventAction(b => b + // .AddControlFlow("process") + // ) + // .AddAction("process", + // async c => { /* processing */ } + // ), + // 10 + // ) + // ) + //) + + .AddPlantUml() .SetEnvironment( builder.Environment.IsDevelopment() @@ -68,6 +101,3 @@ app.MapFallbackToFile("index.html"); app.Run(); - -public class Tran1 : Transition> { } -public class Tran2 : Transition> { } \ No newline at end of file diff --git a/Extensions/Stateflows.Extensions.MinimalAPI/Classes/HttpRequest.cs b/Extensions/Stateflows.Extensions.MinimalAPI/Classes/HttpRequest.cs index 3ace1764..ffa8d1c5 100644 --- a/Extensions/Stateflows.Extensions.MinimalAPI/Classes/HttpRequest.cs +++ b/Extensions/Stateflows.Extensions.MinimalAPI/Classes/HttpRequest.cs @@ -2,7 +2,7 @@ namespace Stateflows.Common { - public class HttpRequest : Request> + public class HttpRequest : Request> { public override string Name => $"{base.Name}[{Method} {Url}]"; @@ -20,10 +20,10 @@ public string Method set => method = value; } - public HttpRequest Request { get; set; } + public Microsoft.AspNetCore.Http.HttpRequest Request { get; set; } } - public class HttpRequest : Request + public class HttpRequest : Request { public override string Name => $"{base.Name}[{Method} {Url}]"; @@ -41,6 +41,6 @@ public string Method set => method = value; } - public HttpRequest Request { get; set; } + public Microsoft.AspNetCore.Http.HttpRequest Request { get; set; } } } diff --git a/Extensions/Stateflows.Extensions.MinimalAPI/StateMachines/StateBuilder/TransitionExtensions.cs b/Extensions/Stateflows.Extensions.MinimalAPI/StateMachines/StateBuilder/TransitionExtensions.cs index d933a85e..9ca80c91 100644 --- a/Extensions/Stateflows.Extensions.MinimalAPI/StateMachines/StateBuilder/TransitionExtensions.cs +++ b/Extensions/Stateflows.Extensions.MinimalAPI/StateMachines/StateBuilder/TransitionExtensions.cs @@ -4,6 +4,7 @@ using Stateflows.Common; using Stateflows.Common.Registration; using Stateflows.StateMachines.Registration.Interfaces; +using HttpRequest = Stateflows.Common.HttpRequest; namespace Stateflows.StateMachines { @@ -11,7 +12,7 @@ public static class TransitionExtensions { private static readonly List Endpoints = new(); - public static IStateBuilder AddHttpGetInternalTransition(this IStateBuilder builder, string pattern, TransitionBuildAction>? transitionbuildAction = null, Action? endpointbuildAction = null) + public static IStateBuilder AddHttpGetInternalTransition(this IStateBuilder builder, string pattern, InternalTransitionBuildAction? transitionbuildAction = null, Action? endpointbuildAction = null) { if (builder is IBehaviorBuilder behaviorBuilder) { @@ -31,7 +32,7 @@ public static IStateBuilder AddHttpGetInternalTransition(this HttpEvent.UrlOverride = pattern; HttpEvent.MethodOverride = "GET"; - builder.AddInternalTransition>(transitionbuildAction); + builder.AddInternalTransition(transitionbuildAction); HttpEvent.UrlOverride = null; HttpEvent.MethodOverride = null; @@ -43,12 +44,12 @@ public static IStateBuilder AddHttpGetInternalTransition(this fullPattern, async (string instance, HttpContext context, IBehaviorLocator locator) => { - RequestResult>? result = locator.TryLocateBehavior(new BehaviorId(behaviorClass, instance), out var behavior) - ? await behavior.RequestAsync(new HttpRequest() { Method = "GET", Url = pattern, Request = context.Request }) + RequestResult>? result = locator.TryLocateBehavior(new BehaviorId(behaviorClass, instance), out var behavior) + ? await behavior.RequestAsync(new HttpRequest() { Method = "GET", Url = pattern, Request = context.Request }) : null; return result?.Status == EventStatus.Consumed - ? Results.Ok(result) + ? result?.Response?.Payload ?? Results.Ok() : Results.NotFound(); } ); @@ -61,7 +62,7 @@ public static IStateBuilder AddHttpGetInternalTransition(this return builder; } - public static IStateBuilder AddHttpPostTransition(this IStateBuilder builder, string pattern, string targetVertexName, TransitionBuildAction>? transitionbuildAction = null, Action? endpointbuildAction = null) + public static IStateBuilder AddHttpPostTransition(this IStateBuilder builder, string pattern, string targetVertexName, TransitionBuildAction>? transitionbuildAction = null, Action? endpointbuildAction = null) { if (builder is IBehaviorBuilder behaviorBuilder) { @@ -81,7 +82,7 @@ public static IStateBuilder AddHttpPostTransition>(targetVertexName, transitionbuildAction); + builder.AddTransition>(targetVertexName, transitionbuildAction); HttpEvent.UrlOverride = null; HttpEvent.MethodOverride = null; @@ -93,12 +94,12 @@ public static IStateBuilder AddHttpPostTransition { - RequestResult>? result = locator.TryLocateBehavior(new BehaviorId(behaviorClass, instance), out var behavior) - ? await behavior.RequestAsync(new HttpRequest() { Method = "POST", Url = pattern, Request = context.Request, Payload = body }) + RequestResult>? result = locator.TryLocateBehavior(new BehaviorId(behaviorClass, instance), out var behavior) + ? await behavior.RequestAsync(new HttpRequest() { Method = "POST", Url = pattern, Request = context.Request, Payload = body }) : null; return result?.Status == EventStatus.Consumed - ? Results.Ok(result) + ? result?.Response?.Payload ?? Results.Ok() : Results.NotFound(); } ); diff --git a/Extensions/Stateflows.Extensions.MinimalAPI/StateMachines/StateBuilder/TransitionTypedExtensions.cs b/Extensions/Stateflows.Extensions.MinimalAPI/StateMachines/StateBuilder/TransitionTypedExtensions.cs index 8f60e5bb..c7e49655 100644 --- a/Extensions/Stateflows.Extensions.MinimalAPI/StateMachines/StateBuilder/TransitionTypedExtensions.cs +++ b/Extensions/Stateflows.Extensions.MinimalAPI/StateMachines/StateBuilder/TransitionTypedExtensions.cs @@ -8,24 +8,24 @@ namespace Stateflows.StateMachines.Typed public static class TransitionTypedExtensions { #region AddHttpGetInternalTransition - public static IStateBuilder AddHttpGetInternalTransition(this IStateBuilder builder, string pattern, Action endpointbuildAction = null) - where TTransition : Transition> - => builder.AddHttpGetInternalTransition(pattern, b => b.AddTransitionEvents>(), endpointbuildAction); + //public static IStateBuilder AddHttpGetInternalTransition(this IStateBuilder builder, string pattern, Action endpointbuildAction = null) + // where TTransition : Transition + // => builder.AddHttpGetInternalTransition(pattern, b => b.AddTransitionEvents(), endpointbuildAction); #endregion #region AddHttpPostTransition - public static IStateBuilder AddHttpPostTransition(this IStateBuilder builder, string pattern, TransitionBuildAction> transitionbuildAction = null, Action endpointbuildAction = null) + public static IStateBuilder AddHttpPostTransition(this IStateBuilder builder, string pattern, TransitionBuildAction> transitionBuildAction = null, Action endpointBuildAction = null) where TTargetState : BaseState - => builder.AddHttpPostTransition(pattern, StateInfo.Name, transitionbuildAction, endpointbuildAction); + => builder.AddHttpPostTransition(pattern, StateInfo.Name, transitionBuildAction, endpointBuildAction); - public static IStateBuilder AddHttpPostTransition(this IStateBuilder builder, string pattern, Action endpointbuildAction = null) - where TTransition : Transition> + public static IStateBuilder AddHttpPostTransition(this IStateBuilder builder, string pattern, Action endpointbuildAction = null) + where TTransition : Transition> where TTargetState : BaseState - => builder.AddHttpPostTransition(pattern, StateInfo.Name, b => b.AddTransitionEvents>(), endpointbuildAction); + => builder.AddHttpPostTransition(pattern, StateInfo.Name, b => b.AddTransitionEvents>(), endpointbuildAction); - public static IStateBuilder AddHttpPostTransition(this IStateBuilder builder, string pattern, string targetVertexName, Action endpointbuildAction = null) - where TTransition : Transition> - => builder.AddHttpPostTransition(pattern, targetVertexName, b => b.AddTransitionEvents>(), endpointbuildAction); + public static IStateBuilder AddHttpPostTransition(this IStateBuilder builder, string pattern, string targetVertexName, Action endpointbuildAction = null) + where TTransition : Transition> + => builder.AddHttpPostTransition(pattern, targetVertexName, b => b.AddTransitionEvents>(), endpointbuildAction); #endregion } } \ No newline at end of file diff --git a/Extensions/Stateflows.Extensions.PlantUml/Classes/PlantUmlHandler.cs b/Extensions/Stateflows.Extensions.PlantUml/Classes/PlantUmlHandler.cs index dd6df788..40261057 100644 --- a/Extensions/Stateflows.Extensions.PlantUml/Classes/PlantUmlHandler.cs +++ b/Extensions/Stateflows.Extensions.PlantUml/Classes/PlantUmlHandler.cs @@ -1,10 +1,9 @@ using System; using System.Threading.Tasks; using Stateflows.Common; +using Stateflows.Activities; using Stateflows.StateMachines; using Stateflows.Extensions.PlantUml.Events; -using Stateflows.Activities; -using Stateflows.Activities.Inspection.Interfaces; namespace Stateflows.Extensions.PlantUml.Classes { @@ -12,11 +11,11 @@ internal class PlantUmlHandler : IStateMachineEventHandler, IActivityEventHandle { public Type EventType => typeof(PlantUmlRequest); - public Task TryHandleEventAsync(Stateflows.StateMachines.Inspection.Interfaces.IEventInspectionContext context) + public Task TryHandleEventAsync(StateMachines.Inspection.Interfaces.IEventInspectionContext context) where TEvent : Event, new() => Task.FromResult(HandleEvent(context.Event, () => context.StateMachine.Inspection.GetPlantUml())); - public Task TryHandleEventAsync(IEventInspectionContext context) + public Task TryHandleEventAsync(Activities.Inspection.Interfaces.IEventInspectionContext context) where TEvent : Event, new() => Task.FromResult(HandleEvent(context.Event, () => context.Activity.Inspection.GetPlantUml())); diff --git a/Extensions/Stateflows.Extensions.PlantUml/Classes/StateMachineInterceptor.cs b/Extensions/Stateflows.Extensions.PlantUml/Classes/StateMachineInterceptor.cs new file mode 100644 index 00000000..b2d0ee9c --- /dev/null +++ b/Extensions/Stateflows.Extensions.PlantUml/Classes/StateMachineInterceptor.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using Stateflows.Common; +using Stateflows.StateMachines; +using Stateflows.StateMachines.Context.Interfaces; +using Stateflows.StateMachines.Inspection.Interfaces; +using Stateflows.Extensions.PlantUml.Events; + +namespace Stateflows.Extensions.PlantUml.Classes +{ + internal class StateMachineInterceptor : IStateMachineInterceptor + { + public Task AfterHydrateAsync(IStateMachineActionContext context) + => Task.CompletedTask; + + public Task AfterProcessEventAsync(IEventContext context) + { + if ( + context is IEventInspectionContext inspectionContext && + inspectionContext.StateMachine.Inspection.StateHasChanged + ) + { + context.StateMachine.Publish( + new PlantUmlNotification() + { + PlantUml = inspectionContext.StateMachine.Inspection.GetPlantUml() + } + ); + } + + return Task.CompletedTask; + } + + public Task BeforeDehydrateAsync(IStateMachineActionContext context) + => Task.CompletedTask; + + public Task BeforeProcessEventAsync(IEventContext context) + => Task.FromResult(true); + } +} diff --git a/Extensions/Stateflows.Extensions.PlantUml/DependencyInjection.cs b/Extensions/Stateflows.Extensions.PlantUml/DependencyInjection.cs index c1ad7257..a916ef69 100644 --- a/Extensions/Stateflows.Extensions.PlantUml/DependencyInjection.cs +++ b/Extensions/Stateflows.Extensions.PlantUml/DependencyInjection.cs @@ -10,9 +10,14 @@ public static class DependencyInjection { public static IStateflowsBuilder AddPlantUml(this IStateflowsBuilder builder) { - builder.ServiceCollection.AddSingleton(); - builder.ServiceCollection.AddSingleton(services => services.GetService()); - builder.ServiceCollection.AddSingleton(services => services.GetService()); + builder + .AddStateMachines(b => b + .AddInterceptor() + ) + .ServiceCollection + .AddSingleton() + .AddSingleton(services => services.GetService()) + .AddSingleton(services => services.GetService()); return builder; } diff --git a/Extensions/Stateflows.Extensions.PlantUml/Events/PlantUmlNotification.cs b/Extensions/Stateflows.Extensions.PlantUml/Events/PlantUmlNotification.cs new file mode 100644 index 00000000..8a3b5d4c --- /dev/null +++ b/Extensions/Stateflows.Extensions.PlantUml/Events/PlantUmlNotification.cs @@ -0,0 +1,15 @@ +using Stateflows.Common; +using Stateflows.Extensions.PlantUml.Classes; + +namespace Stateflows.Extensions.PlantUml.Events +{ + public sealed class PlantUmlNotification : Notification + { + public override string Name => nameof(PlantUmlNotification); + + public string PlantUml { get; set; } + + public string GetUrl(FileType fileType = FileType.PNG) + => $"http://www.plantuml.com/plantuml/{fileType.ToString().ToLower()}/{PlantUmlTextEncoder.Encode(PlantUml)}"; + } +} diff --git a/Extensions/Stateflows.Extensions.PlantUml/Utilities/BehaviorExtensions.cs b/Extensions/Stateflows.Extensions.PlantUml/Utilities/BehaviorExtensions.cs index 4cbec0a3..0d731cfb 100644 --- a/Extensions/Stateflows.Extensions.PlantUml/Utilities/BehaviorExtensions.cs +++ b/Extensions/Stateflows.Extensions.PlantUml/Utilities/BehaviorExtensions.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Stateflows.Common; using Stateflows.Common.Interfaces; using Stateflows.Extensions.PlantUml.Events; @@ -9,5 +10,11 @@ public static class BehaviorExtensions { public static Task> GetPlantUmlAsync(this IBehavior behavior) => behavior.RequestAsync(new PlantUmlRequest()); + + public static Task WatchPlantUmlAsync(this IBehavior behavior, Action handler) + => behavior.WatchAsync(handler); + + public static Task UnwatchPlantUmlAsync(this IBehavior behavior) + => behavior.UnwatchAsync(); } } diff --git a/Stateflows.sln b/Stateflows.sln index bf34f416..75d74ba5 100644 --- a/Stateflows.sln +++ b/Stateflows.sln @@ -85,6 +85,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stateflows.Transport.Http", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stateflows.Transport.Http.Client", "Transport\Stateflows.Transport.Http.Client\Stateflows.Transport.Http.Client.csproj", "{37C61B16-A27E-487B-AEBC-126E7FDD8443}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Activity.IntegrationTests", "Tests\Activity.IntegrationTests\Activity.IntegrationTests.csproj", "{D6FD721E-4B42-4875-B774-2AD84BE5B3B6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -203,6 +205,10 @@ Global {37C61B16-A27E-487B-AEBC-126E7FDD8443}.Debug|Any CPU.Build.0 = Debug|Any CPU {37C61B16-A27E-487B-AEBC-126E7FDD8443}.Release|Any CPU.ActiveCfg = Release|Any CPU {37C61B16-A27E-487B-AEBC-126E7FDD8443}.Release|Any CPU.Build.0 = Release|Any CPU + {D6FD721E-4B42-4875-B774-2AD84BE5B3B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6FD721E-4B42-4875-B774-2AD84BE5B3B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6FD721E-4B42-4875-B774-2AD84BE5B3B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6FD721E-4B42-4875-B774-2AD84BE5B3B6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -242,6 +248,7 @@ Global {8AD6B55A-4307-45A0-8C70-D64B7CE46313} = {580A6B03-79AD-430C-AEF6-80EC6A02FA61} {8C6BEB59-8CCB-4BB9-950D-08D95FDE2BC8} = {D789B3ED-A0A0-40B3-A746-337C7EF71B9D} {37C61B16-A27E-487B-AEBC-126E7FDD8443} = {D789B3ED-A0A0-40B3-A746-337C7EF71B9D} + {D6FD721E-4B42-4875-B774-2AD84BE5B3B6} = {76D44611-8D90-4E93-BA44-AAB939A742AE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {602A9FD6-5053-42E4-AEAE-2813DF169A3C} diff --git a/Tests/Activity.IntegrationTests/Activity.IntegrationTests.csproj b/Tests/Activity.IntegrationTests/Activity.IntegrationTests.csproj new file mode 100644 index 00000000..274d0ec6 --- /dev/null +++ b/Tests/Activity.IntegrationTests/Activity.IntegrationTests.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + diff --git a/Tests/Activity.IntegrationTests/Tests/Exceptions.cs b/Tests/Activity.IntegrationTests/Tests/Exceptions.cs new file mode 100644 index 00000000..007b52ac --- /dev/null +++ b/Tests/Activity.IntegrationTests/Tests/Exceptions.cs @@ -0,0 +1,78 @@ +using Stateflows.Activities.Typed.Data; +using Stateflows.Activities.Data; +using Stateflows.Common; +using StateMachine.IntegrationTests.Utils; + +namespace Activity.IntegrationTests.Tests +{ + [TestClass] + public class Exceptions : StateflowsTestClass + { + private bool Executed1 = false; + private bool Executed2 = false; + public static string Value = "boo"; + + [TestInitialize] + public override void Initialize() + => base.Initialize(); + + [TestCleanup] + public override void Cleanup() + => base.Cleanup(); + + protected override void InitializeStateflows(IStateflowsBuilder builder) + { + builder + .AddActivities(b => b + .AddActivity("handled", b => b + .AddInitial(b => b + .AddControlFlow("main") + ) + .AddStructuredActivity("main", b => b + .AddExceptionHandler(async c => + { + c.Output(new Token() { Payload = c.Exception.Message }); + }) + .AddInitial(b => b + .AddControlFlow("action1") + ) + .AddAction("action1", + async c => + { + c.Output(new Token() { Payload = 42 }); + throw new Exception("test"); + }, + b => b.AddDataFlow("action2") + ) + .AddAction("action2", + async c => + { + Executed2 = true; + } + ) + .AddDataFlow("final") + ) + .AddAction("final", async c => + { + Executed1 = true; + Value = c.Input.OfType>().FirstOrDefault()?.Payload ?? "foo"; + }) + ) + ) + ; + } + + [TestMethod] + public async Task ExceptionHandled() + { + if (ActivityLocator.TryLocateActivity(new ActivityId("handled", "x"), out var a)) + { + await a.InitializeAsync(); + } + + Assert.IsTrue(Executed1); + Assert.IsFalse(Executed2); + Assert.AreEqual("test", Value); + } + } +} \ No newline at end of file diff --git a/Tests/Activity.IntegrationTests/Tests/Initialization.cs b/Tests/Activity.IntegrationTests/Tests/Initialization.cs new file mode 100644 index 00000000..ec1d1a1e --- /dev/null +++ b/Tests/Activity.IntegrationTests/Tests/Initialization.cs @@ -0,0 +1,142 @@ +using Stateflows.Common; +using StateMachine.IntegrationTests.Utils; + +namespace Activity.IntegrationTests.Tests +{ + public class ValueInitializationRequest : InitializationRequest + { + public string Value { get; set; } = String.Empty; + } + + [TestClass] + public class Initialization : StateflowsTestClass + { + private bool Initialized = false; + private bool Executed = false; + private bool Accepted = false; + public static string Value = "boo"; + + [TestInitialize] + public override void Initialize() + => base.Initialize(); + + [TestCleanup] + public override void Cleanup() + => base.Cleanup(); + + protected override void InitializeStateflows(IStateflowsBuilder builder) + { + builder + .AddActivities(b => b + .AddActivity("simple", b => b + .AddOnInitialize(async c => + { + Initialized = true; + + return true; + }) + .AddInitial(b => b + .AddControlFlow("action1") + ) + .AddAction("action1", async c => Executed = true) + ) + + .AddActivity("auto", b => b + .AddOnInitialize(async c => + { + Initialized = true; + + return true; + }) + .AddInitial(b => b + .AddControlFlow("action1") + ) + .AddAction("action1", async c => Executed = true) + .AddAcceptEventAction(async c => Accepted = true) + ) + + .AddActivity("value", b => b + .AddOnInitialize(async c => + { + c.Activity.Values.Set("foo", c.InitializationRequest.Value); + + return true; + }) + .AddInitial(b => b + .AddControlFlow("action1") + ) + .AddAction("action1", async c => + { + if (c.Activity.Values.TryGet("foo", out var v)) + { + Value = v; + } + }) + ) + ) + .AddAutoInitialization(new ActivityClass("auto")) + ; + } + + [TestMethod] + public async Task SimpleInitialization() + { + var initialized = false; + + if (ActivityLocator.TryLocateActivity(new ActivityId("simple", "x"), out var a)) + { + initialized = (await a.InitializeAsync()).Response.InitializationSuccessful; + } + + Assert.IsTrue(initialized); + Assert.IsTrue(Initialized); + Assert.IsTrue(Executed); + } + + [TestMethod] + public async Task AutoInitialization() + { + EventStatus status = EventStatus.NotConsumed; + + if (ActivityLocator.TryLocateActivity(new ActivityId("auto", "x"), out var a)) + { + status = (await a.SendAsync(new SomeEvent())).Status; + } + + Assert.AreEqual(EventStatus.Consumed, status); + Assert.IsTrue(Initialized); + Assert.IsTrue(Executed); + Assert.IsTrue(Accepted); + } + + [TestMethod] + public async Task NoAutoInitialization() + { + EventStatus status = EventStatus.NotConsumed; + + if (ActivityLocator.TryLocateActivity(new ActivityId("simple", "x"), out var a)) + { + status = (await a.SendAsync(new SomeEvent())).Status; + } + + Assert.AreEqual(EventStatus.Rejected, status); + Assert.IsFalse(Initialized); + Assert.IsFalse(Executed); + Assert.IsFalse(Accepted); + } + + [TestMethod] + public async Task ValueInitialization() + { + var initialized = false; + + if (ActivityLocator.TryLocateActivity(new ActivityId("value", "x"), out var a)) + { + initialized = (await a.InitializeAsync(new ValueInitializationRequest() { Value = "bar" })).Response.InitializationSuccessful; + } + + Assert.IsTrue(initialized); + Assert.AreEqual("bar", Value); + } + } +} \ No newline at end of file diff --git a/Tests/Activity.IntegrationTests/Tests/Output.cs b/Tests/Activity.IntegrationTests/Tests/Output.cs new file mode 100644 index 00000000..f7d214f3 --- /dev/null +++ b/Tests/Activity.IntegrationTests/Tests/Output.cs @@ -0,0 +1,65 @@ +using Stateflows.Activities.Typed.Data; +using Stateflows.Activities.Data; +using Stateflows.Common; +using StateMachine.IntegrationTests.Utils; + +namespace Activity.IntegrationTests.Tests +{ + [TestClass] + public class Output : StateflowsTestClass + { + private bool Executed1 = false; + private bool Executed2 = false; + public static string Value = "boo"; + + [TestInitialize] + public override void Initialize() + => base.Initialize(); + + [TestCleanup] + public override void Cleanup() + => base.Cleanup(); + + protected override void InitializeStateflows(IStateflowsBuilder builder) + { + builder + .AddActivities(b => b + .AddActivity("structured", b => b + .AddInitial(b => b + .AddControlFlow("main") + ) + .AddStructuredActivity("main", b => b + .AddInitial(b => b + .AddControlFlow("action1") + ) + .AddAction("action1", + async c => + { + c.Output(new Token() { Payload = 42 }); + }, + b => b.AddDataFlow() + ) + .AddOutput() + .AddDataFlow("final") + ) + .AddAction("final", async c => + { + Executed1 = c.Input.OfType>().Any(); + }) + ) + ) + ; + } + + [TestMethod] + public async Task ExceptionHandled() + { + if (ActivityLocator.TryLocateActivity(new ActivityId("structured", "x"), out var a)) + { + await a.InitializeAsync(); + } + + Assert.IsTrue(Executed1); + } + } +} \ No newline at end of file diff --git a/Tests/Activity.IntegrationTests/Usings.cs b/Tests/Activity.IntegrationTests/Usings.cs new file mode 100644 index 00000000..79fff2db --- /dev/null +++ b/Tests/Activity.IntegrationTests/Usings.cs @@ -0,0 +1,5 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Stateflows; +global using Stateflows.Activities; +global using Stateflows.Common.Registration.Interfaces; +global using Examples.Common; diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Classes/StateMachines/TypedValue.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Classes/StateMachines/TypedValue.cs new file mode 100644 index 00000000..b90e85bf --- /dev/null +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Classes/StateMachines/TypedValue.cs @@ -0,0 +1,26 @@ +using StateMachine.IntegrationTests.Tests; + +namespace StateMachine.IntegrationTests.Classes.StateMachines +{ + public class TypedValue : StateMachine + { + public override async Task OnInitializeAsync(ValueInitializationRequest initializationRequest) + { + Context.StateMachine.Values.Set("foo", initializationRequest.Value); + + return true; + } + + public override void Build(ITypedStateMachineBuilder builder) + => builder + .AddInitialState("state1", b => b + .AddOnEntry(c => + { + if (c.StateMachine.Values.TryGet("foo", out var v)) + { + Initialization.Value = v; + } + }) + ); + } +} diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Behaviors.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Behaviors.cs new file mode 100644 index 00000000..178bf7fc --- /dev/null +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Behaviors.cs @@ -0,0 +1,134 @@ +using Stateflows.Common; +using Stateflows.Activities; +using Stateflows.StateMachines.Sync; +using StateMachine.IntegrationTests.Utils; + +namespace StateMachine.IntegrationTests.Tests +{ + [TestClass] + public class Behaviors : StateflowsTestClass + { + public bool eventConsumed = false; + + [TestInitialize] + public override void Initialize() + => base.Initialize(); + + [TestCleanup] + public override void Cleanup() + => base.Cleanup(); + + protected override void InitializeStateflows(IStateflowsBuilder builder) + { + builder + .AddPlantUml() + .AddStateMachines(b => b + .AddStateMachine("submachine", b => b + .AddExecutionSequenceObserver() + .AddInitialState("state1", b => b + .AddSubmachine("nested", b => b + .AddForwardedEvent() + .AddSubscription() + ) + .AddTransition("state2") + ) + .AddState("state2") + ) + + .AddStateMachine("nested", b => b + .AddExecutionSequenceObserver() + .AddInitialState("stateA", b => b + .AddTransition("stateB") + ) + .AddState("stateB", b => b + .AddOnEntry(c => c.StateMachine.Publish(new SomeNotification())) + ) + ) + + .AddStateMachine("doActivity", b => b + .AddExecutionSequenceObserver() + .AddInitialState("state1", b => b + .AddDoActivity("nested", b => b + .AddForwardedEvent() + .AddSubscription() + ) + .AddTransition("state2") + ) + .AddState("state2") + ) + ) + .AddActivities(b => b + .AddActivity("nested", b => b + .AddAcceptEventAction(async c => + { + eventConsumed = true; + c.Activity.Publish(new SomeNotification()); + }) + ) + ) + ; + } + + [TestMethod] + public async Task SimpleSubmachine() + { + var initialized = false; + string currentState1 = ""; + var someStatus1 = EventStatus.Rejected; + + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("submachine", "x"), out var sm)) + { + initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; + + someStatus1 = (await sm.SendAsync(new SomeEvent())).Status; + + await Task.Delay(100); + + var currentState = (await sm.GetCurrentStateAsync()).Response; + + currentState1 = currentState.StatesStack.First(); + } + + ExecutionSequence.Verify(b => b + .StateEntry("state1") + .StateEntry("stateA") + .StateExit("stateA") + .StateEntry("stateB") + .StateEntry("state2") + .StateExit("stateB") + .StateMachineFinalize() + ); + Assert.IsTrue(initialized); + Assert.AreEqual(EventStatus.Consumed, someStatus1); + Assert.AreEqual("state2", currentState1); + } + + [TestMethod] + public async Task SimpleDoActivity() + { + var initialized = false; + string currentState1 = ""; + var someStatus1 = EventStatus.Rejected; + + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("doActivity", "x"), out var sm)) + { + initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; + + someStatus1 = (await sm.SendAsync(new SomeEvent())).Status; + + await Task.Delay(200); + + currentState1 = (await sm.GetCurrentStateAsync()).Response.StatesStack.First(); + } + + ExecutionSequence.Verify(b => b + .StateEntry("state1") + .StateEntry("state2") + ); + Assert.IsTrue(initialized); + Assert.AreEqual(EventStatus.Consumed, someStatus1); + Assert.AreEqual(true, eventConsumed); + Assert.AreEqual("state2", currentState1); + } + } +} \ No newline at end of file diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Composite.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Composite.cs index ace6b6a5..bd49652c 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Composite.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Composite.cs @@ -96,7 +96,7 @@ public async Task SelfTransition() string currentState = ""; string? currentInnerState = ""; - if (Locator.TryLocateStateMachine(new StateMachineId("composite", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("composite", "x"), out var sm)) { await sm.InitializeAsync(); @@ -126,9 +126,9 @@ public async Task DefaultTransition() var status = EventStatus.Rejected; CurrentStateResponse? currentState = null; - if (Locator.TryLocateStateMachine(new StateMachineId("default", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("default", "x"), out var sm)) { - await sm.InitializeAsync(); + var initRes = await sm.InitializeAsync(); status = (await sm.SendAsync(new OtherEvent() { AnswerToLifeUniverseAndEverything = 42 })).Status; @@ -167,7 +167,7 @@ public async Task LocalExits() var status = EventStatus.Rejected; CurrentStateResponse? currentState = null; - if (Locator.TryLocateStateMachine(new StateMachineId("exits", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("exits", "x"), out var sm)) { await sm.InitializeAsync(); @@ -200,7 +200,7 @@ public async Task SingleInitialization() var status = EventStatus.Rejected; CurrentStateResponse? currentState = null; - if (Locator.TryLocateStateMachine(new StateMachineId("single", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("single", "x"), out var sm)) { await sm.InitializeAsync(); diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Compound.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Compound.cs index 91c00ae3..68bfdb88 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Compound.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Compound.cs @@ -1,7 +1,6 @@ using Stateflows.Common; using Stateflows.Common.Extensions; using StateMachine.IntegrationTests.Utils; -using System.Runtime.InteropServices; namespace StateMachine.IntegrationTests.Tests { @@ -39,7 +38,7 @@ public async Task CompoundRequestOK() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) { await sm.InitializeAsync(); @@ -67,7 +66,7 @@ public async Task CompoundRequestNOK() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) { await sm.InitializeAsync(); @@ -93,10 +92,10 @@ public async Task CompoundRequestNOK() public async Task CompoundRequestInvalid() { var status = EventStatus.Rejected; - SendResult result = null; + SendResult? result = null; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) { await sm.InitializeAsync(); diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Deferral.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Deferral.cs index b57070f0..0e54cc63 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Deferral.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Deferral.cs @@ -82,7 +82,7 @@ public async Task SimpleDeferral() var otherStatus = EventStatus.Rejected; var someStatus = EventStatus.Rejected; - if (Locator.TryLocateStateMachine(new StateMachineId("defer", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("defer", "x"), out var sm)) { initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; @@ -108,7 +108,7 @@ public async Task SequenceDeferral() var otherStatus2 = EventStatus.Rejected; var someStatus = EventStatus.Rejected; - if (Locator.TryLocateStateMachine(new StateMachineId("sequence", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("sequence", "x"), out var sm)) { initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; @@ -137,7 +137,7 @@ public async Task NestedDeferral() var otherStatus2 = EventStatus.Rejected; var someStatus = EventStatus.Rejected; - if (Locator.TryLocateStateMachine(new StateMachineId("nested", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("nested", "x"), out var sm)) { initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/ElseTransitions.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/ElseTransitions.cs index 880efc54..135da25f 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/ElseTransitions.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/ElseTransitions.cs @@ -55,7 +55,7 @@ public async Task ElseFromOneTransition() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("single", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("single", "x"), out var sm)) { await sm.InitializeAsync(); @@ -74,7 +74,7 @@ public async Task ElseFromTwoTransitions() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("multiple", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("multiple", "x"), out var sm)) { await sm.InitializeAsync(); diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/ExternalTransitions.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/ExternalTransitions.cs index 24abbd3b..ce6a6cc0 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/ExternalTransitions.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/ExternalTransitions.cs @@ -93,7 +93,7 @@ public async Task NestedToRootTransition() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("nested-to-root", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("nested-to-root", "x"), out var sm)) { status = (await sm.InitializeAsync()).Status; @@ -126,7 +126,7 @@ public async Task RootToNestedTransition() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("root-to-nested", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("root-to-nested", "x"), out var sm)) { status = (await sm.InitializeAsync()).Status; @@ -154,7 +154,7 @@ public async Task NestedToNestedTransition() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("nested-to-nested", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("nested-to-nested", "x"), out var sm)) { status = (await sm.InitializeAsync()).Status; @@ -189,7 +189,7 @@ public async Task NestedToParentTransition() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("nested-to-parent", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("nested-to-parent", "x"), out var sm)) { await sm.InitializeAsync(); diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Finalization.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Finalization.cs index 06308cc8..6e9dfe6a 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Finalization.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Finalization.cs @@ -47,7 +47,7 @@ public async Task NoFinalization() var status = EventStatus.Consumed; string? currentState = ""; - if (Locator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) { status = (await sm.SendAsync(new SomeEvent())).Status; @@ -65,7 +65,7 @@ public async Task SimpleFinalization() var finalized = false; string currentState = string.Empty; - if (Locator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) { initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; @@ -88,7 +88,7 @@ public async Task CascadeFinalization() var initialized = false; string currentState = string.Empty; - if (Locator.TryLocateStateMachine(new StateMachineId("cascade", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("cascade", "x"), out var sm)) { initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Initialization.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Initialization.cs index 1edbef83..cdbfe7f8 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Initialization.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Initialization.cs @@ -1,9 +1,10 @@ using Stateflows.Common; using Stateflows.Common.Data; -using Stateflows.StateMachines.Sync; using Stateflows.StateMachines.Data; using Stateflows.StateMachines.Sync.Data; using StateMachine.IntegrationTests.Utils; +using Newtonsoft.Json.Linq; +using StateMachine.IntegrationTests.Classes.StateMachines; namespace StateMachine.IntegrationTests.Tests { @@ -16,7 +17,7 @@ public class ValueInitializationRequest : InitializationRequest public class Initialization : StateflowsTestClass { private bool? StateEntered = null; - private string Value = "boo"; + public static string Value = "boo"; [TestInitialize] public override void Initialize() @@ -36,6 +37,12 @@ protected override void InitializeStateflows(IStateflowsBuilder builder) ) ) + .AddStateMachine("auto", b => b + .AddInitialState("state1", b => b + .AddOnEntry(c => StateEntered = true) + ) + ) + .AddStateMachine("value", b => b .AddOnInitialize(c => { @@ -84,7 +91,11 @@ protected override void InitializeStateflows(IStateflowsBuilder builder) .AddState("state1.2") ) ) + + .AddStateMachine() ) + + .AddAutoInitialization(new StateMachineClass("auto")) ; } @@ -94,7 +105,7 @@ public async Task NoInitialization() var status = EventStatus.Rejected; string? currentState = ""; - if (Locator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) { status = (await sm.SendAsync(new SomeEvent())).Status; @@ -112,7 +123,7 @@ public async Task SimpleInitialization() var initialized = false; string currentState = string.Empty; - if (Locator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) { initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; @@ -124,13 +135,41 @@ public async Task SimpleInitialization() Assert.AreEqual("state1", currentState); } + [TestMethod] + public async Task AutoInitialization() + { + string currentState = string.Empty; + + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("auto", "x"), out var sm)) + { + currentState = (await sm.GetCurrentStateAsync()).Response.StatesStack.FirstOrDefault() ?? string.Empty; + } + + Assert.IsTrue(StateEntered); + Assert.AreEqual("state1", currentState); + } + + [TestMethod] + public async Task NoAutoInitialization() + { + string currentState = string.Empty; + + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) + { + currentState = (await sm.GetCurrentStateAsync()).Response.StatesStack.FirstOrDefault() ?? string.Empty; + } + + Assert.IsNull(StateEntered); + Assert.AreNotEqual("state1", currentState); + } + [TestMethod] public async Task DoubleInitialization() { var initialized = false; string currentState = string.Empty; - if (Locator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) { await sm.InitializeAsync(); @@ -150,7 +189,25 @@ public async Task ValueInitialization() var initialized = false; string currentState = string.Empty; - if (Locator.TryLocateStateMachine(new StateMachineId("value", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("value", "x"), out var sm)) + { + initialized = (await sm.InitializeAsync(new ValueInitializationRequest() { Value = "bar" })).Response.InitializationSuccessful; + + currentState = (await sm.GetCurrentStateAsync()).Response.StatesStack.First() ?? string.Empty; + } + + Assert.IsTrue(initialized); + Assert.AreEqual("bar", Value); + Assert.AreEqual("state1", currentState); + } + + [TestMethod] + public async Task TypedValueInitialization() + { + var initialized = false; + string currentState = string.Empty; + + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId(StateMachineInfo.Name, "x"), out var sm)) { initialized = (await sm.InitializeAsync(new ValueInitializationRequest() { Value = "bar" })).Response.InitializationSuccessful; @@ -168,7 +225,7 @@ public async Task PayloadInitialization() var initialized = false; string currentState = string.Empty; - if (Locator.TryLocateStateMachine(new StateMachineId("payload", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("payload", "x"), out var sm)) { initialized = (await sm.InitializeAsync("bar")).Response.InitializationSuccessful; @@ -184,7 +241,7 @@ public async Task InvalidInitialization() { var initialized = false; - if (Locator.TryLocateStateMachine(new StateMachineId("invalid", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("invalid", "x"), out var sm)) { initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; } @@ -197,7 +254,7 @@ public async Task FailedInitialization() { var initialized = false; - if (Locator.TryLocateStateMachine(new StateMachineId("failed", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("failed", "x"), out var sm)) { initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; } @@ -211,7 +268,7 @@ public async Task InitializationWithCompletion() var initialized = false; string currentState = string.Empty; - if (Locator.TryLocateStateMachine(new StateMachineId("completion", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("completion", "x"), out var sm)) { initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; @@ -228,7 +285,7 @@ public async Task InitializationWithNestedCompletion() var initialized = false; string? currentState = string.Empty; - if (Locator.TryLocateStateMachine(new StateMachineId("nested-completion", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("nested-completion", "x"), out var sm)) { initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Reset.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Reset.cs index 0050cbd6..385083ce 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Reset.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Reset.cs @@ -38,7 +38,7 @@ public async Task ResettingInitialized() CurrentStateResponse? currentState2 = null; ResetResponse? resetResponse = null; - if (Locator.TryLocateStateMachine(new StateMachineId("reset", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("reset", "x"), out var sm)) { await sm.InitializeAsync(); @@ -64,7 +64,7 @@ public async Task ResettingNotInitialized() CurrentStateResponse? currentState2 = null; ResetResponse? resetResponse = null; - if (Locator.TryLocateStateMachine(new StateMachineId("reset", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("reset", "x"), out var sm)) { currentState1 = (await sm.GetCurrentStateAsync()).Response; diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Scale.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Scale.cs index 6e81010f..82878179 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Scale.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Scale.cs @@ -46,7 +46,7 @@ public async Task ProcessingInScale() { var sequence = Enumerable .Range(0, 10000) - .Select(i => Locator.TryLocateStateMachine(new StateMachineId($"scale{Random.Shared.Next(1, 5)}", i.ToString()), out var stateMachine) + .Select(i => StateMachineLocator.TryLocateStateMachine(new StateMachineId($"scale{Random.Shared.Next(1, 5)}", i.ToString()), out var stateMachine) ? stateMachine : null ) diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Submachine.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Submachine.cs deleted file mode 100644 index 5b64d808..00000000 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Submachine.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Stateflows.Common; -using StateMachine.IntegrationTests.Utils; - -namespace StateMachine.IntegrationTests.Tests -{ - [TestClass] - public class Submachine : StateflowsTestClass - { - [TestInitialize] - public override void Initialize() - => base.Initialize(); - - [TestCleanup] - public override void Cleanup() - => base.Cleanup(); - - protected override void InitializeStateflows(IStateflowsBuilder builder) - { - builder - .AddPlantUml() - .AddStateMachines(b => b - .AddStateMachine("submachine", b => b - .AddExecutionSequenceObserver() - .AddInitialState("state1", b => b - .AddSubmachine("nested") - .AddTransition("state2") - ) - .AddState("state2") - ) - - .AddStateMachine("nested", b => b - .AddExecutionSequenceObserver() - .AddInitialState("stateA", b => b - .AddTransition("stateB") - ) - .AddState("stateB") - ) - ) - ; - } - - [TestMethod] - public async Task SimpleSubmachine() - { - var initialized = false; - string currentState1 = ""; - string currentSubState1 = ""; - string currentState2 = ""; - var someStatus1 = EventStatus.Rejected; - var someStatus2 = EventStatus.Rejected; - - if (Locator.TryLocateStateMachine(new StateMachineId("submachine", "x"), out var sm)) - { - initialized = (await sm.InitializeAsync()).Response.InitializationSuccessful; - - someStatus1 = (await sm.SendAsync(new SomeEvent())).Status; - - var currentState = (await sm.GetCurrentStateAsync()).Response; - - currentState1 = currentState.StatesStack.First(); - - currentSubState1 = currentState.StatesStack.Skip(1).First(); - - someStatus2 = (await sm.SendAsync(new SomeEvent())).Status; - - currentState2 = (await sm.GetCurrentStateAsync()).Response.StatesStack.First(); - } - - ExecutionSequence.Verify(b => b - .StateEntry("state1") - .StateEntry("stateA") - .StateExit("stateA") - .StateEntry("stateB") - .StateExit("stateB") - .StateMachineFinalize() - .StateEntry("state2") - ); - Assert.IsTrue(initialized); - Assert.AreEqual(EventStatus.Consumed, someStatus1); - Assert.AreEqual("state1", currentState1); - Assert.AreEqual("stateB", currentSubState1); - Assert.AreEqual(EventStatus.Consumed, someStatus2); - Assert.AreEqual("state2", currentState2); - } - } -} \ No newline at end of file diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Subscription.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Subscription.cs new file mode 100644 index 00000000..800fd447 --- /dev/null +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Subscription.cs @@ -0,0 +1,124 @@ +using StateMachine.IntegrationTests.Utils; +using Stateflows.Common; +using Stateflows.StateMachines.Sync; + +namespace StateMachine.IntegrationTests.Tests +{ + [TestClass] + public class Subscription : StateflowsTestClass + { + [TestInitialize] + public override void Initialize() + => base.Initialize(); + + [TestCleanup] + public override void Cleanup() + => base.Cleanup(); + + protected override void InitializeStateflows(IStateflowsBuilder builder) + { + builder + .AddStateMachines(b => b + .AddStateMachine("subscriber", b => b + .AddExecutionSequenceObserver() + .AddInitialState("state1", b => b + //.AddDeferredEvent() + .AddOnEntry(c => c.StateMachine.SubscribeAsync(new StateMachineId("subscribee", c.StateMachine.Id.Instance))) + .AddTransition("state2") + ) + .AddState("state2", b => b + .AddInternalTransition(b => b + .AddEffect(c => c.StateMachine.UnsubscribeAsync(new StateMachineId("subscribee", c.StateMachine.Id.Instance))) + ) + .AddTransition("state3") + ) + .AddState("state3") + ) + + .AddStateMachine("subscribee", b => b + .AddInitialState("state1", b => b + .AddInternalTransition(b => b + .AddEffect(c => c.StateMachine.Publish(new SomeNotification())) + ) + ) + ) + ) + ; + } + + [TestMethod] + public async Task SubscribingSuccessful() + { + string? currentState = ""; + + if ( + StateMachineLocator.TryLocateStateMachine(new StateMachineId("subscriber", "x"), out var subscriber) && + StateMachineLocator.TryLocateStateMachine(new StateMachineId("subscribee", "x"), out var subscribee) + ) + { + await subscriber.InitializeAsync(); + + await subscribee.InitializeAsync(); + + await subscribee.SendAsync(new OtherEvent()); + + await subscriber.SendAsync(new OtherEvent()); + + await subscribee.SendAsync(new OtherEvent()); + + currentState = (await subscriber.GetCurrentStateAsync()).Response?.StatesStack.FirstOrDefault(); + } + + Assert.AreEqual("state2", currentState); + } + + [TestMethod] + public async Task WatchSuccessful() + { + var watchHit = false; + + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("subscribee", "y"), out var subscribee)) + { + _ = subscribee.WatchAsync(n => + { + lock (StateMachineLocator) + { + watchHit = true; + } + }); + + await subscribee.InitializeAsync(); + + await subscribee.SendAsync(new OtherEvent()); + + await subscribee.GetCurrentStateAsync(); + } + + lock (StateMachineLocator) + { + Assert.AreEqual(true, watchHit); + } + } + + [TestMethod] + public async Task WatchStandardNotifications() + { + var currentStatus = BehaviorStatus.Unknown; + var currentState = ""; + + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("subscribee", "z"), out var subscribee)) + { + _ = subscribee.WatchCurrentStateAsync(n => currentState = n.StatesStack.First()); + + _ = subscribee.WatchStatusAsync(n => currentStatus = n.BehaviorStatus); + + await subscribee.InitializeAsync(); + + await subscribee.GetCurrentStateAsync(); + } + + Assert.AreEqual("state1", currentState); + Assert.AreEqual(BehaviorStatus.Initialized, currentStatus); + } + } +} \ No newline at end of file diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Transitions.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Transitions.cs index ba141b5f..5ac9c7d5 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Transitions.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Transitions.cs @@ -86,7 +86,7 @@ public async Task SimpleTransition() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("simple", "x"), out var sm)) { await sm.InitializeAsync(); @@ -108,7 +108,7 @@ public async Task GuardedTransition() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("guarded", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("guarded", "x"), out var sm)) { await sm.InitializeAsync(); @@ -129,7 +129,7 @@ public async Task DefaultTransition() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("default", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("default", "x"), out var sm)) { await sm.InitializeAsync(); @@ -148,7 +148,7 @@ public async Task InternalTransition() var status = EventStatus.Rejected; string currentState = string.Empty; - if (Locator.TryLocateStateMachine(new StateMachineId("internal", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("internal", "x"), out var sm)) { await sm.InitializeAsync(); @@ -174,7 +174,7 @@ public async Task SelfTransition() var status = EventStatus.Rejected; string currentState = "state1"; - if (Locator.TryLocateStateMachine(new StateMachineId("self", "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId("self", "x"), out var sm)) { await sm.InitializeAsync(); diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Typed.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Typed.cs index 4364374b..cf1ca606 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Typed.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Tests/Typed.cs @@ -40,7 +40,7 @@ public async Task TypedStateMachine() State2.Reset(); SomeEventTransition.Reset(); - if (Locator.TryLocateStateMachine(new StateMachineId(StateMachineInfo.Name, "x"), out var sm)) + if (StateMachineLocator.TryLocateStateMachine(new StateMachineId(StateMachineInfo.Name, "x"), out var sm)) { await sm.InitializeAsync(); diff --git a/Tests/StateMachine/StateMachine.IntegrationTests/Usings.cs b/Tests/StateMachine/StateMachine.IntegrationTests/Usings.cs index ce8c8d41..2e670543 100644 --- a/Tests/StateMachine/StateMachine.IntegrationTests/Usings.cs +++ b/Tests/StateMachine/StateMachine.IntegrationTests/Usings.cs @@ -1,7 +1,7 @@ -global using Microsoft.Extensions.DependencyInjection; global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Stateflows; global using Stateflows.StateMachines; +global using Stateflows.StateMachines.Sync; global using Stateflows.Common.Registration.Interfaces; global using Stateflows.Testing.StateMachines; global using Examples.Common; diff --git a/Transport/@stateflows/common/package.json b/Transport/@stateflows/common/package.json index 6067b990..d4318f74 100644 --- a/Transport/@stateflows/common/package.json +++ b/Transport/@stateflows/common/package.json @@ -1,6 +1,6 @@ { "name": "@stateflows/common", - "version": "0.9.9", + "version": "0.11.2", "description": "Common package for Stateflows framework clients", "main": "dist/index.js", "module": "./dist/index.mjs", diff --git a/Transport/@stateflows/common/src/behaviors/activity.ts b/Transport/@stateflows/common/src/behaviors/activity.ts index 913fddd0..9f0f50e0 100644 --- a/Transport/@stateflows/common/src/behaviors/activity.ts +++ b/Transport/@stateflows/common/src/behaviors/activity.ts @@ -3,6 +3,6 @@ import { Behavior } from "./behavior"; export class Activity extends Behavior implements IActivity { constructor(behavior: Behavior) { - super(behavior, behavior.behaviorId); + super(behavior, behavior.id); } } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/behaviors/behavior.ts b/Transport/@stateflows/common/src/behaviors/behavior.ts index c50511e3..35df8ed8 100644 --- a/Transport/@stateflows/common/src/behaviors/behavior.ts +++ b/Transport/@stateflows/common/src/behaviors/behavior.ts @@ -6,35 +6,46 @@ import { InitializationResponse } from "../events/initialization.response"; import { BehaviorStatusResponse } from "../events/behavior-status.response"; import { BehaviorId } from "../ids/behavior.id"; import { Response } from "../events/response"; +import { CompoundRequest } from "../events/compound.request"; +import { CompoundResponse } from "../events/compound.response"; import { InitializationRequest } from "../events/initialization.request"; import { Event } from "../events/event"; import { BehaviorStatusRequest } from "../events/behavior-status.request"; import { IStateflowsClientTransport } from "../interfaces/stateflows-client-transport"; +import { FinalizationResponse } from "../events/finalization.response"; +import { ResetResponse } from "../events/reset.response"; +import { FinalizationRequest } from "../events/finalization.request"; +import { ResetRequest } from "../events/reset.request"; +import { BehaviorStatusNotification } from "../events/behavior-status.notification"; +import { Notification } from "../events/notification"; +import { IWatcher } from "../interfaces/watcher"; +import { NotificationHandler } from "../utils/notification-handler"; -export class Behavior implements IBehavior { - #transportPromise: Promise +export class Behavior implements IBehavior, IWatcher { + #transportPromise: Promise; + #notificationHandlers: Map>> = new Map>>(); - constructor(transportPromiseOrBehavior: Promise | Behavior, public behaviorId: BehaviorId) { + constructor(transportPromiseOrBehavior: Promise | Behavior, public id: BehaviorId) { this.#transportPromise = transportPromiseOrBehavior instanceof Behavior ? transportPromiseOrBehavior.#transportPromise : this.#transportPromise = transportPromiseOrBehavior; } - send(event: Event): Promise { - return new Promise(async (resolve, reject) => { - let hub = await this.#transportPromise; - let result = await hub.send(this.behaviorId, event); - resolve(result); - }); + async send(event: Event): Promise { + let transport = await this.#transportPromise; + let result = await transport.send(this.id, event); + return result; } - request(request: Request): Promise> { - return new Promise>(async (resolve, reject) => { - let sendResult = await this.send(request); - request.Response = (sendResult.Event as any).Response; - let result = new RequestResult(request.Response, request, sendResult.Status, sendResult.Validation); - resolve(result); - }); + sendCompound(...events: Event[]): Promise> { + return this.request(new CompoundRequest(events)) + } + + async request(request: Request): Promise> { + let sendResult = await this.send(request); + request.response = (sendResult.event as any).response; + let result = new RequestResult(request.response, request, sendResult.status, sendResult.validation); + return result; } initialize(initializationRequest?: InitializationRequest): Promise> { @@ -45,7 +56,71 @@ export class Behavior implements IBehavior { return this.request(initializationRequest); } + finalize(): Promise> { + return this.request(new FinalizationRequest()); + } + + reset(keepVersion?: boolean): Promise> { + return this.request(new ResetRequest(keepVersion ?? false)); + } + + async reinitialize(initializationRequest?: InitializationRequest, keepVersion: boolean = true): Promise> { + if (typeof initializationRequest === "undefined") { + initializationRequest = new InitializationRequest(); + } + + let result = await this.sendCompound( + new ResetRequest(keepVersion ?? true), + initializationRequest + ); + + let initializationResult = result.response.results.slice(-1)[0]; + + return new RequestResult( + initializationResult.response as InitializationResponse, + initializationRequest, + initializationResult.status, + initializationResult.validation + ); + } + + notify(notification: Notification): void { + if (this.#notificationHandlers.has(notification.name)) + { + let handlers = this.#notificationHandlers.get(notification.name) as Array>; + handlers.forEach(handler => handler(notification)); + } + } + + async watch(notificationName: string, handler: NotificationHandler): Promise { + let transport = await this.#transportPromise; + await transport.watch(this, notificationName); + + let handlers: Array> = this.#notificationHandlers.has(notificationName) + ? this.#notificationHandlers.get(notificationName) as Array> + : []; + + handlers.push(n => handler(n as TNotification)); + + this.#notificationHandlers.set(notificationName, handlers); + } + + async unwatch(notificationName: string): Promise { + let transport = await this.#transportPromise; + await transport.unwatch(this, notificationName); + + this.#notificationHandlers.delete(notificationName); + } + getStatus(): Promise> { return this.request(new BehaviorStatusRequest()); } + + watchStatus(handler: NotificationHandler): Promise { + return this.watch(BehaviorStatusNotification.notificationName, handler); + } + + unwatchStatus(): Promise { + return this.unwatch(BehaviorStatusNotification.notificationName); + } } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/behaviors/state-machine.ts b/Transport/@stateflows/common/src/behaviors/state-machine.ts index 0c41c2b4..29e399bb 100644 --- a/Transport/@stateflows/common/src/behaviors/state-machine.ts +++ b/Transport/@stateflows/common/src/behaviors/state-machine.ts @@ -1,13 +1,23 @@ +import { CurrentStateNotification } from "../events/current-state.notification"; import { CurrentStateRequest } from "../events/current-state.request"; import { IStateMachine } from "../interfaces/state-machine"; +import { NotificationHandler } from "../utils/notification-handler"; import { Behavior } from "./behavior"; export class StateMachine extends Behavior implements IStateMachine { constructor(behavior: Behavior) { - super(behavior, behavior.behaviorId); + super(behavior, behavior.id); } getCurrentState(): Promise { return this.request(new CurrentStateRequest()); } + + watchCurrentState(handler: NotificationHandler): Promise { + return this.watch(CurrentStateNotification.notificationName, handler); + } + + unwatchCurrentState(): Promise { + return this.unwatch(CurrentStateNotification.notificationName); + } } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/classes/event-validation.ts b/Transport/@stateflows/common/src/classes/event-validation.ts index df393e54..7ecbe506 100644 --- a/Transport/@stateflows/common/src/classes/event-validation.ts +++ b/Transport/@stateflows/common/src/classes/event-validation.ts @@ -2,7 +2,7 @@ import { ValidationResult } from "./validation-result"; export class EventValidation { constructor( - public IsValid: boolean, - public ValidationResults: Array, + public isValid: boolean, + public validationResults: Array, ) {} } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/classes/request-result.ts b/Transport/@stateflows/common/src/classes/request-result.ts index b76e79c5..3a89bf94 100644 --- a/Transport/@stateflows/common/src/classes/request-result.ts +++ b/Transport/@stateflows/common/src/classes/request-result.ts @@ -5,7 +5,7 @@ import { SendResult } from "./send-result"; export class RequestResult extends SendResult { constructor( - public Response: TResponse, + public response: TResponse, event: Event, status: EventStatus, validation: EventValidation, diff --git a/Transport/@stateflows/common/src/classes/send-result.ts b/Transport/@stateflows/common/src/classes/send-result.ts index 2bfc12f0..abb41125 100644 --- a/Transport/@stateflows/common/src/classes/send-result.ts +++ b/Transport/@stateflows/common/src/classes/send-result.ts @@ -4,8 +4,8 @@ import { EventValidation } from "./event-validation"; export class SendResult { constructor( - public Event: Event, - public Status: EventStatus, - public Validation: EventValidation, + public event: Event, + public status: EventStatus, + public validation: EventValidation, ) {} } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/classes/validation-result.ts b/Transport/@stateflows/common/src/classes/validation-result.ts index 6ba798b6..a8e809eb 100644 --- a/Transport/@stateflows/common/src/classes/validation-result.ts +++ b/Transport/@stateflows/common/src/classes/validation-result.ts @@ -1,6 +1,6 @@ export class ValidationResult { constructor( - public ErrorMessage: string | null, - public MemberNames: Array | null, + public errorMessage: string | null, + public memberNames: Array | null, ) {} } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/available-behavior-classes.request.ts b/Transport/@stateflows/common/src/events/available-behavior-classes.request.ts index 7dcbdb61..72bfec5c 100644 --- a/Transport/@stateflows/common/src/events/available-behavior-classes.request.ts +++ b/Transport/@stateflows/common/src/events/available-behavior-classes.request.ts @@ -3,7 +3,7 @@ import { AvailableBehaviorClassesResponse } from "./available-behavior-classes.r export class AvailableBehaviorClassesRequest extends Request { public $type = "Stateflows.System.AvailableBehaviorClassesRequest, Stateflows.Common"; - public Name = "Stateflows.System.AvailableBehaviorClassesRequest"; + public name = "Stateflows.System.AvailableBehaviorClassesRequest"; constructor() { super(); diff --git a/Transport/@stateflows/common/src/events/available-behavior-classes.response.ts b/Transport/@stateflows/common/src/events/available-behavior-classes.response.ts index 0c3ef750..c664fcf2 100644 --- a/Transport/@stateflows/common/src/events/available-behavior-classes.response.ts +++ b/Transport/@stateflows/common/src/events/available-behavior-classes.response.ts @@ -3,7 +3,7 @@ import { BehaviorClass } from "../ids/behavior.class"; export class AvailableBehaviorClassesResponse extends Response { constructor( - public AvailableBehaviorClasses: Array, + public availableBehaviorClasses: Array, ) { super(); } diff --git a/Transport/@stateflows/common/src/events/behavior-instances.request.ts b/Transport/@stateflows/common/src/events/behavior-instances.request.ts index 195527b5..22194efd 100644 --- a/Transport/@stateflows/common/src/events/behavior-instances.request.ts +++ b/Transport/@stateflows/common/src/events/behavior-instances.request.ts @@ -3,7 +3,7 @@ import { BehaviorInstancesResponse } from "./behavior-instances.response"; export class BehaviorInstancesRequest extends Request { public $type = "Stateflows.System.BehaviorInstancesRequest, Stateflows.Common"; - public Name = "Stateflows.System.BehaviorInstancesRequest"; + public name = "Stateflows.System.BehaviorInstancesRequest"; constructor() { super(); diff --git a/Transport/@stateflows/common/src/events/behavior-instances.response.ts b/Transport/@stateflows/common/src/events/behavior-instances.response.ts index bcf15a8c..decf1470 100644 --- a/Transport/@stateflows/common/src/events/behavior-instances.response.ts +++ b/Transport/@stateflows/common/src/events/behavior-instances.response.ts @@ -4,13 +4,13 @@ import { BehaviorStatus } from "../enums/behavior-status"; export class BehaviorDescriptor { - Id: BehaviorId; - Status: BehaviorStatus; + id: BehaviorId; + status: BehaviorStatus; } export class BehaviorInstancesResponse extends Response { constructor( - public Behaviors: Array, + public behaviors: Array, ) { super(); } diff --git a/Transport/@stateflows/common/src/events/behavior-status.notification.ts b/Transport/@stateflows/common/src/events/behavior-status.notification.ts new file mode 100644 index 00000000..8946285e --- /dev/null +++ b/Transport/@stateflows/common/src/events/behavior-status.notification.ts @@ -0,0 +1,12 @@ +import { Notification } from "./notification"; +import { BehaviorStatus } from "../enums/behavior-status"; + +export class BehaviorStatusNotification extends Notification { + constructor( + public behaviorStatus: BehaviorStatus, + ) { + super(); + } + + public static notificationName = "Stateflows.Common.BehaviorStatusNotification"; +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/behavior-status.response.ts b/Transport/@stateflows/common/src/events/behavior-status.response.ts index 7a904dbb..31df1ceb 100644 --- a/Transport/@stateflows/common/src/events/behavior-status.response.ts +++ b/Transport/@stateflows/common/src/events/behavior-status.response.ts @@ -3,7 +3,8 @@ import { BehaviorStatus } from "../enums/behavior-status"; export class BehaviorStatusResponse extends Response { constructor( - public BehaviorStatus: BehaviorStatus, + public behaviorStatus: BehaviorStatus, + public expectedEvents: Array, ) { super(); } diff --git a/Transport/@stateflows/common/src/events/compound.request.ts b/Transport/@stateflows/common/src/events/compound.request.ts new file mode 100644 index 00000000..04b6bca6 --- /dev/null +++ b/Transport/@stateflows/common/src/events/compound.request.ts @@ -0,0 +1,14 @@ +import { Event } from "./event"; +import { Request } from "./request"; +import { CompoundResponse } from "./compound.response"; + +export class CompoundRequest extends Request { + public $type = "Stateflows.Common.CompoundRequest, Stateflows.Common"; + public name = "Stateflows.Common.CompoundRequest"; + + constructor( + public events: Array, + ) { + super(); + } +} diff --git a/Transport/@stateflows/common/src/events/compound.response.ts b/Transport/@stateflows/common/src/events/compound.response.ts new file mode 100644 index 00000000..ecd4a2a6 --- /dev/null +++ b/Transport/@stateflows/common/src/events/compound.response.ts @@ -0,0 +1,10 @@ +import { Response } from "./response"; +import { RequestResult } from "../classes/request-result"; + +export class CompoundResponse extends Response { + constructor( + public results: Array>, + ) { + super(); + } +} diff --git a/Transport/@stateflows/common/src/events/current-state.notification.ts b/Transport/@stateflows/common/src/events/current-state.notification.ts new file mode 100644 index 00000000..ab13e5a2 --- /dev/null +++ b/Transport/@stateflows/common/src/events/current-state.notification.ts @@ -0,0 +1,12 @@ +import { Notification } from "./notification"; + +export class CurrentStateNotification extends Notification { + constructor( + public statesStack: Array, + public expectedEvents: Array, + ) { + super(); + } + + public static notificationName = "Stateflows.StateMachines.Events.CurrentStateNotification"; +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/current-state.response.ts b/Transport/@stateflows/common/src/events/current-state.response.ts index 7cefe243..8ee12d5f 100644 --- a/Transport/@stateflows/common/src/events/current-state.response.ts +++ b/Transport/@stateflows/common/src/events/current-state.response.ts @@ -1,10 +1,12 @@ -import { Response } from "./response"; +import { BehaviorStatus } from "../enums/behavior-status"; +import { BehaviorStatusResponse } from "./behavior-status.response"; -export class CurrentStateResponse extends Response { +export class CurrentStateResponse extends BehaviorStatusResponse { constructor( - public StatesStack: Array, - public ExpectedEvents: Array, + behaviorStatus: BehaviorStatus, + expectedEvents: Array, + public statesStack: Array, ) { - super(); + super(behaviorStatus, expectedEvents); } } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/event.ts b/Transport/@stateflows/common/src/events/event.ts index 50eb3c3d..690df953 100644 --- a/Transport/@stateflows/common/src/events/event.ts +++ b/Transport/@stateflows/common/src/events/event.ts @@ -1,4 +1,5 @@ export class Event { - public Headers: any[]; - public Name: string; + public headers: any[]; + public name: string; + public id: string; } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/finalization.request.ts b/Transport/@stateflows/common/src/events/finalization.request.ts new file mode 100644 index 00000000..18313b39 --- /dev/null +++ b/Transport/@stateflows/common/src/events/finalization.request.ts @@ -0,0 +1,7 @@ +import { Request } from "./request"; +import { FinalizationResponse } from "./finalization.response"; + +export class FinalizationRequest extends Request { + public $type = "Stateflows.Common.FinalizationRequest, Stateflows.Common"; + public name = "Stateflows.Common.FinalizationRequest"; +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/finalization.response.ts b/Transport/@stateflows/common/src/events/finalization.response.ts new file mode 100644 index 00000000..852ebf82 --- /dev/null +++ b/Transport/@stateflows/common/src/events/finalization.response.ts @@ -0,0 +1,9 @@ +import { Response } from "./response"; + +export class FinalizationResponse extends Response { + constructor( + public finalizationSuccessful: boolean, + ) { + super(); + } +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/initialization.request.ts b/Transport/@stateflows/common/src/events/initialization.request.ts index f9a8df41..e42a4460 100644 --- a/Transport/@stateflows/common/src/events/initialization.request.ts +++ b/Transport/@stateflows/common/src/events/initialization.request.ts @@ -3,5 +3,5 @@ import { InitializationResponse } from "./initialization.response"; export class InitializationRequest extends Request { public $type = "Stateflows.Common.InitializationRequest, Stateflows.Common"; - public Name = "Stateflows.Common.InitializationRequest"; + public name = "Stateflows.Common.InitializationRequest"; } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/initialization.response.ts b/Transport/@stateflows/common/src/events/initialization.response.ts index 8705a1b2..2e817636 100644 --- a/Transport/@stateflows/common/src/events/initialization.response.ts +++ b/Transport/@stateflows/common/src/events/initialization.response.ts @@ -2,7 +2,7 @@ import { Response } from "./response"; export class InitializationResponse extends Response { constructor( - public InitializationSuccessful: boolean, + public initializationSuccessful: boolean, ) { super(); } diff --git a/Transport/@stateflows/common/src/events/notification.ts b/Transport/@stateflows/common/src/events/notification.ts new file mode 100644 index 00000000..c336add6 --- /dev/null +++ b/Transport/@stateflows/common/src/events/notification.ts @@ -0,0 +1,7 @@ +import { BehaviorId } from "../ids/behavior.id"; +import { Event } from "./event"; + +export class Notification extends Event { + public static notificationName: string; + public senderId: BehaviorId; +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/notifications.request.ts b/Transport/@stateflows/common/src/events/notifications.request.ts new file mode 100644 index 00000000..79197200 --- /dev/null +++ b/Transport/@stateflows/common/src/events/notifications.request.ts @@ -0,0 +1,11 @@ +import { Request } from "./request"; +import { NotificationsResponse } from "./notifications.response"; + +export class NotificationsRequest extends Request { + public $type = "Stateflows.Common.NotificationsRequest, Stateflows.Common"; + public name = "Stateflows.Common.NotificationsRequest"; + + constructor() { + super(); + } +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/notifications.response.ts b/Transport/@stateflows/common/src/events/notifications.response.ts new file mode 100644 index 00000000..afc4c56f --- /dev/null +++ b/Transport/@stateflows/common/src/events/notifications.response.ts @@ -0,0 +1,7 @@ +import { Response } from "./response"; + +export class NotificationsResponse extends Response { + constructor() { + super(); + } +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/plant-uml.notification.ts b/Transport/@stateflows/common/src/events/plant-uml.notification.ts new file mode 100644 index 00000000..e2823a4b --- /dev/null +++ b/Transport/@stateflows/common/src/events/plant-uml.notification.ts @@ -0,0 +1,11 @@ +import { Notification } from "./notification"; + +export class PlantUmlNotification extends Notification { + constructor( + public plantUml: string, + ) { + super(); + } + + public static notificationName = "PlantUmlNotification"; +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/plant-uml.response.ts b/Transport/@stateflows/common/src/events/plant-uml.response.ts index 5878c190..39c45b9d 100644 --- a/Transport/@stateflows/common/src/events/plant-uml.response.ts +++ b/Transport/@stateflows/common/src/events/plant-uml.response.ts @@ -2,7 +2,7 @@ import { Response } from "./response"; export class PlantUmlResponse extends Response { constructor( - public PlantUml: string, + public plantUml: string, ) { super(); } diff --git a/Transport/@stateflows/common/src/events/request.ts b/Transport/@stateflows/common/src/events/request.ts index 061bd99d..63b31f1c 100644 --- a/Transport/@stateflows/common/src/events/request.ts +++ b/Transport/@stateflows/common/src/events/request.ts @@ -2,5 +2,5 @@ import { Response } from "./response"; import { Event } from "./event"; export class Request extends Event { - public Response: TResponse; + public response: TResponse; } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/reset.request.ts b/Transport/@stateflows/common/src/events/reset.request.ts new file mode 100644 index 00000000..92068784 --- /dev/null +++ b/Transport/@stateflows/common/src/events/reset.request.ts @@ -0,0 +1,13 @@ +import { Request } from "./request"; +import { ResetResponse } from "./reset.response"; + +export class ResetRequest extends Request { + public $type = "Stateflows.Common.ResetRequest, Stateflows.Common"; + public name = "Stateflows.Common.ResetRequest"; + + constructor( + public keepVersion: boolean = false, + ) { + super(); + } +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/events/reset.response.ts b/Transport/@stateflows/common/src/events/reset.response.ts new file mode 100644 index 00000000..6ab49e0d --- /dev/null +++ b/Transport/@stateflows/common/src/events/reset.response.ts @@ -0,0 +1,9 @@ +import { Response } from "./response"; + +export class ResetResponse extends Response { + constructor( + public resetSuccessful: boolean, + ) { + super(); + } +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/ids/behavior.id.ts b/Transport/@stateflows/common/src/ids/behavior.id.ts index 67c91b8b..eadcf011 100644 --- a/Transport/@stateflows/common/src/ids/behavior.id.ts +++ b/Transport/@stateflows/common/src/ids/behavior.id.ts @@ -1,3 +1,4 @@ +import { JsonUtils } from "../utils/json.utils"; import { BehaviorClass } from "./behavior.class"; export class BehaviorId { @@ -7,4 +8,8 @@ export class BehaviorId { ) {} public $type: string = "Stateflows.BehaviorId, Stateflows.Common"; + + public toString(): string { + return JsonUtils.stringify(this); + } } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/index.ts b/Transport/@stateflows/common/src/index.ts index 7df4a305..f3538d10 100644 --- a/Transport/@stateflows/common/src/index.ts +++ b/Transport/@stateflows/common/src/index.ts @@ -7,16 +7,27 @@ export { StateMachineId } from "./ids/state-machine.id"; export { ActivityId } from "./ids/activity.id"; export { Event } from "./events/event"; export { Request } from "./events/request"; +export { Notification } from "./events/notification"; +export { CompoundRequest } from "./events/compound.request"; +export { CompoundResponse } from "./events/compound.response"; export { InitializationRequest } from "./events/initialization.request"; export { InitializationResponse } from "./events/initialization.response"; +export { FinalizationRequest } from "./events/finalization.request"; +export { FinalizationResponse } from "./events/finalization.response"; +export { ResetRequest } from "./events/reset.request"; +export { ResetResponse } from "./events/reset.response"; export { Response } from "./events/response"; export { BehaviorStatusRequest } from "./events/behavior-status.request"; export { BehaviorStatusResponse } from "./events/behavior-status.response"; +export { BehaviorStatusNotification } from "./events/behavior-status.notification"; export { PlantUmlRequest } from "./events/plant-uml.request"; export { PlantUmlResponse } from "./events/plant-uml.response"; +export { PlantUmlNotification } from "./events/plant-uml.notification"; export { CurrentStateRequest } from "./events/current-state.request"; export { CurrentStateResponse } from "./events/current-state.response"; +export { CurrentStateNotification } from "./events/current-state.notification"; export { IBehaviorLocator } from "./interfaces/behavior.locator"; +export { IWatcher } from "./interfaces/watcher"; export { IBehavior } from "./interfaces/behavior"; export { ISystem } from "./interfaces/system"; export { IStateMachineLocator } from "./interfaces/state-machine.locator"; @@ -31,3 +42,7 @@ export { BehaviorInstancesRequest } from "./events/behavior-instances.request"; export { BehaviorInstancesResponse } from "./events/behavior-instances.response"; export { IStateflowsClientTransport } from "./interfaces/stateflows-client-transport"; export { IStateflowsClientTransportFactory } from "./interfaces/stateflows-client-transport-factory"; +export { JsonUtils } from "./utils/json.utils"; +export { NotificationHandler } from "./utils/notification-handler"; +export { NotificationsRequest } from "./events/notifications.request"; +export { NotificationsResponse } from "./events/notifications.response"; diff --git a/Transport/@stateflows/common/src/interfaces/behavior.ts b/Transport/@stateflows/common/src/interfaces/behavior.ts index 50e7a773..106ceb85 100644 --- a/Transport/@stateflows/common/src/interfaces/behavior.ts +++ b/Transport/@stateflows/common/src/interfaces/behavior.ts @@ -1,15 +1,35 @@ import { RequestResult } from "../classes/request-result"; import { SendResult } from "../classes/send-result"; import { BehaviorStatusResponse } from "../events/behavior-status.response"; +import { BehaviorStatusNotification } from "../events/behavior-status.notification"; import { InitializationResponse } from "../events/initialization.response"; +import { FinalizationResponse } from "../events/finalization.response"; +import { ResetResponse } from "../events/reset.response"; +import { CompoundResponse } from "../events/compound.response"; import { Event } from "../events/event"; import { Request } from "../events/request"; import { Response } from "../events/response"; +import { Notification } from "../events/notification"; import { InitializationRequest } from "../events/initialization.request"; +import { NotificationHandler } from "../utils/notification-handler"; +import { BehaviorId } from "../ids/behavior.id"; export interface IBehavior { + id: BehaviorId; + send(event: Event): Promise; + sendCompound(...events: Event[]): Promise>; request(request: Request): Promise>; + initialize(initializationRequest?: InitializationRequest): Promise>; + finalize(): Promise>; + reset(keepVersion?: boolean): Promise>; + reinitialize(initializationRequest?: InitializationRequest): Promise>; + + watch(notificationName: string, handler: NotificationHandler): Promise; + unwatch(notificationName: string): Promise; + getStatus(): Promise>; + watchStatus(handler: NotificationHandler): Promise; + unwatchStatus(): Promise; } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/interfaces/state-machine.ts b/Transport/@stateflows/common/src/interfaces/state-machine.ts index 869db9dc..fd8ab6f8 100644 --- a/Transport/@stateflows/common/src/interfaces/state-machine.ts +++ b/Transport/@stateflows/common/src/interfaces/state-machine.ts @@ -1,7 +1,11 @@ import { RequestResult } from "../classes/request-result"; +import { CurrentStateNotification } from "../events/current-state.notification"; import { CurrentStateResponse } from "../events/current-state.response"; +import { NotificationHandler } from "../utils/notification-handler"; import { IBehavior } from "./behavior"; export interface IStateMachine extends IBehavior { getCurrentState(): Promise>; + watchCurrentState(handler: NotificationHandler): Promise; + unwatchCurrentState(): Promise; } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/interfaces/stateflows-client-transport.ts b/Transport/@stateflows/common/src/interfaces/stateflows-client-transport.ts index ca7c1710..5cc802a4 100644 --- a/Transport/@stateflows/common/src/interfaces/stateflows-client-transport.ts +++ b/Transport/@stateflows/common/src/interfaces/stateflows-client-transport.ts @@ -2,8 +2,11 @@ import { SendResult } from "../classes/send-result"; import { Event } from "../events/event"; import { BehaviorClass } from "../ids/behavior.class"; import { BehaviorId } from "../ids/behavior.id"; +import { IWatcher } from "./watcher"; export interface IStateflowsClientTransport { getAvailableClasses(): Promise; send(behaviorId: BehaviorId, event: Event): Promise; + watch(watcher: IWatcher, notificationName: string): Promise; + unwatch(watcher: IWatcher, notificationName: string): Promise; } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/interfaces/watcher.ts b/Transport/@stateflows/common/src/interfaces/watcher.ts new file mode 100644 index 00000000..25af8663 --- /dev/null +++ b/Transport/@stateflows/common/src/interfaces/watcher.ts @@ -0,0 +1,7 @@ +import { Notification } from "../events/notification"; +import { BehaviorId } from "../ids/behavior.id"; + +export interface IWatcher { + id: BehaviorId; + notify(notification: Notification): void; +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/locators/activity.locator.ts b/Transport/@stateflows/common/src/locators/activity.locator.ts index 5d48a076..2bab571f 100644 --- a/Transport/@stateflows/common/src/locators/activity.locator.ts +++ b/Transport/@stateflows/common/src/locators/activity.locator.ts @@ -12,7 +12,7 @@ export class ActivityLocator implements IActivityLocator { return new Promise((resolve, reject) => { this.behaviorLocator.locateBehavior(id) .then(behavior => resolve(new Activity(behavior as Behavior))) - .catch(_ => reject("State Machine not found")); + .catch(_ => reject("Activity not found")); }); } } \ No newline at end of file diff --git a/Transport/@stateflows/common/src/locators/behavior.locator.ts b/Transport/@stateflows/common/src/locators/behavior.locator.ts index 795081c3..c075183e 100644 --- a/Transport/@stateflows/common/src/locators/behavior.locator.ts +++ b/Transport/@stateflows/common/src/locators/behavior.locator.ts @@ -26,12 +26,13 @@ export class BehaviorLocator implements IBehaviorLocator { locateBehavior(behaviorId: BehaviorId): Promise { return new Promise((resolve, reject) => { this.transportPromise - .then(hub => { + .then(transport => { if (this.behaviorClasses.findIndex(behaviorClass => behaviorClass.type === behaviorId.behaviorClass.type && behaviorClass.name === behaviorId.behaviorClass.name ) !== -1) { - resolve(new Behavior(this.transportPromise, behaviorId)); + let behavior = new Behavior(this.transportPromise, behaviorId); + resolve(behavior); } else { diff --git a/Transport/@stateflows/common/src/utils/json.utils.ts b/Transport/@stateflows/common/src/utils/json.utils.ts new file mode 100644 index 00000000..e0f62868 --- /dev/null +++ b/Transport/@stateflows/common/src/utils/json.utils.ts @@ -0,0 +1,22 @@ +export class JsonUtils { + public static stringify(object: any): string { + const replacer = (key: string, value: any) => + value instanceof Object && !(value instanceof Array) + ? Object.keys(value) + .sort() + .reduce( + (sorted, key) => { + sorted[key] = value[key]; + return sorted; + }, + {} + ) + : value; + + return JSON.stringify(object, replacer); + } + + public static parse(json: string): any { + return JSON.parse(json); + } +} \ No newline at end of file diff --git a/Transport/@stateflows/common/src/utils/notification-handler.ts b/Transport/@stateflows/common/src/utils/notification-handler.ts new file mode 100644 index 00000000..5ab3f17a --- /dev/null +++ b/Transport/@stateflows/common/src/utils/notification-handler.ts @@ -0,0 +1,3 @@ +import { Notification } from "../events/notification"; + +export type NotificationHandler = (notification: TNotification) => void; diff --git a/Transport/@stateflows/http-client/package.json b/Transport/@stateflows/http-client/package.json index 3ee2e19b..cca5c52a 100644 --- a/Transport/@stateflows/http-client/package.json +++ b/Transport/@stateflows/http-client/package.json @@ -1,6 +1,6 @@ { "name": "@stateflows/http-client", - "version": "0.9.2", + "version": "0.10.0", "description": "HTTP-based client for Stateflows framework", "main": "dist/index.js", "module": "./dist/index.mjs", @@ -22,11 +22,11 @@ "author": "Stateflows", "license": "MIT", "homepage": "https://www.stateflows.net", - "dependencies": { - "@stateflows/common": "file:../common" - }, "devDependencies": { "tsup": "^8.0.1", "typescript": "^5.3.3" + }, + "dependencies": { + "@stateflows/common": "^0.11.2" } } diff --git a/Transport/@stateflows/http-client/src/classes/http-transport.ts b/Transport/@stateflows/http-client/src/classes/http-transport.ts index 73772798..8f82e874 100644 --- a/Transport/@stateflows/http-client/src/classes/http-transport.ts +++ b/Transport/@stateflows/http-client/src/classes/http-transport.ts @@ -1,53 +1,203 @@ -import { Event, BehaviorClass, BehaviorId, IStateflowsClientTransport, SendResult } from "@stateflows/common"; +import { Event, BehaviorClass, BehaviorId, IStateflowsClientTransport, SendResult, JsonUtils, IWatcher, Notification, CompoundRequest } from "@stateflows/common"; +import { NotificationTarget } from "./notification-target"; +import { Watch } from "./watch"; +import { NotificationsRequest } from "@stateflows/common"; export class HttpTransport implements IStateflowsClientTransport { + #targets: Map = new Map(); + #notificationIds: Array = []; + constructor(private url: string) { if (url.slice(-1) != '/') { url = url + '/'; } + + setInterval(async () => { + if (this.#targets.size === 0) { + return; + } + + this.#targets.forEach(async target => { + await this.send(target.behaviorId, new NotificationsRequest()); + }); + + // let body = JsonUtils.stringify({ + // $type: "Stateflows.Common.Transport.Classes.StateflowsNotificationRequest, Stateflows.Common.Transport", + // targets: this.getTargets() + // }); + + // let result = await fetch( + // `${this.url}stateflows/getNotifications`, + // { + // method: "POST", + // headers: { + // 'Accept': 'application/json', + // 'Content-Type': 'application/json' + // }, + // body: body + // } + // ); + + // let stateflowsNotificationResponse = await result.json(); + + // this.handleNotifications(stateflowsNotificationResponse.notifications, stateflowsNotificationResponse.responseTime); + }, 10 * 1000); } - getAvailableClasses(): Promise { - return new Promise(async (resolve, reject) => { - fetch(`${this.url}stateflows/availableClasses`) - .then(async result => resolve(await result.json() as BehaviorClass[])) - .catch(reason => reject(reason)); + private updateTimestamp(responseTime: string) { + this.#targets.forEach(target => { + target.watches.forEach(watch => { + watch.lastNotificationCheck = responseTime; + delete watch.milisecondsSinceLastNotificationCheck; + }); }); } - - send(behaviorId: BehaviorId, event: Event): Promise { - return new Promise(async (resolve, reject) => { - const eventName = event.Name; - delete event.Name; - fetch( - `${this.url}stateflows/send`, - { - method: "POST", - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - "$type": "Stateflows.Common.Transport.Classes.StateflowsRequest, Stateflows.Common.Transport", - BehaviorId: behaviorId, - Event: event - }) - } - ) - .then(async result => { - let stateflowsResponse = await result.json(); - let response = stateflowsResponse.Response; - let validation = stateflowsResponse.Validation; - event.Name = eventName; - if (response) { - (event as any).Response = response; + + private handleNotifications(notifications: Array, responseTime: string | null = null) { + if (responseTime !== null) { + this.updateTimestamp(responseTime); + } + + notifications.forEach((notification: Notification) => { + if (this.#notificationIds.includes(notification.id)) { + return; + } + delete (notification.senderId.behaviorClass as any).environment; + + let target = this.#targets.get(JsonUtils.stringify(notification.senderId)); + if (typeof target !== 'undefined') { + target.watches.forEach(watch => { + if (watch.notificationName === notification.name) { + target.handleNotifications([notification]); } + }); + } + }); + + this.#notificationIds = notifications.map(notification => notification.id); + } - let sendResult = new SendResult(event, stateflowsResponse.eventStatus, validation); + // private getTargets(): Array { + // let targets: any[] = []; + + // this.#targets.forEach((target, behaviorId) => { + // let targetIndex = targets.findIndex(t => JsonUtils.stringify(t.id) === JsonUtils.stringify(behaviorId)); + // if (targetIndex !== -1) { + // target.watches.forEach(watch => { + // let watchIndex = targets[targetIndex].watches.findIndex((w: any) => w.notificationName === watch.notificationName); + // if (watchIndex === -1) { + // targets[targetIndex].watches.push({ + // notificationName: watch.notificationName, + // lastNotificationCheck: watch.lastNotificationCheck, + // milisecondsSinceLastNotificationCheck: watch.milisecondsSinceLastNotificationCheck !== null + // ? Date.now() - watch.milisecondsSinceLastNotificationCheck + // : null, + // }); + // } + // }); + // } else { + // targets.push({ + // id: target.behaviorId, + // watches: target.watches.map(watch => { + // return { + // notificationName: watch.notificationName, + // lastNotificationCheck: watch.lastNotificationCheck, + // milisecondsSinceLastNotificationCheck: watch.milisecondsSinceLastNotificationCheck !== null + // ? Date.now() - watch.milisecondsSinceLastNotificationCheck + // : null, + // }; + // }) + // }); + // } + // }); + + // return targets; + // } + + private getWatches(behaviorId: BehaviorId) { + if (this.#targets.has(JsonUtils.stringify(behaviorId))) { + let target = this.#targets.get(JsonUtils.stringify(behaviorId)); + return target.watches.map(watch => { + return { + notificationName: watch.notificationName, + lastNotificationCheck: watch.lastNotificationCheck, + milisecondsSinceLastNotificationCheck: watch.milisecondsSinceLastNotificationCheck !== null + ? Date.now() - watch.milisecondsSinceLastNotificationCheck + : null, + }; + }); + } else { + return []; + } + } - resolve(sendResult); + async getAvailableClasses(): Promise { + let result = await fetch(`${this.url}stateflows/availableClasses`); + return await result.json() as BehaviorClass[]; + } + + async send(behaviorId: BehaviorId, event: Event): Promise { + const eventNameParts = (event as any).$type.split(',')[0].split('.'); + let eventName = eventNameParts[eventNameParts.length - 1]; + if (eventName === 'CompoundRequest') { + const eventNames = (event as CompoundRequest).events.map(event => { + const eventNameParts = (event as any).$type.split(',')[0].split('.'); + return eventNameParts[eventNameParts.length - 1]; + }); + eventName = eventNames.join(','); + } + let result = await fetch( + `${this.url}stateflows/send?${eventName}`, + { + method: "POST", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JsonUtils.stringify({ + "$type": "Stateflows.Common.Transport.Classes.StateflowsRequest, Stateflows.Common.Transport", + behaviorId: behaviorId, + event: event, + watches: this.getWatches(behaviorId) }) - .catch(reason => reject(reason)); - }); + } + ); + + let stateflowsResponse = await result.json(); + let response = stateflowsResponse.response; + let validation = stateflowsResponse.validation; + if (response) { + (event as any).response = response; + } + + this.handleNotifications(stateflowsResponse.notifications, stateflowsResponse.responseTime); + + let sendResult = new SendResult(event, stateflowsResponse.eventStatus, validation); + + return sendResult; + } + + async watch(watcher: IWatcher, notificationName: string): Promise { + let target = this.#targets.has(JsonUtils.stringify(watcher.id)) + ? this.#targets.get(JsonUtils.stringify(watcher.id)) as NotificationTarget + : new NotificationTarget(watcher); + + this.#targets.set(JsonUtils.stringify(watcher.id), target); + + let watchIndex = target.watches.findIndex(watch => watch.notificationName === notificationName); + if (watchIndex === -1) { + target.watches.push(new Watch(notificationName, Date.now())); + } + } + + async unwatch(watcher: IWatcher, notificationName: string): Promise { + if (this.#targets.has(JsonUtils.stringify(watcher.id))) { + let target = this.#targets.get(JsonUtils.stringify(watcher.id)) as NotificationTarget; + let index = target.watches.findIndex(watch => watch.notificationName === notificationName); + if (index !== -1) { + delete target.watches[index]; + } + this.#targets.delete(JsonUtils.stringify(watcher.id)); + } } } \ No newline at end of file diff --git a/Transport/@stateflows/http-client/src/classes/notification-target.ts b/Transport/@stateflows/http-client/src/classes/notification-target.ts new file mode 100644 index 00000000..3fbb9645 --- /dev/null +++ b/Transport/@stateflows/http-client/src/classes/notification-target.ts @@ -0,0 +1,27 @@ +import { BehaviorId } from "@stateflows/common"; +import { Notification } from "@stateflows/common"; +import { IWatcher } from "@stateflows/common"; +import { Watch } from "./watch"; + +export class NotificationTarget { + #watcher: IWatcher; + + constructor(watcher: IWatcher) { + this.#watcher = watcher; + } + + watches: Array = []; + + get behaviorId(): BehaviorId { + return this.#watcher.id; + } + + handleNotifications(notifications: Array) { + let notificationNames = this.watches.map(watch => watch.notificationName); + notifications.forEach(notification => { + if (notificationNames.indexOf(notification.name) !== -1) { + this.#watcher.notify(notification); + } + }); + } +} \ No newline at end of file diff --git a/Transport/@stateflows/http-client/src/classes/watch.ts b/Transport/@stateflows/http-client/src/classes/watch.ts new file mode 100644 index 00000000..0a57ac3c --- /dev/null +++ b/Transport/@stateflows/http-client/src/classes/watch.ts @@ -0,0 +1,12 @@ +import { Notification, NotificationHandler } from "@stateflows/common"; + +export class Watch { + constructor( + public notificationName: string, + public milisecondsSinceLastNotificationCheck: number + ) {} + + handlers: Array> = []; + notifications: Array = []; + lastNotificationCheck: string; +} \ No newline at end of file diff --git a/Transport/@stateflows/signalr-client/package.json b/Transport/@stateflows/signalr-client/package.json index 9fd0ffb4..bbc42df0 100644 --- a/Transport/@stateflows/signalr-client/package.json +++ b/Transport/@stateflows/signalr-client/package.json @@ -1,6 +1,6 @@ { "name": "@stateflows/signalr-client", - "version": "0.9.2", + "version": "0.10.0", "description": "SignalR-based client for Stateflows framework", "main": "dist/index.js", "module": "./dist/index.mjs", diff --git a/Transport/@stateflows/signalr-client/src/classes/signalr-transport-factory.ts b/Transport/@stateflows/signalr-client/src/classes/signalr-transport-factory.ts index 3efd7db8..fb7d8780 100644 --- a/Transport/@stateflows/signalr-client/src/classes/signalr-transport-factory.ts +++ b/Transport/@stateflows/signalr-client/src/classes/signalr-transport-factory.ts @@ -1,14 +1,21 @@ import { IStateflowsClientTransport, IStateflowsClientTransportFactory } from "@stateflows/common"; import { SignalRTransport } from "./signalr-transport"; +import { HubConnectionBuilderAction } from "../types/hub-connection-builder-action"; export class SignalRTransportFactory implements IStateflowsClientTransportFactory { - constructor(private url: string) {} + constructor( + private url: string, + private builderAction: HubConnectionBuilderAction + ) {} getTransport(): Promise { - return Promise.resolve(new SignalRTransport(this.url)); + return Promise.resolve(new SignalRTransport(this.url, this.builderAction)); } } -export function UseSignalR(url: string): IStateflowsClientTransportFactory { - return new SignalRTransportFactory(url); +export function UseSignalR(url: string, builderAction: HubConnectionBuilderAction = null): IStateflowsClientTransportFactory { + if (builderAction !== null) { + builderAction = b => b; + } + return new SignalRTransportFactory(url, builderAction); } \ No newline at end of file diff --git a/Transport/@stateflows/signalr-client/src/classes/signalr-transport.ts b/Transport/@stateflows/signalr-client/src/classes/signalr-transport.ts index bc3b6fb3..1d7bb417 100644 --- a/Transport/@stateflows/signalr-client/src/classes/signalr-transport.ts +++ b/Transport/@stateflows/signalr-client/src/classes/signalr-transport.ts @@ -1,5 +1,6 @@ import { HubConnection, HubConnectionBuilder, HubConnectionState } from "@microsoft/signalr"; -import { Event, BehaviorClass, BehaviorId, IStateflowsClientTransport, SendResult } from "@stateflows/common"; +import { Event, BehaviorClass, BehaviorId, IStateflowsClientTransport, SendResult, JsonUtils, IWatcher } from "@stateflows/common"; +import { HubConnectionBuilderAction } from "../types/hub-connection-builder-action"; export class SignalRTransport implements IStateflowsClientTransport { #hub: Promise | null = null; @@ -7,8 +8,14 @@ export class SignalRTransport implements IStateflowsClientTransport { private get hub(): Promise { if (this.#hub == null) { this.#hub = new Promise((resolve, reject) => { - let hub = new HubConnectionBuilder() - .withUrl(this.url + "stateflows_v1") + let hubBuilder = new HubConnectionBuilder() + .withUrl(this.url + "stateflows_v1"); + + if (this.builderAction !== null) { + hubBuilder = this.builderAction(hubBuilder); + } + + let hub = hubBuilder .build(); hub.start().then(() => resolve(hub)); @@ -31,7 +38,10 @@ export class SignalRTransport implements IStateflowsClientTransport { }); } - constructor(private url: string) { + constructor( + private url: string, + private builderAction: HubConnectionBuilderAction = null + ) { if (url.slice(-1) != '/') { url = url + '/'; } @@ -48,13 +58,19 @@ export class SignalRTransport implements IStateflowsClientTransport { send(behaviorId: BehaviorId, event: Event): Promise { return new Promise(async (resolve, reject) => { let hub = await this.reconnectingHub; - let resultString = await hub.invoke("Send", behaviorId, JSON.stringify(event)); - let result = JSON.parse(resultString); - if (result.Response) { - (event as any).Response = result.Response; - delete result.Response; + let resultString = await hub.invoke("Send", behaviorId, JsonUtils.stringify(event)); + let result = JsonUtils.parse(resultString); + if (result.response) { + (event as any).response = result.response; + delete result.response; } - resolve(new SendResult(event, result.EventStatus, result.Validation)); + resolve(new SendResult(event, result.status, result.validation)); }); } + + async watch(watcher: IWatcher, notificationName: string): Promise { + } + + async unwatch(watcher: IWatcher, notificationName: string): Promise { + } } \ No newline at end of file diff --git a/Transport/@stateflows/signalr-client/src/types/hub-connection-builder-action.ts b/Transport/@stateflows/signalr-client/src/types/hub-connection-builder-action.ts new file mode 100644 index 00000000..05aa5fc0 --- /dev/null +++ b/Transport/@stateflows/signalr-client/src/types/hub-connection-builder-action.ts @@ -0,0 +1,3 @@ +import { HubConnectionBuilder } from "@microsoft/signalr"; + +export type HubConnectionBuilderAction = (builder: HubConnectionBuilder) => HubConnectionBuilder; \ No newline at end of file diff --git a/Transport/Stateflows.Transport.AspNetCore.SignalR.Client/Behavior.cs b/Transport/Stateflows.Transport.AspNetCore.SignalR.Client/Behavior.cs index 1150458f..16a00b29 100644 --- a/Transport/Stateflows.Transport.AspNetCore.SignalR.Client/Behavior.cs +++ b/Transport/Stateflows.Transport.AspNetCore.SignalR.Client/Behavior.cs @@ -35,7 +35,7 @@ public async Task SendAsync(TEvent @event) { var hub = await GetHub(); - var resultString = await hub.InvokeAsync("Send", Id, StateflowsJsonConverter.SerializePolymorphicObject(@event)); + var resultString = await hub.InvokeAsync("Send", Id, StateflowsJsonConverter.SerializePolymorphicObject(@event, true)); var result = StateflowsJsonConverter.DeserializeObject(resultString); @@ -52,7 +52,7 @@ public async Task> RequestAsync(Request("Request", Id, StateflowsJsonConverter.SerializePolymorphicObject(request)); + var resultString = await hub.InvokeAsync("Request", Id, StateflowsJsonConverter.SerializePolymorphicObject(request, true)); var result = StateflowsJsonConverter.DeserializeObject(resultString); @@ -60,5 +60,27 @@ public async Task> RequestAsync(Request(request, result.Status, result.Validation); } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { } + + public Task WatchAsync(Action handler) where TNotification : Notification, new() + { + throw new NotImplementedException(); + } + + public Task UnwatchAsync() where TNotification : Notification, new() + { + throw new NotImplementedException(); + } + + ~Behavior() + => Dispose(false); } } diff --git a/Transport/Stateflows.Transport.AspNetCore.SignalR/StateflowsHub.cs b/Transport/Stateflows.Transport.AspNetCore.SignalR/StateflowsHub.cs index 3c515a2a..e2857e6c 100644 --- a/Transport/Stateflows.Transport.AspNetCore.SignalR/StateflowsHub.cs +++ b/Transport/Stateflows.Transport.AspNetCore.SignalR/StateflowsHub.cs @@ -13,12 +13,38 @@ public class StateflowsHub : Hub private readonly IEnumerable _providers; + private readonly Dictionary _clients = new(); + + private readonly Dictionary> _behaviors = new(); + public StateflowsHub(IBehaviorLocator locator, IEnumerable providers) { _locator = locator; _providers = providers; } + public override Task OnDisconnectedAsync(Exception? exception) + { + lock (_clients) + { + _clients.Remove(Context.ConnectionId); + } + + return base.OnDisconnectedAsync(exception); + } + + public void Greet(Guid clientId) + { + lock (_clients) + { + _clients[Context.ConnectionId] = clientId; + if (!_behaviors.ContainsKey(clientId)) + { + _behaviors[clientId] = new(); + } + } + } + public Task> GetAvailableClasses() { return Task.FromResult(_providers.SelectMany(p => p.BehaviorClasses)); @@ -52,7 +78,7 @@ public async Task Send(BehaviorId behaviorId, string eventData) result = new RequestResult(@event, @event.GetResponse(), result.Status, result.Validation); - return StateflowsJsonConverter.SerializePolymorphicObject(result); + return StateflowsJsonConverter.SerializePolymorphicObject(result, true); } public async Task Request(BehaviorId behaviorId, string requestData) @@ -83,7 +109,7 @@ public async Task Request(BehaviorId behaviorId, string requestData) result = new RequestResult(@event, @event.GetResponse(), result.Status, result.Validation); - return StateflowsJsonConverter.SerializePolymorphicObject(result); + return StateflowsJsonConverter.SerializePolymorphicObject(result, true); } else { diff --git a/Transport/Stateflows.Transport.Http.Client/Behavior.cs b/Transport/Stateflows.Transport.Http.Client/Behavior.cs index ade667c3..c8d41c94 100644 --- a/Transport/Stateflows.Transport.Http.Client/Behavior.cs +++ b/Transport/Stateflows.Transport.Http.Client/Behavior.cs @@ -1,23 +1,57 @@ using Stateflows.Common; -using Stateflows.Common.Extensions; +using Stateflows.Common.Transport.Classes; +using Stateflows.Common.Transport.Interfaces; namespace Stateflows.Transport.Http.Client { - internal class Behavior : IBehavior + internal class Behavior : IBehavior, INotificationTarget { - private readonly StateflowsApiClient _apiClient; + private readonly StateflowsApiClient apiClient; + private readonly List watches = new(); + public IEnumerable Watches + => watches; public BehaviorId Id { get; } public Behavior(StateflowsApiClient apiClient, BehaviorId id) { - _apiClient = apiClient; + this.apiClient = apiClient; + lock (apiClient) + { + this.apiClient.OnNotify += ApiClient_OnNotify; + this.apiClient.NotificationTargets.Add(this); + } + Id = id; } + private void ApiClient_OnNotify(Notification notification, DateTime responseTime) + { + lock (watches) + { + foreach (var watch in watches) + { + watch.LastNotificationCheck = responseTime; + } + + var notifiedWatch = watches.Find(watch => watch.NotificationName == notification.Name); + if (notifiedWatch != null && !notifiedWatch.Notifications.Any(n => n.Id == notification.Id)) + { + notifiedWatch.Notifications.Add(notification); + Task.Run(() => + { + foreach (var handler in notifiedWatch.Handlers) + { + handler.Invoke(notification); + } + }); + } + } + } + public async Task SendAsync(TEvent @event) where TEvent : Event, new() - => await _apiClient.SendAsync(Id, @event); + => await apiClient.SendAsync(Id, @event, watches); public async Task> RequestAsync(Request request) where TResponse : Response, new() @@ -25,5 +59,64 @@ public async Task> RequestAsync(Request(request, result.Status, result.Validation); } + + public Task WatchAsync(Action handler) + where TNotification : Notification, new() + { + lock (watches) + { + var notificationName = EventInfo.Name; + var watch = watches.Find(watch => watch.NotificationName == notificationName); + if (watch == null) + { + watch = new Watch() + { + LastNotificationCheck = DateTime.Now, + NotificationName = notificationName + }; + + watches.Add(watch); + } + + watch.Handlers.Add(notification => handler((TNotification)notification)); + } + + return Task.CompletedTask; + } + + public Task UnwatchAsync() + where TNotification : Notification, new() + { + lock (watches) + { + var notificationName = EventInfo.Name; + var watch = watches.Find(watch => watch.NotificationName == notificationName); + if (watch != null) + { + watches.Remove(watch); + } + } + + return Task.CompletedTask; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + watches.Clear(); + lock (apiClient) + { + apiClient.OnNotify -= ApiClient_OnNotify; + apiClient.NotificationTargets.Remove(this); + } + } + + ~Behavior() + => Dispose(false); } } diff --git a/Transport/Stateflows.Transport.Http.Client/DependencyInjection.cs b/Transport/Stateflows.Transport.Http.Client/DependencyInjection.cs index 09c61d88..10096e79 100644 --- a/Transport/Stateflows.Transport.Http.Client/DependencyInjection.cs +++ b/Transport/Stateflows.Transport.Http.Client/DependencyInjection.cs @@ -7,7 +7,7 @@ namespace Stateflows { public static class DependencyInjection { - public static IStateflowsClientBuilder AddHttpTransport(this IStateflowsClientBuilder builder, Func baseUriProvider, Action? clientBuilderAction = null) + public static IStateflowsClientBuilder AddHttpTransport(this IStateflowsClientBuilder builder, Func baseUriProvider, Action? clientBuilderAction = null, int notificationsCheckSecondsInverval = 10) { if (baseUriProvider == null) throw new ArgumentNullException(nameof(baseUriProvider)); @@ -15,6 +15,7 @@ public static IStateflowsClientBuilder AddHttpTransport(this IStateflowsClientBu var clientBuilder = builder.ServiceCollection .AddSingleton(provider => new BehaviorProvider(provider.GetRequiredService())) + .AddSingleton(provider => new StateflowsApiClientConfig() { NotificationsCheckSecondsInverval = notificationsCheckSecondsInverval }) .AddHttpClient((provider, client) => client.BaseAddress = baseUriProvider(provider)) ; @@ -26,9 +27,9 @@ public static IStateflowsClientBuilder AddHttpTransport(this IStateflowsClientBu return builder; } - public static IStateflowsClientBuilder AddHttpTransport(this IStateflowsClientBuilder builder, Func> baseUriProviderAsync, Action? clientBuilderAction = null) + public static IStateflowsClientBuilder AddHttpTransport(this IStateflowsClientBuilder builder, Func> baseUriProviderAsync, Action? clientBuilderAction = null, int notificationsCheckSecondsInverval = 10) { - return builder.AddHttpTransport(serviceProvider => baseUriProviderAsync(serviceProvider).GetAwaiter().GetResult(), clientBuilderAction); + return builder.AddHttpTransport(serviceProvider => baseUriProviderAsync(serviceProvider).GetAwaiter().GetResult(), clientBuilderAction, notificationsCheckSecondsInverval); } } } diff --git a/Transport/Stateflows.Transport.Http.Client/StateflowsApiClient.cs b/Transport/Stateflows.Transport.Http.Client/StateflowsApiClient.cs index e2ed54b7..e18a113b 100644 --- a/Transport/Stateflows.Transport.Http.Client/StateflowsApiClient.cs +++ b/Transport/Stateflows.Transport.Http.Client/StateflowsApiClient.cs @@ -1,5 +1,5 @@ -using System.Text; using System.Net; +using System.Text; using System.Net.Mime; using System.Net.Http.Json; using System.Diagnostics; @@ -7,26 +7,52 @@ using Stateflows.Common.Utilities; using Stateflows.Common.Extensions; using Stateflows.Common.Transport.Classes; +using Stateflows.Common.Transport.Interfaces; namespace Stateflows.Transport.Http.Client { - public class StateflowsApiClient + internal class StateflowsApiClient : IDisposable { private readonly HttpClient _httpClient; - public StateflowsApiClient(HttpClient httpClient) + private readonly Timer _timer; + + public event Action? OnNotify; + + public List NotificationTargets { get; } = new(); + + public StateflowsApiClient(HttpClient httpClient, StateflowsApiClientConfig config) { _httpClient = httpClient; + + _timer = new Timer(async (_) => await this.CheckForNotificationsAsync(), null, 0, 1000 * config.NotificationsCheckSecondsInverval); + } + + private async Task CheckForNotificationsAsync() + { + IEnumerable targets; + lock (this) + { + targets = NotificationTargets; + } + + await Task.WhenAll(targets.Select(target => SendAsync(target.Id, new NotificationsRequest(), target.Watches))); } [DebuggerHidden] - public async Task SendAsync(BehaviorId behaviorId, Event @event) + public async Task SendAsync(BehaviorId behaviorId, Event @event, IEnumerable watches) { var requestResult = await _httpClient.PostAsync( "/stateflows/send", new StringContent( StateflowsJsonConverter.SerializePolymorphicObject( - new StateflowsRequest() { Event = @event, BehaviorId = behaviorId } + new StateflowsRequest() + { + Event = @event, + BehaviorId = behaviorId, + Watches = watches + }, + true ), Encoding.UTF8, MediaTypeNames.Application.Json @@ -44,6 +70,14 @@ public async Task SendAsync(BehaviorId behaviorId, Event @event) @event.Respond(result.Response); } + lock (this) + { + foreach (var notification in result.Notifications) + { + OnNotify?.Invoke(notification, result.ResponseTime); + } + } + return new SendResult(@event, result.EventStatus, result.Validation); } } @@ -54,5 +88,20 @@ public async Task SendAsync(BehaviorId behaviorId, Event @event) [DebuggerHidden] public async Task> GetAvailableClassesAsync() => await _httpClient.GetFromJsonAsync>($"/stateflows/availableClasses") ?? Array.Empty(); + + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _timer.Dispose(); + } + } } } \ No newline at end of file diff --git a/Transport/Stateflows.Transport.Http.Client/StateflowsApiClientConfig.cs b/Transport/Stateflows.Transport.Http.Client/StateflowsApiClientConfig.cs new file mode 100644 index 00000000..0f1caf72 --- /dev/null +++ b/Transport/Stateflows.Transport.Http.Client/StateflowsApiClientConfig.cs @@ -0,0 +1,7 @@ +namespace Stateflows.Transport.Http.Client +{ + internal class StateflowsApiClientConfig + { + public int NotificationsCheckSecondsInverval { get; set; } = 10; + } +} diff --git a/Transport/Stateflows.Transport.Http/DependencyInjection.cs b/Transport/Stateflows.Transport.Http/DependencyInjection.cs index 9af9247c..dc99bbe8 100644 --- a/Transport/Stateflows.Transport.Http/DependencyInjection.cs +++ b/Transport/Stateflows.Transport.Http/DependencyInjection.cs @@ -24,9 +24,11 @@ public static IEndpointRouteBuilder MapStateflowsHttpTransport(this IEndpointRou "/stateflows/send", async ( HttpContext context, - IBehaviorLocator locator + IBehaviorLocator locator, + INotificationsHub hub ) => { + var responseTime = DateTime.Now; using var reader = new StreamReader(context.Request.Body); var body = await reader.ReadToEndAsync(); var input = StateflowsJsonConverter.DeserializeObject(body); @@ -41,8 +43,15 @@ IBehaviorLocator locator { EventStatus = result.Status, Validation = result.Validation, - Response = result.Event.GetResponse() - } + Response = result.Status == EventStatus.Consumed + ? result.Event.GetResponse() + : null, + Notifications = result.Status != EventStatus.Rejected + ? hub.Notifications.GetPendingNotifications(behaviorId, input.Watches) + : Array.Empty(), + ResponseTime = responseTime, + }, + true ), MediaTypeNames.Application.Json ); diff --git a/Transport/Stateflows.Transport.Http/Stateflows.Transport.Http.csproj b/Transport/Stateflows.Transport.Http/Stateflows.Transport.Http.csproj index c30eb2e6..2165dc4b 100644 --- a/Transport/Stateflows.Transport.Http/Stateflows.Transport.Http.csproj +++ b/Transport/Stateflows.Transport.Http/Stateflows.Transport.Http.csproj @@ -38,7 +38,7 @@ - + diff --git a/Transport/Stateflows.Transport.MassTransit/MassTransit/Consumers/BehaviorRequestConsumer.cs b/Transport/Stateflows.Transport.MassTransit/MassTransit/Consumers/BehaviorRequestConsumer.cs index fdfed4e8..af5ffa04 100644 --- a/Transport/Stateflows.Transport.MassTransit/MassTransit/Consumers/BehaviorRequestConsumer.cs +++ b/Transport/Stateflows.Transport.MassTransit/MassTransit/Consumers/BehaviorRequestConsumer.cs @@ -50,12 +50,12 @@ public async Task Consume(ConsumeContext context) if (@event.IsRequest()) { var response = @event.GetResponse(); - responseMessage.ResponseData = StateflowsJsonConverter.SerializePolymorphicObject(response); + responseMessage.ResponseData = StateflowsJsonConverter.SerializePolymorphicObject(response, true); } if (result.Validation != null) { - responseMessage.ValidationData = StateflowsJsonConverter.SerializePolymorphicObject(result.Validation); + responseMessage.ValidationData = StateflowsJsonConverter.SerializePolymorphicObject(result.Validation, true); } context.Respond(responseMessage); diff --git a/Transport/Stateflows.Transport.MassTransit/Stateflows/Behavior.cs b/Transport/Stateflows.Transport.MassTransit/Stateflows/Behavior.cs index 8a29c672..0fd977a7 100644 --- a/Transport/Stateflows.Transport.MassTransit/Stateflows/Behavior.cs +++ b/Transport/Stateflows.Transport.MassTransit/Stateflows/Behavior.cs @@ -28,7 +28,7 @@ public async Task SendAsync(TEvent @event) new BehaviorRequest() { BehaviorId = Id, - RequestData = StateflowsJsonConverter.SerializePolymorphicObject(@event) + RequestData = StateflowsJsonConverter.SerializePolymorphicObject(@event, true) } );