646 lines
26 KiB
C#
646 lines
26 KiB
C#
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
|
|
}
|