diff --git a/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.cshtml b/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.cshtml index aa8de38b3f..c63baf4af5 100644 --- a/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.cshtml +++ b/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.cshtml @@ -8,18 +8,18 @@ @model Volo.ClientSimulation.Pages.ClientSimulation.SimulationAreaModel
- Start + Start
- Stop + Stop
- @Model.Simulation.State + @Model.Snapshot.State
- @foreach (var client in Model.Simulation.Clients) + @foreach (var client in Model.Snapshot.Clients) { @@ -37,12 +37,12 @@ } - @client.Scenario.GetDisplayText() + @client.Scenario.DisplayText @if (client.State != ClientState.Stopped) { - | @client.Scenario.CurrentStep.GetDisplayText() + | @client.Scenario.CurrentStep.DisplayText } diff --git a/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.cshtml.cs b/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.cshtml.cs index 6faa48d538..e55f504f10 100644 --- a/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.cshtml.cs +++ b/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.cshtml.cs @@ -1,12 +1,15 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Volo.ClientSimulation.Snapshot; namespace Volo.ClientSimulation.Pages.ClientSimulation { public class SimulationAreaModel : PageModel { - public Simulation Simulation { get; } + public SimulationSnapshot Snapshot { get; private set; } + + protected Simulation Simulation { get; } public SimulationAreaModel(Simulation simulation) { @@ -15,6 +18,7 @@ namespace Volo.ClientSimulation.Pages.ClientSimulation public Task OnGetAsync() { + Snapshot = Simulation.CreateSnapshot(); return Task.CompletedTask; } diff --git a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Clients/Client.cs b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Clients/Client.cs index 3d4f303799..b56f733f6f 100644 --- a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Clients/Client.cs +++ b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Clients/Client.cs @@ -1,13 +1,17 @@ -using System.Threading; +using System; +using System.Threading; using Volo.Abp; using Volo.Abp.DependencyInjection; using Volo.Abp.Threading; using Volo.ClientSimulation.Scenarios; +using Volo.ClientSimulation.Snapshot; namespace Volo.ClientSimulation.Clients { public class Client : IClient, ITransientDependency { + public event EventHandler Stopped; + public Scenario Scenario { get; private set; } public ClientState State @@ -23,7 +27,15 @@ namespace Volo.ClientSimulation.Clients public void Initialize(Scenario scenario) { - Scenario = scenario; + lock (SyncLock) + { + if (State != ClientState.Stopped) + { + throw new UserFriendlyException($"Client should be stopped to be able to initialize it. Current state is '{State}'."); + } + + Scenario = scenario; + } } public void Start() @@ -37,6 +49,7 @@ namespace Volo.ClientSimulation.Clients State = ClientState.Running; + Scenario.Reset(); _thread = new Thread(Run); _thread.Start(); } @@ -55,10 +68,20 @@ namespace Volo.ClientSimulation.Clients } } - private void Run() + public ClientSnapshot CreateSnapshot() { - Scenario.Reset(); + lock (SyncLock) + { + return new ClientSnapshot + { + State = State, + Scenario = Scenario.CreateSnapshot() + }; + } + } + private void Run() + { while (true) { lock (SyncLock) @@ -66,6 +89,8 @@ namespace Volo.ClientSimulation.Clients if (State != ClientState.Running) { State = ClientState.Stopped; + _thread = null; + Stopped.InvokeSafely(this); break; } } diff --git a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Clients/IClient.cs b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Clients/IClient.cs index 790c1df553..918dcc5d34 100644 --- a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Clients/IClient.cs +++ b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Clients/IClient.cs @@ -1,9 +1,13 @@ -using Volo.ClientSimulation.Scenarios; +using System; +using Volo.ClientSimulation.Scenarios; +using Volo.ClientSimulation.Snapshot; namespace Volo.ClientSimulation.Clients { public interface IClient { + event EventHandler Stopped; + Scenario Scenario { get; } ClientState State { get; } @@ -13,5 +17,7 @@ namespace Volo.ClientSimulation.Clients void Start(); void Stop(); + + ClientSnapshot CreateSnapshot(); } } \ No newline at end of file diff --git a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Scenarios/Scenario.cs b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Scenarios/Scenario.cs index ec35580a43..b75542557f 100644 --- a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Scenarios/Scenario.cs +++ b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Scenarios/Scenario.cs @@ -1,29 +1,30 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; +using Volo.ClientSimulation.Snapshot; namespace Volo.ClientSimulation.Scenarios { public abstract class Scenario : ITransientDependency { - public IReadOnlyList Steps => StepList.ToImmutableList(); - protected List StepList { get; } + protected List Steps { get; } - public ScenarioStep CurrentStep + protected ScenarioStep CurrentStep { get { CheckStepCount(); - return StepList[CurrentStepIndex]; + return Steps[CurrentStepIndex]; } } - public int CurrentStepIndex { get; protected set; } + + protected int CurrentStepIndex { get; set; } protected Scenario() { - StepList = new List(); + Steps = new List(); } public virtual string GetDisplayText() @@ -37,11 +38,11 @@ namespace Volo.ClientSimulation.Scenarios { CheckStepCount(); - await StepList[CurrentStepIndex].RunAsync(); + await Steps[CurrentStepIndex].RunAsync(); CurrentStepIndex++; - if (CurrentStepIndex >= StepList.Count) + if (CurrentStepIndex >= Steps.Count) { CurrentStepIndex = 0; } @@ -52,19 +53,29 @@ namespace Volo.ClientSimulation.Scenarios CurrentStepIndex = 0; } + public ScenarioSnapshot CreateSnapshot() + { + return new ScenarioSnapshot + { + DisplayText = GetDisplayText(), + Steps = Steps.Select(s => s.CreateSnapshot()).ToList(), + CurrentStep = CurrentStep.CreateSnapshot() + }; + } + + protected void AddStep(ScenarioStep step) + { + Steps.Add(step); + } + private void CheckStepCount() { - if (StepList.Count <= 0) + if (Steps.Count <= 0) { throw new ApplicationException( $"No Steps added to the scenario '{GetDisplayText()}'" ); } } - - protected void AddStep(ScenarioStep step) - { - StepList.Add(step); - } } } diff --git a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Scenarios/ScenarioStep.cs b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Scenarios/ScenarioStep.cs index e21c6d4cd1..53df153fc5 100644 --- a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Scenarios/ScenarioStep.cs +++ b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Scenarios/ScenarioStep.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Threading.Tasks; +using Volo.ClientSimulation.Snapshot; namespace Volo.ClientSimulation.Scenarios { @@ -46,5 +47,13 @@ namespace Volo.ClientSimulation.Scenarios .Name .RemovePostFix(nameof(ScenarioStep)); } + + public ScenarioStepSnapshot CreateSnapshot() + { + return new ScenarioStepSnapshot + { + DisplayText = GetDisplayText() + }; + } } } \ No newline at end of file diff --git a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Simulation.cs b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Simulation.cs index 6d858d7dc0..3684965bbc 100644 --- a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Simulation.cs +++ b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Simulation.cs @@ -1,23 +1,29 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp; using Volo.Abp.DependencyInjection; using Volo.ClientSimulation.Clients; using Volo.ClientSimulation.Scenarios; +using Volo.ClientSimulation.Snapshot; namespace Volo.ClientSimulation { public class Simulation : ISingletonDependency, IDisposable { + public ILogger Logger { get; set; } + public SimulationState State { get; private set; } public List Clients { get; } protected ClientSimulationOptions Options { get; } - - protected IServiceScope ServiceScope { get; } + protected IServiceScopeFactory ServiceScopeFactory { get; } + protected IServiceScope ServiceScope { get; private set; } protected readonly object SyncObj = new object(); @@ -25,27 +31,15 @@ namespace Volo.ClientSimulation IServiceScopeFactory serviceScopeFactory, IOptions options) { + ServiceScopeFactory = serviceScopeFactory; Options = options.Value; - ServiceScope = serviceScopeFactory.CreateScope(); - Clients = new List(); + Logger = NullLogger.Instance; - foreach (var scenarioConfiguration in Options.Scenarios) - { - for (int i = 0; i < scenarioConfiguration.ClientCount; i++) - { - var scenario = (Scenario) ServiceScope.ServiceProvider.GetRequiredService( - scenarioConfiguration.ScenarioType - ); - - var client = ServiceScope.ServiceProvider.GetRequiredService(); - client.Initialize(scenario); - Clients.Add(client); - } - } + Clients = new List(); } - public void Start() + public virtual void Start() { lock (SyncObj) { @@ -56,16 +50,42 @@ namespace Volo.ClientSimulation State = SimulationState.Starting; - foreach (var client in Clients) + try { - client.Start(); + DisposeResources(); + ServiceScope = ServiceScopeFactory.CreateScope(); + + foreach (var scenarioConfiguration in Options.Scenarios) + { + for (int i = 0; i < scenarioConfiguration.ClientCount; i++) + { + var scenario = (Scenario)ServiceScope.ServiceProvider.GetRequiredService( + scenarioConfiguration.ScenarioType + ); + + var client = ServiceScope.ServiceProvider.GetRequiredService(); + client.Stopped += Client_OnStopped; + client.Initialize(scenario); + Clients.Add(client); + } + } + + foreach (var client in Clients) + { + client.Start(); + } + + State = SimulationState.Started; + } + catch(Exception ex) + { + Logger.LogException(ex); + State = SimulationState.Stopped; } - - State = SimulationState.Started; } } - public void Stop() + public virtual void Stop() { lock (SyncObj) { @@ -80,14 +100,48 @@ namespace Volo.ClientSimulation { client.Stop(); } + } + } - State = SimulationState.Stopped; + public virtual SimulationSnapshot CreateSnapshot() + { + lock (SyncObj) + { + return new SimulationSnapshot + { + State = State, + Clients = Clients + .Select(c => c.CreateSnapshot()) + .ToList() + }; + } + } + + public virtual void Dispose() + { + DisposeResources(); + } + + protected virtual void Client_OnStopped(object sender, EventArgs e) + { + lock (SyncObj) + { + if (Clients.All(c => c.State == ClientState.Stopped)) + { + OnStopped(); + } } } - public void Dispose() + private void OnStopped() + { + State = SimulationState.Stopped; + } + + private void DisposeResources() { - ServiceScope.Dispose(); + Clients.Clear(); + ServiceScope?.Dispose(); } } } diff --git a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/ClientSnapshot.cs b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/ClientSnapshot.cs new file mode 100644 index 0000000000..457762d536 --- /dev/null +++ b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/ClientSnapshot.cs @@ -0,0 +1,13 @@ +using System; +using Volo.ClientSimulation.Clients; + +namespace Volo.ClientSimulation.Snapshot +{ + [Serializable] + public class ClientSnapshot + { + public ClientState State { get; set; } + + public ScenarioSnapshot Scenario { get; set; } + } +} \ No newline at end of file diff --git a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/ScenarioSnapshot.cs b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/ScenarioSnapshot.cs new file mode 100644 index 0000000000..9965a06dad --- /dev/null +++ b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/ScenarioSnapshot.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace Volo.ClientSimulation.Snapshot +{ + [Serializable] + public class ScenarioSnapshot + { + public string DisplayText { get; set; } + + public List Steps { get; set; } + + public ScenarioStepSnapshot CurrentStep { get; set; } + } +} \ No newline at end of file diff --git a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/ScenarioStepSnapshot.cs b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/ScenarioStepSnapshot.cs new file mode 100644 index 0000000000..3e4363eda1 --- /dev/null +++ b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/ScenarioStepSnapshot.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.ClientSimulation.Snapshot +{ + [Serializable] + public class ScenarioStepSnapshot + { + public string DisplayText { get; set; } + } +} \ No newline at end of file diff --git a/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/SimulationSnapshot.cs b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/SimulationSnapshot.cs new file mode 100644 index 0000000000..0fe835e0aa --- /dev/null +++ b/modules/client-simulation/src/Volo.ClientSimulation/Volo/ClientSimulation/Snapshot/SimulationSnapshot.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace Volo.ClientSimulation.Snapshot +{ + [Serializable] + public class SimulationSnapshot + { + public SimulationState State { get; set; } + + public List Clients { get; set; } + } +}