558 lines
18 KiB
Markdown
558 lines
18 KiB
Markdown
# Appccelerate.StateMachine Usage Guide / Hướng dẫn Sử dụng Appccelerate.StateMachine
|
|
|
|
## 📋 Overview / Tổng quan
|
|
|
|
Tài liệu này mô tả cách sử dụng thư viện **Appccelerate.StateMachine** trong dự án RobotNet10. Thư viện này được sử dụng để quản lý state machine cho ScriptEngine (Task, Mission, và Engine Manager).
|
|
|
|
## 📦 Installation / Cài đặt
|
|
|
|
### NuGet Package
|
|
|
|
```xml
|
|
<PackageReference Include="Appccelerate.StateMachine" Version="6.0.0" />
|
|
```
|
|
|
|
### Namespaces
|
|
|
|
```csharp
|
|
using Appccelerate.StateMachine;
|
|
using Appccelerate.StateMachine.Machine;
|
|
```
|
|
|
|
## 🎯 Core Concepts / Khái niệm Cơ bản
|
|
|
|
### 1. States / Trạng thái
|
|
|
|
States là các trạng thái mà state machine có thể ở trong. Thường được định nghĩa bằng enum:
|
|
|
|
```csharp
|
|
public enum TaskState
|
|
{
|
|
Idle = 0,
|
|
Running,
|
|
Pausing,
|
|
Paused,
|
|
Resuming,
|
|
Stopping,
|
|
Stopped,
|
|
Error,
|
|
}
|
|
```
|
|
|
|
### 2. Triggers / Sự kiện
|
|
|
|
Triggers là các sự kiện có thể kích hoạt chuyển đổi trạng thái. Cũng thường là enum:
|
|
|
|
```csharp
|
|
public enum TaskTrigger
|
|
{
|
|
Start,
|
|
Pause,
|
|
Resume,
|
|
Stop,
|
|
Dispose,
|
|
PausingCompleted,
|
|
ResumingCompleted,
|
|
StoppingCompleted,
|
|
ErrorOccurred,
|
|
}
|
|
```
|
|
|
|
### 3. State Machine Types / Các Loại State Machine
|
|
|
|
Appccelerate.StateMachine hỗ trợ hai loại state machine:
|
|
|
|
- **PassiveStateMachine**: State machine được điều khiển thủ công, cần gọi `Fire()` để trigger transitions
|
|
- **ActiveStateMachine**: State machine tự động xử lý events từ queue
|
|
|
|
**Trong RobotNet10, chúng ta sử dụng `PassiveStateMachine`** để có kiểm soát tốt hơn.
|
|
|
|
## 🏗️ Building State Machine / Xây dựng State Machine
|
|
|
|
### Step 1: Create Builder / Tạo Builder
|
|
|
|
```csharp
|
|
var builder = new StateMachineDefinitionBuilder<TState, TTrigger>();
|
|
```
|
|
|
|
### Step 2: Configure States / Cấu hình States
|
|
|
|
Sử dụng fluent API để cấu hình các states và transitions:
|
|
|
|
```csharp
|
|
builder.In(TaskState.Idle)
|
|
.On(TaskTrigger.Start)
|
|
.Goto(TaskState.Running)
|
|
.Execute(() => OnEnterRunning());
|
|
```
|
|
|
|
### Step 3: Configure Entry/Exit Actions / Cấu hình Entry/Exit Actions
|
|
|
|
**Important**: `ExecuteOnEntry()` và `ExecuteOnExit()` phải được gọi **trước** các `On()` calls:
|
|
|
|
```csharp
|
|
builder.In(TaskState.Running)
|
|
.ExecuteOnEntry(() => { _currentState = TaskState.Running; OnEnterRunning(); })
|
|
.ExecuteOnExit(() => OnExitRunning())
|
|
.On(TaskTrigger.Pause)
|
|
.Goto(TaskState.Pausing)
|
|
.Execute(() => { _currentState = TaskState.Pausing; OnEnterPausing(); });
|
|
```
|
|
|
|
**⚠️ Common Mistake**: Đặt `ExecuteOnEntry()` sau `On()` sẽ gây lỗi compile.
|
|
|
|
### Step 4: Build and Create / Build và Tạo
|
|
|
|
```csharp
|
|
var stateMachine = builder
|
|
.WithInitialState(TaskState.Idle)
|
|
.Build()
|
|
.CreatePassiveStateMachine();
|
|
|
|
stateMachine.Start();
|
|
```
|
|
|
|
## 📝 Complete Example / Ví dụ Hoàn chỉnh
|
|
|
|
Dựa trên implementation của `ScriptTask`:
|
|
|
|
```csharp
|
|
using Appccelerate.StateMachine;
|
|
using Appccelerate.StateMachine.Machine;
|
|
|
|
public class ScriptTask
|
|
{
|
|
private readonly PassiveStateMachine<ScriptTaskState, TaskTrigger> _stateMachine;
|
|
private ScriptTaskState _currentState; // Track state manually
|
|
|
|
public enum TaskTrigger
|
|
{
|
|
Start,
|
|
Pause,
|
|
Resume,
|
|
Stop,
|
|
Dispose,
|
|
PausingCompleted,
|
|
ResumingCompleted,
|
|
StoppingCompleted,
|
|
ErrorOccurred,
|
|
}
|
|
|
|
public ScriptTask()
|
|
{
|
|
var builder = new StateMachineDefinitionBuilder<ScriptTaskState, TaskTrigger>();
|
|
ConfigureStateMachine(builder);
|
|
|
|
_stateMachine = builder
|
|
.WithInitialState(ScriptTaskState.Idle)
|
|
.Build()
|
|
.CreatePassiveStateMachine();
|
|
|
|
_currentState = ScriptTaskState.Idle;
|
|
_stateMachine.Start();
|
|
}
|
|
|
|
private void ConfigureStateMachine(StateMachineDefinitionBuilder<ScriptTaskState, TaskTrigger> builder)
|
|
{
|
|
// Idle state
|
|
builder.In(ScriptTaskState.Idle)
|
|
.On(TaskTrigger.Start)
|
|
.Goto(ScriptTaskState.Running)
|
|
.Execute(() => { _currentState = ScriptTaskState.Running; OnEnterRunning(); });
|
|
|
|
// Running state
|
|
builder.In(ScriptTaskState.Running)
|
|
.ExecuteOnEntry(() => { _currentState = ScriptTaskState.Running; OnEnterRunning(); })
|
|
.ExecuteOnExit(() => OnExitRunning())
|
|
.On(TaskTrigger.Pause)
|
|
.Goto(ScriptTaskState.Pausing)
|
|
.Execute(() => { _currentState = ScriptTaskState.Pausing; OnEnterPausing(); })
|
|
.On(TaskTrigger.Stop)
|
|
.Goto(ScriptTaskState.Stopping)
|
|
.Execute(() => { _currentState = ScriptTaskState.Stopping; OnEnterStopping(); })
|
|
.On(TaskTrigger.ErrorOccurred)
|
|
.Goto(ScriptTaskState.Error)
|
|
.Execute(() => { _currentState = ScriptTaskState.Error; OnEnterError(); });
|
|
|
|
// Pausing state
|
|
builder.In(ScriptTaskState.Pausing)
|
|
.ExecuteOnEntry(() => { _currentState = ScriptTaskState.Pausing; OnEnterPausing(); })
|
|
.ExecuteOnExit(() => OnExitPausing())
|
|
.On(TaskTrigger.PausingCompleted)
|
|
.Goto(ScriptTaskState.Paused)
|
|
.Execute(() => { _currentState = ScriptTaskState.Paused; OnEnterPaused(); })
|
|
.On(TaskTrigger.ErrorOccurred)
|
|
.Goto(ScriptTaskState.Error)
|
|
.Execute(() => { _currentState = ScriptTaskState.Error; OnEnterError(); });
|
|
|
|
// Paused state
|
|
builder.In(ScriptTaskState.Paused)
|
|
.ExecuteOnEntry(() => { _currentState = ScriptTaskState.Paused; OnEnterPaused(); })
|
|
.ExecuteOnExit(() => OnExitPaused())
|
|
.On(TaskTrigger.Resume)
|
|
.Goto(ScriptTaskState.Resuming)
|
|
.Execute(() => { _currentState = ScriptTaskState.Resuming; OnEnterResuming(); })
|
|
.On(TaskTrigger.Stop)
|
|
.Goto(ScriptTaskState.Stopping)
|
|
.Execute(() => { _currentState = ScriptTaskState.Stopping; OnEnterStopping(); });
|
|
|
|
// Resuming state
|
|
builder.In(ScriptTaskState.Resuming)
|
|
.ExecuteOnEntry(() => { _currentState = ScriptTaskState.Resuming; OnEnterResuming(); })
|
|
.ExecuteOnExit(() => OnExitResuming())
|
|
.On(TaskTrigger.ResumingCompleted)
|
|
.Goto(ScriptTaskState.Running)
|
|
.Execute(() => { _currentState = ScriptTaskState.Running; OnEnterRunning(); })
|
|
.On(TaskTrigger.ErrorOccurred)
|
|
.Goto(ScriptTaskState.Error)
|
|
.Execute(() => { _currentState = ScriptTaskState.Error; OnEnterError(); });
|
|
|
|
// Stopping state
|
|
builder.In(ScriptTaskState.Stopping)
|
|
.ExecuteOnEntry(() => { _currentState = ScriptTaskState.Stopping; OnEnterStopping(); })
|
|
.ExecuteOnExit(() => OnExitStopping())
|
|
.On(TaskTrigger.StoppingCompleted)
|
|
.Goto(ScriptTaskState.Stopped)
|
|
.Execute(() => { _currentState = ScriptTaskState.Stopped; OnEnterStopped(); })
|
|
.On(TaskTrigger.ErrorOccurred)
|
|
.Goto(ScriptTaskState.Error)
|
|
.Execute(() => { _currentState = ScriptTaskState.Error; OnEnterError(); });
|
|
|
|
// Stopped state
|
|
builder.In(ScriptTaskState.Stopped)
|
|
.ExecuteOnEntry(() => { _currentState = ScriptTaskState.Stopped; OnEnterStopped(); })
|
|
.ExecuteOnExit(() => OnExitStopped())
|
|
.On(TaskTrigger.Start)
|
|
.Goto(ScriptTaskState.Running)
|
|
.Execute(() => { _currentState = ScriptTaskState.Running; OnEnterRunning(); })
|
|
.On(TaskTrigger.Dispose)
|
|
.Execute(() => OnDispose());
|
|
|
|
// Error state
|
|
builder.In(ScriptTaskState.Error)
|
|
.ExecuteOnEntry(() => { _currentState = ScriptTaskState.Error; OnEnterError(); })
|
|
.ExecuteOnExit(() => OnExitError())
|
|
.On(TaskTrigger.Start)
|
|
.Goto(ScriptTaskState.Running)
|
|
.Execute(() => { _currentState = ScriptTaskState.Running; OnEnterRunning(); })
|
|
.On(TaskTrigger.Dispose)
|
|
.Execute(() => OnDispose());
|
|
}
|
|
|
|
// Public methods to fire triggers
|
|
public void Start()
|
|
{
|
|
_stateMachine.Fire(TaskTrigger.Start);
|
|
}
|
|
|
|
public void Pause()
|
|
{
|
|
_stateMachine.Fire(TaskTrigger.Pause);
|
|
}
|
|
|
|
// State property
|
|
public ScriptTaskState State => _currentState;
|
|
}
|
|
```
|
|
|
|
## 🔑 Key API Methods / Các Method API Chính
|
|
|
|
### StateMachineDefinitionBuilder Methods
|
|
|
|
| Method | Description | Example |
|
|
|--------|-------------|---------|
|
|
| `In(TState state)` | Bắt đầu cấu hình một state | `builder.In(TaskState.Running)` |
|
|
| `On(TTrigger trigger)` | Định nghĩa trigger cho transition | `.On(TaskTrigger.Start)` |
|
|
| `Goto(TState state)` | Chỉ định state đích | `.Goto(TaskState.Running)` |
|
|
| `Execute(Action action)` | Thực thi action khi transition | `.Execute(() => OnEnterRunning())` |
|
|
| `ExecuteOnEntry(Action action)` | Thực thi khi vào state | `.ExecuteOnEntry(() => OnEnterRunning())` |
|
|
| `ExecuteOnExit(Action action)` | Thực thi khi ra khỏi state | `.ExecuteOnExit(() => OnExitRunning())` |
|
|
| `WithInitialState(TState state)` | Đặt initial state | `.WithInitialState(TaskState.Idle)` |
|
|
| `Build()` | Build definition | `.Build()` |
|
|
| `CreatePassiveStateMachine()` | Tạo passive state machine | `.CreatePassiveStateMachine()` |
|
|
|
|
### PassiveStateMachine Methods
|
|
|
|
| Method | Description | Example |
|
|
|--------|-------------|---------|
|
|
| `Start()` | Khởi động state machine | `stateMachine.Start()` |
|
|
| `Fire(TTrigger trigger)` | Fire một trigger | `stateMachine.Fire(TaskTrigger.Start)` |
|
|
| `Stop()` | Dừng state machine | `stateMachine.Stop()` |
|
|
|
|
## ⚠️ Important Notes / Lưu Ý Quan trọng
|
|
|
|
### 1. State Tracking / Theo dõi State
|
|
|
|
**Problem**: `PassiveStateMachine` không có property `CurrentState` hoặc `CurrentStateId` để đọc state hiện tại.
|
|
|
|
**Solution**: Sử dụng một field riêng để track state và update nó trong các transition handlers:
|
|
|
|
```csharp
|
|
private ScriptTaskState _currentState;
|
|
|
|
// Update trong ExecuteOnEntry hoặc Execute
|
|
builder.In(TaskState.Running)
|
|
.ExecuteOnEntry(() => { _currentState = TaskState.Running; OnEnterRunning(); });
|
|
|
|
// Hoặc trong Execute của transition
|
|
.On(TaskTrigger.Start)
|
|
.Goto(TaskState.Running)
|
|
.Execute(() => { _currentState = TaskState.Running; OnEnterRunning(); });
|
|
```
|
|
|
|
### 2. Entry/Exit Actions Order / Thứ tự Entry/Exit Actions
|
|
|
|
**Critical**: `ExecuteOnEntry()` và `ExecuteOnExit()` **PHẢI** được gọi **TRƯỚC** các `On()` calls:
|
|
|
|
```csharp
|
|
// ✅ CORRECT
|
|
builder.In(TaskState.Running)
|
|
.ExecuteOnEntry(() => OnEnterRunning()) // First
|
|
.ExecuteOnExit(() => OnExitRunning()) // Second
|
|
.On(TaskTrigger.Pause) // Then transitions
|
|
.Goto(TaskState.Pausing);
|
|
|
|
// ❌ WRONG - Will cause compile error
|
|
builder.In(TaskState.Running)
|
|
.On(TaskTrigger.Pause)
|
|
.Goto(TaskState.Pausing)
|
|
.ExecuteOnEntry(() => OnEnterRunning()); // Error!
|
|
```
|
|
|
|
### 3. State Machine Lifecycle / Vòng đời State Machine
|
|
|
|
1. **Create**: Build definition và create state machine
|
|
2. **Start**: Gọi `Start()` để khởi động (state machine sẽ ở initial state)
|
|
3. **Fire Triggers**: Sử dụng `Fire()` để trigger transitions
|
|
4. **Stop**: Gọi `Stop()` khi không cần dùng nữa
|
|
|
|
```csharp
|
|
var stateMachine = builder
|
|
.WithInitialState(TaskState.Idle)
|
|
.Build()
|
|
.CreatePassiveStateMachine();
|
|
|
|
stateMachine.Start(); // State machine is now in Idle state
|
|
|
|
// Later...
|
|
stateMachine.Fire(TaskTrigger.Start); // Transition to Running
|
|
|
|
// When done...
|
|
stateMachine.Stop();
|
|
```
|
|
|
|
### 4. Error Handling / Xử lý Lỗi
|
|
|
|
State machine sẽ throw exception nếu:
|
|
- Fire trigger không hợp lệ (không có transition từ state hiện tại)
|
|
- State machine chưa được start
|
|
- State machine đã được stop
|
|
|
|
```csharp
|
|
try
|
|
{
|
|
_stateMachine.Fire(TaskTrigger.Start);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError($"Failed to fire trigger: {ex.Message}");
|
|
throw;
|
|
}
|
|
```
|
|
|
|
### 5. Thread Safety / An toàn Luồng
|
|
|
|
`PassiveStateMachine` **không thread-safe**. Nếu cần thread safety, sử dụng lock:
|
|
|
|
```csharp
|
|
private readonly object _lockObject = new();
|
|
|
|
public void Start()
|
|
{
|
|
lock (_lockObject)
|
|
{
|
|
_stateMachine.Fire(TaskTrigger.Start);
|
|
}
|
|
}
|
|
```
|
|
|
|
## 🎨 Best Practices / Thực hành Tốt nhất
|
|
|
|
### 1. Separate Configuration Method / Tách Method Cấu hình
|
|
|
|
Tách logic cấu hình state machine ra method riêng để code dễ đọc:
|
|
|
|
```csharp
|
|
private void ConfigureStateMachine(StateMachineDefinitionBuilder<TState, TTrigger> builder)
|
|
{
|
|
// All configuration here
|
|
}
|
|
```
|
|
|
|
### 2. Consistent State Tracking / Theo dõi State Nhất quán
|
|
|
|
Luôn update `_currentState` trong cả `ExecuteOnEntry()` và `Execute()` của transitions để đảm bảo consistency:
|
|
|
|
```csharp
|
|
builder.In(TaskState.Running)
|
|
.ExecuteOnEntry(() => { _currentState = TaskState.Running; OnEnterRunning(); })
|
|
.On(TaskTrigger.Start)
|
|
.Goto(TaskState.Running)
|
|
.Execute(() => { _currentState = TaskState.Running; OnEnterRunning(); });
|
|
```
|
|
|
|
### 3. Use Descriptive Trigger Names / Sử dụng Tên Trigger Mô tả
|
|
|
|
Đặt tên trigger rõ ràng để dễ hiểu:
|
|
|
|
```csharp
|
|
// ✅ Good
|
|
TaskTrigger.PausingCompleted
|
|
TaskTrigger.ResumingCompleted
|
|
TaskTrigger.ErrorOccurred
|
|
|
|
// ❌ Bad
|
|
TaskTrigger.Done
|
|
TaskTrigger.Ok
|
|
TaskTrigger.Err
|
|
```
|
|
|
|
### 4. Document State Transitions / Tài liệu hóa State Transitions
|
|
|
|
Sử dụng comments để giải thích logic:
|
|
|
|
```csharp
|
|
// Idle → Running: Start task
|
|
builder.In(TaskState.Idle)
|
|
.On(TaskTrigger.Start)
|
|
.Goto(TaskState.Running);
|
|
|
|
// Running → Pausing → Paused: Pause task (wait for current execution)
|
|
builder.In(TaskState.Running)
|
|
.On(TaskTrigger.Pause)
|
|
.Goto(TaskState.Pausing);
|
|
```
|
|
|
|
## 🔄 State Transition Patterns / Mẫu Chuyển đổi State
|
|
|
|
### Pattern 1: Simple Transition / Chuyển đổi Đơn giản
|
|
|
|
```csharp
|
|
builder.In(StateA)
|
|
.On(TriggerX)
|
|
.Goto(StateB)
|
|
.Execute(() => OnEnterStateB());
|
|
```
|
|
|
|
### Pattern 2: Transition with Entry/Exit / Chuyển đổi với Entry/Exit
|
|
|
|
```csharp
|
|
builder.In(StateA)
|
|
.ExecuteOnEntry(() => OnEnterStateA())
|
|
.ExecuteOnExit(() => OnExitStateA())
|
|
.On(TriggerX)
|
|
.Goto(StateB)
|
|
.Execute(() => OnEnterStateB());
|
|
```
|
|
|
|
### Pattern 3: Intermediate State / State Trung gian
|
|
|
|
Sử dụng intermediate state cho async operations:
|
|
|
|
```csharp
|
|
// Start → Intermediate → Final
|
|
builder.In(StateA)
|
|
.On(TriggerStart)
|
|
.Goto(StateIntermediate)
|
|
.Execute(() => StartAsyncOperation());
|
|
|
|
builder.In(StateIntermediate)
|
|
.ExecuteOnEntry(() => OnEnterIntermediate())
|
|
.On(TriggerCompleted)
|
|
.Goto(StateFinal)
|
|
.Execute(() => OnEnterFinal());
|
|
```
|
|
|
|
## 🐛 Common Pitfalls / Lỗi Thường gặp
|
|
|
|
### 1. Forgetting to Start / Quên Start
|
|
|
|
```csharp
|
|
// ❌ Wrong
|
|
var stateMachine = builder.Build().CreatePassiveStateMachine();
|
|
stateMachine.Fire(TaskTrigger.Start); // Exception!
|
|
|
|
// ✅ Correct
|
|
var stateMachine = builder.Build().CreatePassiveStateMachine();
|
|
stateMachine.Start();
|
|
stateMachine.Fire(TaskTrigger.Start);
|
|
```
|
|
|
|
### 2. Wrong Order of ExecuteOnEntry / Thứ tự ExecuteOnEntry Sai
|
|
|
|
```csharp
|
|
// ❌ Wrong - Compile error
|
|
builder.In(StateA)
|
|
.On(TriggerX)
|
|
.Goto(StateB)
|
|
.ExecuteOnEntry(() => OnEnterStateB());
|
|
|
|
// ✅ Correct
|
|
builder.In(StateA)
|
|
.ExecuteOnEntry(() => OnEnterStateA())
|
|
.On(TriggerX)
|
|
.Goto(StateB)
|
|
.Execute(() => OnEnterStateB());
|
|
```
|
|
|
|
### 3. Not Tracking State / Không Theo dõi State
|
|
|
|
```csharp
|
|
// ❌ Wrong - No way to read current state
|
|
public TaskState State => _stateMachine.CurrentState; // Property doesn't exist!
|
|
|
|
// ✅ Correct
|
|
private TaskState _currentState;
|
|
public TaskState State => _currentState;
|
|
|
|
// Update in transitions
|
|
.Execute(() => { _currentState = TaskState.Running; OnEnterRunning(); });
|
|
```
|
|
|
|
### 4. Fire Invalid Trigger / Fire Trigger Không hợp lệ
|
|
|
|
```csharp
|
|
// ❌ Wrong - Will throw exception if no transition defined
|
|
stateMachine.Fire(TaskTrigger.Start); // If current state doesn't allow Start
|
|
|
|
// ✅ Correct - Check state first or handle exception
|
|
if (_currentState == TaskState.Idle || _currentState == TaskState.Stopped)
|
|
{
|
|
stateMachine.Fire(TaskTrigger.Start);
|
|
}
|
|
```
|
|
|
|
## 📚 Related Documents / Tài liệu Liên quan
|
|
|
|
- [StateMachine Design](../ScriptEngine/StateMachine_Design.md) - State machine architecture cho ScriptEngine
|
|
- [ScriptTask Implementation](../../srcs/RobotNet10/Commons/RobotNet10.ScriptEngine/Models/ScriptTask.cs) - Reference implementation
|
|
- [Appccelerate.StateMachine Documentation](https://github.com/appccelerate/statemachine) - Official documentation
|
|
|
|
## 🔗 Example Usage in RobotNet10 / Ví dụ Sử dụng trong RobotNet10
|
|
|
|
State machine được sử dụng trong:
|
|
|
|
1. **ScriptTask** (`ScriptTask.cs`) - Task state management
|
|
2. **ScriptMissionInstance** (planned) - Mission instance state management
|
|
3. **ScriptEngineManager** (planned) - Engine manager state management
|
|
|
|
Xem implementation chi tiết trong các file này để hiểu cách sử dụng trong context thực tế.
|
|
|
|
---
|
|
|
|
**Last Updated**: 2025-11-13
|
|
**Status**: Usage Guide for Appccelerate.StateMachine
|
|
**Version**: 1.0
|
|
**Library Version**: 6.0.0
|
|
|