Initial commit
This commit is contained in:
645
srcs/RobotNet10/Commons/RobotNet10.ScriptEngine/ScriptEngine.cs
Normal file
645
srcs/RobotNet10/Commons/RobotNet10.ScriptEngine/ScriptEngine.cs
Normal file
@@ -0,0 +1,645 @@
|
||||
using Appccelerate.StateMachine;
|
||||
using Appccelerate.StateMachine.Machine;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RobotNet10.ScriptEngine.Enums;
|
||||
using RobotNet10.ScriptEngine.Helpers;
|
||||
using RobotNet10.ScriptEngine.HubContexts;
|
||||
using RobotNet10.ScriptEngine.Hubs;
|
||||
using RobotNet10.ScriptEngine.Models;
|
||||
using RobotNet10.ScriptEngine.Shared;
|
||||
using RobotNet10.Shared;
|
||||
using System.Text;
|
||||
|
||||
namespace RobotNet10.ScriptEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Core ScriptEngine class that manages script compilation, execution, and state machine.
|
||||
/// </summary>
|
||||
public class ScriptEngine : IDisposable
|
||||
{
|
||||
private readonly PassiveStateMachine<ScriptEngineState, ScriptEngineTrigger> _stateMachine;
|
||||
private readonly ScriptBuilder _scriptBuilder;
|
||||
private readonly FileManager _fileManager;
|
||||
private readonly VariableManager _variableManager;
|
||||
private readonly TaskManager _taskManager;
|
||||
private readonly MissionManager _missionManager;
|
||||
private readonly ILogger<ScriptEngine> _logger;
|
||||
private readonly IHubContext<ScriptManagerHub> _hubContext;
|
||||
private readonly ConsoleHubContext _consoleHubContext;
|
||||
private readonly Lock _stateLockObject = new();
|
||||
private ScriptEngineState _currentState = ScriptEngineState.Initializing;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current state of the ScriptEngine.
|
||||
/// </summary>
|
||||
public ScriptEngineState State => _currentState;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FileManager instance.
|
||||
/// </summary>
|
||||
public FileManager FileManager => _fileManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the VariableManager instance.
|
||||
/// </summary>
|
||||
public VariableManager VariableManager => _variableManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TaskManager instance.
|
||||
/// </summary>
|
||||
public TaskManager TaskManager => _taskManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MissionManager instance.
|
||||
/// </summary>
|
||||
public MissionManager MissionManager => _missionManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of ScriptEngine.
|
||||
/// </summary>
|
||||
public ScriptEngine(
|
||||
IScriptEngineResource scriptResource,
|
||||
IConfiguration configuration,
|
||||
FileManager fileManager,
|
||||
VariableManager variableManager,
|
||||
TaskManager taskManager,
|
||||
MissionManager missionManager,
|
||||
ILogger<ScriptEngine> logger,
|
||||
IHubContext<ScriptManagerHub> hubContext,
|
||||
ConsoleHubContext consoleHubContext)
|
||||
{
|
||||
_fileManager = fileManager ?? throw new ArgumentNullException(nameof(fileManager));
|
||||
_variableManager = variableManager ?? throw new ArgumentNullException(nameof(variableManager));
|
||||
_taskManager = taskManager ?? throw new ArgumentNullException(nameof(taskManager));
|
||||
_missionManager = missionManager ?? throw new ArgumentNullException(nameof(missionManager));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
|
||||
_consoleHubContext = consoleHubContext ?? throw new ArgumentNullException(nameof(consoleHubContext));
|
||||
_scriptBuilder = new ScriptBuilder(scriptResource, configuration["ScriptEngine:RuntimeDllFolder"] ?? "dlls");
|
||||
|
||||
var builder = new StateMachineDefinitionBuilder<ScriptEngineState, ScriptEngineTrigger>();
|
||||
|
||||
// Configure state machine transitions according to StateMachine_Design.md
|
||||
ConfigureStateMachine(builder);
|
||||
|
||||
_stateMachine = builder
|
||||
.WithInitialState(ScriptEngineState.Initializing)
|
||||
.Build()
|
||||
.CreatePassiveStateMachine();
|
||||
|
||||
_currentState = ScriptEngineState.Initializing;
|
||||
_stateMachine.Start();
|
||||
|
||||
// Auto-transition from Initializing to Idle
|
||||
_stateMachine.Fire(ScriptEngineTrigger.InitializationCompleted);
|
||||
}
|
||||
|
||||
private void ConfigureStateMachine(StateMachineDefinitionBuilder<ScriptEngineState, ScriptEngineTrigger> builder)
|
||||
{
|
||||
// Initializing state
|
||||
builder.In(ScriptEngineState.Initializing)
|
||||
.ExecuteOnEntry(() => { lock (_stateLockObject) { _currentState = ScriptEngineState.Initializing; } })
|
||||
.On(ScriptEngineTrigger.InitializationCompleted)
|
||||
.Goto(ScriptEngineState.Idle)
|
||||
.On(ScriptEngineTrigger.Reset)
|
||||
.Goto(ScriptEngineState.Resetting);
|
||||
|
||||
// Resetting state
|
||||
builder.In(ScriptEngineState.Resetting)
|
||||
.ExecuteOnEntry(() => { lock (_stateLockObject) { _currentState = ScriptEngineState.Resetting; } OnEnterResetting(); })
|
||||
.On(ScriptEngineTrigger.ResettingCompleted)
|
||||
.Goto(ScriptEngineState.Idle)
|
||||
.On(ScriptEngineTrigger.FaultOccurred)
|
||||
.Goto(ScriptEngineState.Fault);
|
||||
|
||||
// Idle state - scripts can be edited
|
||||
builder.In(ScriptEngineState.Idle)
|
||||
.ExecuteOnEntry(() => { lock (_stateLockObject) { _currentState = ScriptEngineState.Idle; } OnEnterIdle(); })
|
||||
.On(ScriptEngineTrigger.Build)
|
||||
.Goto(ScriptEngineState.Building)
|
||||
.On(ScriptEngineTrigger.Reset)
|
||||
.Goto(ScriptEngineState.Resetting);
|
||||
|
||||
// Building state
|
||||
builder.In(ScriptEngineState.Building)
|
||||
.ExecuteOnEntry(() => { lock (_stateLockObject) { _currentState = ScriptEngineState.Building; } OnEnterBuilding(); })
|
||||
.On(ScriptEngineTrigger.BuildingCompleted)
|
||||
.Goto(ScriptEngineState.Ready)
|
||||
.On(ScriptEngineTrigger.BuildErrorOccurred)
|
||||
.Goto(ScriptEngineState.BuildError)
|
||||
.On(ScriptEngineTrigger.FaultOccurred)
|
||||
.Goto(ScriptEngineState.Fault);
|
||||
|
||||
// BuildError state
|
||||
builder.In(ScriptEngineState.BuildError)
|
||||
.ExecuteOnEntry(() => { lock (_stateLockObject) { _currentState = ScriptEngineState.BuildError; } OnEnterBuildError(); })
|
||||
.On(ScriptEngineTrigger.Reset)
|
||||
.Goto(ScriptEngineState.Idle)
|
||||
.On(ScriptEngineTrigger.Build)
|
||||
.Goto(ScriptEngineState.Building);
|
||||
|
||||
// Ready state - scripts compiled successfully, cannot edit
|
||||
builder.In(ScriptEngineState.Ready)
|
||||
.ExecuteOnEntry(() => { lock (_stateLockObject) { _currentState = ScriptEngineState.Ready; } OnEnterReady(); })
|
||||
.On(ScriptEngineTrigger.Start)
|
||||
.Goto(ScriptEngineState.Starting)
|
||||
.On(ScriptEngineTrigger.Reset)
|
||||
.Goto(ScriptEngineState.Idle)
|
||||
.On(ScriptEngineTrigger.Build)
|
||||
.Goto(ScriptEngineState.Building);
|
||||
|
||||
// Starting state
|
||||
builder.In(ScriptEngineState.Starting)
|
||||
.ExecuteOnEntry(() => { lock (_stateLockObject) { _currentState = ScriptEngineState.Starting; } OnEnterStarting(); })
|
||||
.On(ScriptEngineTrigger.StartingCompleted)
|
||||
.Goto(ScriptEngineState.Running)
|
||||
.On(ScriptEngineTrigger.FaultOccurred)
|
||||
.Goto(ScriptEngineState.Fault);
|
||||
|
||||
// Running state - tasks and missions can execute
|
||||
builder.In(ScriptEngineState.Running)
|
||||
.ExecuteOnEntry(() => { lock (_stateLockObject) { _currentState = ScriptEngineState.Running; } OnEnterRunning(); })
|
||||
.On(ScriptEngineTrigger.Stop)
|
||||
.Goto(ScriptEngineState.Stopping)
|
||||
.On(ScriptEngineTrigger.Reset)
|
||||
.Goto(ScriptEngineState.Resetting)
|
||||
.On(ScriptEngineTrigger.FaultOccurred)
|
||||
.Goto(ScriptEngineState.Fault);
|
||||
|
||||
// Stopping state
|
||||
builder.In(ScriptEngineState.Stopping)
|
||||
.ExecuteOnEntry(() => { lock (_stateLockObject) { _currentState = ScriptEngineState.Stopping; } OnEnterStopping(); })
|
||||
.On(ScriptEngineTrigger.StoppingCompleted)
|
||||
.Goto(ScriptEngineState.Ready)
|
||||
.On(ScriptEngineTrigger.FaultOccurred)
|
||||
.Goto(ScriptEngineState.Fault);
|
||||
|
||||
// Fault state
|
||||
builder.In(ScriptEngineState.Fault)
|
||||
.ExecuteOnEntry(() => { lock (_stateLockObject) { _currentState = ScriptEngineState.Fault; } OnEnterFault(); })
|
||||
.On(ScriptEngineTrigger.Reset)
|
||||
.Goto(ScriptEngineState.Resetting);
|
||||
}
|
||||
|
||||
#region State Machine Event Handlers
|
||||
|
||||
private void OnEnterIdle()
|
||||
{
|
||||
_fileManager.SetState(ScriptEngineState.Idle);
|
||||
var message = "ScriptEngine entered Idle state. Scripts can be edited.";
|
||||
_logger.LogInformation(message);
|
||||
_consoleHubContext.LogInfo(message);
|
||||
_ = NotifyStateChanged(ScriptEngineState.Idle);
|
||||
}
|
||||
|
||||
private void OnEnterBuilding()
|
||||
{
|
||||
_fileManager.SetState(ScriptEngineState.Building);
|
||||
var message = "ScriptEngine entered Building state. Compiling scripts...";
|
||||
_logger.LogInformation(message);
|
||||
_consoleHubContext.LogInfo(message);
|
||||
_ = NotifyStateChanged(ScriptEngineState.Building);
|
||||
|
||||
// Fire and forget async operation with proper error handling
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Aggregate all script files code
|
||||
var aggregatedCode = await _fileManager.AggregateAllCodeAsync();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(aggregatedCode))
|
||||
{
|
||||
var warningMessage = "No script files found to compile.";
|
||||
_logger.LogWarning(warningMessage);
|
||||
_consoleHubContext.LogWarning(warningMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.BuildingCompleted);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build using ScriptBuilder
|
||||
_scriptBuilder.Build(aggregatedCode, out var variables, out var tasks, out var missions);
|
||||
|
||||
// Load into managers
|
||||
_variableManager.Load(variables);
|
||||
var taskLoadResult = _taskManager.Load(tasks);
|
||||
var missionLoadResult = _missionManager.Load(missions);
|
||||
|
||||
if (!taskLoadResult.IsSuccess)
|
||||
{
|
||||
var errorMessage = $"Failed to load tasks: {taskLoadResult.Message}";
|
||||
_logger.LogError(errorMessage);
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.BuildErrorOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!missionLoadResult.IsSuccess)
|
||||
{
|
||||
var errorMessage = $"Failed to load missions: {missionLoadResult.Message}";
|
||||
_logger.LogError(errorMessage);
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.BuildErrorOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
var successMessage = $"Build completed successfully. Loaded {variables.Count()} variables, {tasks.Count()} tasks, {missions.Count()} missions.";
|
||||
_logger.LogInformation(successMessage);
|
||||
_consoleHubContext.LogInfo(successMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.BuildingCompleted);
|
||||
}
|
||||
catch (ScriptCompilationException ex)
|
||||
{
|
||||
var errorMessage = $"Script compilation failed: {ex.Message}";
|
||||
_logger.LogError(ex, "Script compilation failed.");
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.BuildErrorOccurred);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = $"Unexpected error during script building: {ex.Message}";
|
||||
_logger.LogError(ex, "Unexpected error during script building.");
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.FaultOccurred);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnEnterBuildError()
|
||||
{
|
||||
_fileManager.SetState(ScriptEngineState.BuildError);
|
||||
var errorMessage = "ScriptEngine entered BuildError state. Script compilation failed.";
|
||||
_logger.LogWarning(errorMessage);
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
_ = NotifyStateChanged(ScriptEngineState.BuildError);
|
||||
}
|
||||
|
||||
private void OnEnterReady()
|
||||
{
|
||||
_fileManager.SetState(ScriptEngineState.Ready);
|
||||
var message = "ScriptEngine entered Ready state. Scripts compiled successfully, ready to start.";
|
||||
_logger.LogInformation(message);
|
||||
_consoleHubContext.LogInfo(message);
|
||||
_ = NotifyStateChanged(ScriptEngineState.Ready);
|
||||
}
|
||||
|
||||
private void OnEnterStarting()
|
||||
{
|
||||
_fileManager.SetState(ScriptEngineState.Starting);
|
||||
var message = "ScriptEngine entered Starting state. Starting TaskManager and MissionManager...";
|
||||
_logger.LogInformation(message);
|
||||
_consoleHubContext.LogInfo(message);
|
||||
_ = NotifyStateChanged(ScriptEngineState.Starting);
|
||||
|
||||
// Fire and forget async operation with proper error handling
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Start TaskManager and MissionManager
|
||||
var taskStartResult = _taskManager.Start();
|
||||
var missionStartResult = _missionManager.Start();
|
||||
|
||||
if (!taskStartResult.IsSuccess)
|
||||
{
|
||||
var errorMessage = $"Failed to start TaskManager: {taskStartResult.Message}";
|
||||
_logger.LogError(errorMessage);
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.FaultOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!missionStartResult.IsSuccess)
|
||||
{
|
||||
var errorMessage = $"Failed to start MissionManager: {missionStartResult.Message}";
|
||||
_logger.LogError(errorMessage);
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.FaultOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait a bit to ensure managers are started
|
||||
await Task.Delay(100);
|
||||
|
||||
var successMessage = "TaskManager and MissionManager started successfully.";
|
||||
_logger.LogInformation(successMessage);
|
||||
_consoleHubContext.LogInfo(successMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.StartingCompleted);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = $"Unexpected error during starting: {ex.Message}";
|
||||
_logger.LogError(ex, "Unexpected error during starting.");
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.FaultOccurred);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnEnterRunning()
|
||||
{
|
||||
_fileManager.SetState(ScriptEngineState.Running);
|
||||
var message = "ScriptEngine entered Running state. Tasks and missions can execute.";
|
||||
_logger.LogInformation(message);
|
||||
_consoleHubContext.LogInfo(message);
|
||||
_ = NotifyStateChanged(ScriptEngineState.Running);
|
||||
}
|
||||
|
||||
private void OnEnterStopping()
|
||||
{
|
||||
_fileManager.SetState(ScriptEngineState.Stopping);
|
||||
var message = "ScriptEngine entered Stopping state. Stopping TaskManager and MissionManager...";
|
||||
_logger.LogInformation(message);
|
||||
_consoleHubContext.LogInfo(message);
|
||||
_ = NotifyStateChanged(ScriptEngineState.Stopping);
|
||||
|
||||
// Fire and forget async operation with proper error handling
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Stop TaskManager and MissionManager
|
||||
var taskStopResult = _taskManager.Stop();
|
||||
var missionStopResult = _missionManager.Stop();
|
||||
|
||||
if (!taskStopResult.IsSuccess)
|
||||
{
|
||||
_logger.LogWarning($"Failed to stop TaskManager: {taskStopResult.Message}");
|
||||
}
|
||||
|
||||
if (!missionStopResult.IsSuccess)
|
||||
{
|
||||
_logger.LogWarning($"Failed to stop MissionManager: {missionStopResult.Message}");
|
||||
}
|
||||
|
||||
// Wait for all tasks to stop and all missions to not be running
|
||||
// Only timeout when tasks/missions are actually executing (ScriptRunner running)
|
||||
var maxWaitTime = TimeSpan.FromSeconds(30);
|
||||
var checkInterval = TimeSpan.FromMilliseconds(100);
|
||||
var elapsed = TimeSpan.Zero;
|
||||
|
||||
while (elapsed < maxWaitTime)
|
||||
{
|
||||
// Check if all tasks are stopped (snapshot collection to avoid race condition)
|
||||
var tasksSnapshot = _taskManager.Tasks.Values.ToList();
|
||||
// Check both state and IsExecuting - only consider stopped if not executing
|
||||
var allTasksStopped = tasksSnapshot.All(t =>
|
||||
(t.State == ScriptTaskState.Stopped || t.State == ScriptTaskState.Idle || t.State == ScriptTaskState.Error)
|
||||
&& !t.IsExecuting);
|
||||
|
||||
// Check if all missions are not running (snapshot collection to avoid race condition)
|
||||
var missionsSnapshot = _missionManager.GetAllMissions().ToList();
|
||||
// Only consider stopped if not in Running state and not executing
|
||||
var allMissionsNotRunning = missionsSnapshot.All(m =>
|
||||
m.State != ScriptMissionState.Running && !m.IsExecuting);
|
||||
|
||||
if (allTasksStopped && allMissionsNotRunning)
|
||||
{
|
||||
var successMessage = "All tasks stopped and all missions are not running.";
|
||||
_logger.LogInformation(successMessage);
|
||||
_consoleHubContext.LogInfo(successMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.StoppingCompleted);
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Delay(checkInterval);
|
||||
elapsed = elapsed.Add(checkInterval);
|
||||
}
|
||||
|
||||
// Only log timeout if there are actually running tasks/missions
|
||||
var runningTasks = _taskManager.Tasks.Values.Where(t => t.IsExecuting).ToList();
|
||||
var runningMissions = _missionManager.GetAllMissions().Where(m => m.IsExecuting).ToList();
|
||||
|
||||
if (runningTasks.Count > 0 || runningMissions.Count > 0)
|
||||
{
|
||||
var warningMessage = $"Timeout waiting for {runningTasks.Count} task(s) and {runningMissions.Count} mission(s) to stop. ScriptRunner may be blocking. Proceeding anyway.";
|
||||
_logger.LogWarning(warningMessage);
|
||||
_consoleHubContext.LogWarning(warningMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
// All stopped but state machine may not have transitioned - proceed anyway
|
||||
var infoMessage = "All tasks and missions stopped. Proceeding with state transition.";
|
||||
_logger.LogInformation(infoMessage);
|
||||
_consoleHubContext.LogInfo(infoMessage);
|
||||
}
|
||||
|
||||
_stateMachine.Fire(ScriptEngineTrigger.StoppingCompleted);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = $"Unexpected error during stopping: {ex.Message}";
|
||||
_logger.LogError(ex, "Unexpected error during stopping.");
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.FaultOccurred);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnEnterResetting()
|
||||
{
|
||||
_fileManager.SetState(ScriptEngineState.Resetting);
|
||||
var message = "ScriptEngine entered Resetting state. Resetting managers...";
|
||||
_logger.LogInformation(message);
|
||||
_consoleHubContext.LogInfo(message);
|
||||
|
||||
try
|
||||
{
|
||||
// Reset all managers
|
||||
_variableManager.Reset();
|
||||
var taskResetResult = _taskManager.Reset();
|
||||
var missionResetResult = _missionManager.Reset();
|
||||
|
||||
if (!taskResetResult.IsSuccess)
|
||||
{
|
||||
_logger.LogWarning($"Failed to reset TaskManager: {taskResetResult.Message}");
|
||||
}
|
||||
|
||||
if (!missionResetResult.IsSuccess)
|
||||
{
|
||||
_logger.LogWarning($"Failed to reset MissionManager: {missionResetResult.Message}");
|
||||
}
|
||||
|
||||
var successMessage = "All managers reset successfully.";
|
||||
_logger.LogInformation(successMessage);
|
||||
_consoleHubContext.LogInfo(successMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.ResettingCompleted);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = $"Unexpected error during resetting: {ex.Message}";
|
||||
_logger.LogError(ex, "Unexpected error during resetting.");
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
_stateMachine.Fire(ScriptEngineTrigger.FaultOccurred);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnterFault()
|
||||
{
|
||||
_fileManager.SetState(ScriptEngineState.Fault);
|
||||
var errorMessage = "ScriptEngine entered Fault state. System error occurred.";
|
||||
_logger.LogError(errorMessage);
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
_ = NotifyStateChanged(ScriptEngineState.Fault);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies all connected clients about state change via SignalR.
|
||||
/// </summary>
|
||||
private async Task NotifyStateChanged(ScriptEngineState newState)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _hubContext.Clients.All.SendAsync("StateChanged", newState);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to notify state change via SignalR.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Builds scripts from all files. Only allowed when state is Idle or BuildError.
|
||||
/// </summary>
|
||||
public MessageResult Build()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_stateLockObject)
|
||||
{
|
||||
if (_currentState != ScriptEngineState.Idle && _currentState != ScriptEngineState.BuildError)
|
||||
{
|
||||
return new MessageResult(false, $"Cannot build when ScriptEngine is in state: {_currentState}");
|
||||
}
|
||||
}
|
||||
|
||||
_stateMachine.Fire(ScriptEngineTrigger.Build);
|
||||
return new MessageResult(true, "Build initiated successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = $"Failed to initiate build: {ex.Message}";
|
||||
_logger.LogError(ex, "Failed to initiate build.");
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
return new MessageResult(false, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the ScriptEngine. Only allowed when state is Ready.
|
||||
/// </summary>
|
||||
public MessageResult Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_stateLockObject)
|
||||
{
|
||||
if (_currentState != ScriptEngineState.Ready)
|
||||
{
|
||||
return new MessageResult(false, $"Cannot start when ScriptEngine is in state: {_currentState}");
|
||||
}
|
||||
}
|
||||
|
||||
_stateMachine.Fire(ScriptEngineTrigger.Start);
|
||||
return new MessageResult(true, "Start initiated successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = $"Failed to initiate start: {ex.Message}";
|
||||
_logger.LogError(ex, "Failed to initiate start.");
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
return new MessageResult(false, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the ScriptEngine. Only allowed when state is Running.
|
||||
/// </summary>
|
||||
public MessageResult Stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_stateLockObject)
|
||||
{
|
||||
if (_currentState != ScriptEngineState.Running)
|
||||
{
|
||||
return new MessageResult(false, $"Cannot stop when ScriptEngine is in state: {_currentState}");
|
||||
}
|
||||
}
|
||||
|
||||
_stateMachine.Fire(ScriptEngineTrigger.Stop);
|
||||
return new MessageResult(true, "Stop initiated successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = $"Failed to initiate stop: {ex.Message}";
|
||||
_logger.LogError(ex, "Failed to initiate stop.");
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
return new MessageResult(false, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the ScriptEngine. Allowed from Idle, Ready, BuildError, Running, or Fault.
|
||||
/// </summary>
|
||||
public MessageResult Reset()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_stateLockObject)
|
||||
{
|
||||
if (_currentState != ScriptEngineState.Idle &&
|
||||
_currentState != ScriptEngineState.Ready &&
|
||||
_currentState != ScriptEngineState.BuildError &&
|
||||
_currentState != ScriptEngineState.Running &&
|
||||
_currentState != ScriptEngineState.Fault)
|
||||
{
|
||||
return new MessageResult(false, $"Cannot reset when ScriptEngine is in state: {_currentState}");
|
||||
}
|
||||
}
|
||||
|
||||
_stateMachine.Fire(ScriptEngineTrigger.Reset);
|
||||
return new MessageResult(true, "Reset initiated successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = $"Failed to initiate reset: {ex.Message}";
|
||||
_logger.LogError(ex, "Failed to initiate reset.");
|
||||
_consoleHubContext.LogError(errorMessage);
|
||||
return new MessageResult(false, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_stateMachine?.Stop();
|
||||
_taskManager?.Dispose();
|
||||
_missionManager?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during ScriptEngine disposal.");
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user