Skip to content

Commit d72faa9

Browse files
authored
Merge pull request #90 from CedaryCat/develop
docs: document Command System V2 and Atelier REPL
2 parents e369bf8 + dc18df0 commit d72faa9

7 files changed

Lines changed: 886 additions & 4 deletions

File tree

GitVersion.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ commit-date-format: 'yyyy-MM-dd'
2323
# Ignore merge commits (except those that should trigger releases)
2424
merge-message-formats: '^Merge (branch|tag|pull request)'
2525

26-
next-version: 'v0.2.1'
26+
next-version: 'v0.3.1'
2727

2828
# Branch-specific configuration
2929
branches:

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ These are reachable directions, even though the launcher does not currently ship
7777
| 🛡 **Bundled TShock port** | Ships with a USP-adapted TShock baseline ready for use |
7878
| 💻 **Per-context console isolation** | Independent, auto-reconnecting console I/O windows for each world context, plus semantic readline prompts and live status bars |
7979
| 🚀 **RID-targeted publishing** | Publisher produces reproducible, runtime-specific directory trees |
80+
|**Command System V2** | A brand-new declarative command framework — 200+ TShock commands already migrated, with smart context-aware completion built in. Plugins define commands as structured declarations; the framework handles binding, permissions, output, and audit logging across terminal, player, and REST endpoints |
81+
| 🧪 **Atelier REPL** | A Roslyn-powered C# workspace running inside the live runtime. Write and run code against real server state, get IDE-quality completion and diagnostics, and iterate without restarting — a proper workbench for operators and developers alike |
8082

8183
---
8284

@@ -555,6 +557,8 @@ graph LR
555557
| **Config registration** | Configs stored in `config/<PluginName>/`, supports auto-reload (`TriggerReloadOnExternalChange(true)`) |
556558
| **Collectible contexts** | `ModuleLoadContext` enables unloadable plugin domains |
557559

560+
Command System V2 is the recommended way to expose commands from plugins. Controllers are declared with attributes, and the framework handles discovery, endpoint binding, parameter parsing, and permission checks automatically. The same declaration also drives completion candidates, help text generation, and audit logging — so there is no separate usage string to maintain.
561+
558562
→ Full guide: [Plugin Development Guide](./docs/dev-plugin.md)
559563

560564
---
@@ -596,6 +600,8 @@ This table reflects the currently maintained/documented packaging targets, not e
596600
| `linux-arm` | ⚠️ Partial support / needs manual verification |
597601
| `osx-x64` | ✅ Supported |
598602

603+
If you want to inspect or script against a running world without writing a full plugin, Atelier REPL gives you a Roslyn workspace attached to the live runtime. You can run persistent C# sessions, query server state, call plugin APIs, and run background tasks — all without restarting. See [docs/dev-overview.md](./docs/dev-overview.md#28-atelier-repl) for session setup and meta-command reference.
604+
599605
---
600606

601607
<a id="resources"></a>

docs/README.zh-cn.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ UnifierTSL 把 [OTAPI Unified Server Process](https://github.com/CedaryCat/OTAPI
7777
| 🛡 **内置 TShock 移植基线** | 内置适配 USP 的 TShock 基线,开箱可用 |
7878
| 💻 **上下文级控制台隔离** | 默认为每个世界实例提供独立、自动重连的控制台窗口 IO,以及语义化 readline 提示与实时状态栏 |
7979
| 🚀 **按 RID 发布** | Publisher 生成可复现、面向目标运行时的目录结构 |
80+
|**命令系统 V2** | 全新声明式命令体系——200+ 条 TShock 命令已完成迁移,并内置智能上下文感知补全。插件通过属性标注声明命令结构,框架统一处理绑定、权限、输出和审计日志,覆盖终端、玩家和 REST 三类入口 |
81+
| 🧪 **Atelier REPL** | 直接运行在运行时内部的 Roslyn C# 工作台。针对真实服务器状态编写并执行代码,享有媲美 IDE 的补全与诊断体验,无需重启即可迭代——运维和开发都能用得上的正经工具 |
8082

8183
---
8284

@@ -556,6 +558,8 @@ graph LR
556558
| **配置注册** | 配置存放在 `config/<PluginName>/`,支持自动重载(`TriggerReloadOnExternalChange(true)`|
557559
| **可回收上下文** | `ModuleLoadContext` 支持可卸载的插件域 |
558560

561+
命令系统 V2 是插件暴露命令的推荐方式。控制器通过属性标注声明,框架负责发现、端点绑定、参数解析和权限检查,同一份声明还会自动派生出补全候选、帮助文本和审计日志,不需要单独维护 usage 字符串。完整的 API 参考和代码示例可以在插件开发指南里找到。
562+
559563
→ 完整指南:[插件开发指南](./dev-plugin.zh-cn.md)
560564

561565
---
@@ -597,6 +601,8 @@ dotnet run --project src/UnifierTSL.Publisher/UnifierTSL.Publisher.csproj -- \
597601
| `linux-arm` | ⚠️ 部分支持 / 仍需人工验证 |
598602
| `osx-x64` | ✅ 支持 |
599603

604+
如果你想在不写完整插件的情况下探索或操作运行中的世界,Atelier REPL 提供了一个直接附着在运行时上的 Roslyn 工作台。你可以在持久会话里逐步构建脚本、查询服务器状态、调用插件 API,也可以把长时间运行的操作放到后台执行——整个过程不需要重启。会话配置和元命令参考可以在 [dev-overview.zh-cn.md](./dev-overview.zh-cn.md#28-atelier-repl) 里找到。
605+
600606
---
601607

602608
<a id="resources"></a>

docs/dev-overview.md

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Welcome! This doc walks you through how the UnifierTSL runtime is put together o
1515
- [2.4 Networking & Coordinator](#24-networking--coordinator)
1616
- [2.5 Logging Infrastructure](#25-logging-infrastructure)
1717
- [2.6 Configuration Service](#26-configuration-service)
18+
- [2.7 Command System V2](#27-command-system-v2)
19+
- [2.8 Atelier REPL](#28-atelier-repl)
1820
- [3. USP Integration Points](#3-usp-integration-points)
1921
- [4. Public API Surface](#4-public-api-surface)
2022
- [4.1 Facade (`UnifierApi`)](#41-facade-unifierapi)
@@ -55,6 +57,8 @@ Welcome! This doc walks you through how the UnifierTSL runtime is put together o
5557
- `ModuleAssemblyLoader` – takes care of module staging, dependency extraction, collectible load contexts, and unload order.
5658
- `EventHub` – the central event registry that bridges MonoMod detours into priority-sorted event pipelines.
5759
- `Logging` subsystem – lightweight, allocation-friendly logging with metadata injection and pluggable writers.
60+
- `CommandSystem` – the declarative command framework: catalog, endpoint compiler, dispatcher, Prompt projection, and audit integration.
61+
- `AtelierPlugin` + `ReplSession` – the Roslyn-based interactive REPL, providing persistent sessions, real-time diagnostics, code completion, and a Surface-based windowed UI.
5862

5963
## 2. Core Services & Subsystems
6064

@@ -1495,7 +1499,171 @@ For more logging examples, see `src/Plugins/TShockAPI/TShock.cs` and `src/Unifie
14951499
- Startup precedence for launcher settings is `config/config.json` -> CLI overrides -> interactive fallback for missing port/password, and the effective startup snapshot is persisted back to `config/config.json`. After startup, edits to `config/config.json` apply to the launcher settings that support reload.
14961500
- Root-config hot reload applies `launcher.serverPassword`, `launcher.joinServer`, additive `launcher.autoStartServers`, `launcher.listenPort` (via listener rebind), `launcher.colorfulConsoleStatus`, and `launcher.consoleStatus`.
14971501

1498-
## 3. USP Integration Points
1502+
<a id="27-command-system-v2"></a>
1503+
### 2.7 Command System V2
1504+
1505+
The command system v2 replaces the traditional string-callback model with a declarative, attribute-driven framework. Controllers are `static` classes; actions are static methods. The framework discovers these at install time, compiles them into endpoint-specific catalogs, and handles dispatch, parameter binding, permission checks, and output formatting — all from the same set of declarations.
1506+
1507+
<details>
1508+
<summary><strong>Expand Command System V2 implementation deep dive</strong></summary>
1509+
1510+
#### Architecture Overview
1511+
1512+
The system has five distinct layers, each handling a specific concern:
1513+
1514+
**Discovery layer** (`src/UnifierTSL/Commanding`): `CommandSystemDiscovery` reflects over controller types to build a `CommandCatalog` — an immutable snapshot of all registered commands, their action paths, parameter signatures, and metadata. Reflection only happens at install time; runtime dispatch never touches it again.
1515+
1516+
**Compilation layer** (`src/UnifierTSL/Commanding/Endpoints`): `CommandEndpointCatalogCompiler` takes the generic catalog and produces endpoint-specific bindings. Each endpoint type (terminal, TShock player, REST) has its own context requirements. Actions that don't satisfy an endpoint's constraints are excluded from that endpoint's catalog at compile time, not at dispatch time.
1517+
1518+
**Dispatch layer** (`src/UnifierTSL/Commanding/Execution`): `CommandDispatchCoordinator` receives raw input, tokenizes it via `CommandLineLexer`, routes to the matching root and action, runs guard checks, binds parameters, invokes the action, and hands the `CommandOutcome` to the appropriate outcome writer.
1519+
1520+
**Prompt layer** (`src/UnifierTSL/Commanding/Prompting`): `CommandPrompting` projects endpoint bindings into `PromptAlternativeSpec` objects. The same command definition that drives execution also drives completion candidates, semantic highlighting, and parameter hints.
1521+
1522+
**TShock layer** (`src/Plugins/TShockAPI/Commanding`): Adds TShock-specific endpoint types, permission model, domain binders (player, group, region, warp, etc.), and audit log integration. The core layer stays generic; TShock semantics live entirely in the plugin. Importantly, this is an integration and extension of Command System V2 rather than a separate parallel stack, so TShock-oriented plugins can usually build directly on that existing surface instead of inventing their own command bridge.
1523+
1524+
In practice, the UTSL-TS command architecture is split by ownership:
1525+
1526+
- **UTSL core owns the neutral command engine**: discovery, catalog building, endpoint compilation, dispatch, generic binding rules, and prompt projection.
1527+
- **TShock owns the game/admin-facing adaptation**: player/REST-facing endpoints, TShock permission semantics, domain binders, and TShock-specific outcome/audit behavior.
1528+
- **Plugins choose the highest-level surface that already matches their needs**: if you are building a TShock-style admin/gameplay command, you usually attach at the TShock layer instead of re-expressing those same semantics from scratch over raw V2 primitives.
1529+
1530+
#### Data Flow
1531+
1532+
```
1533+
CommandSystem.Install(configure)
1534+
1535+
CommandSystemDiscovery → CommandCatalog (immutable snapshot)
1536+
1537+
CommandEndpointCatalogCompiler → per-endpoint root/action bindings
1538+
1539+
CommandPrompting → PromptAlternativeSpec (completion / highlighting)
1540+
1541+
[User input arrives]
1542+
1543+
CommandDispatchCoordinator
1544+
1545+
CommandLineLexer: tokenize → prefix, root, args
1546+
1547+
CommandEndpointDispatcher: path match → guard checks → parameter binding
1548+
1549+
Action invoked → CommandOutcome
1550+
1551+
OutcomeWriter: receipt + attachment + log → terminal / player / REST
1552+
```
1553+
1554+
#### Immutable Catalog and Atomic Swap
1555+
1556+
`CommandSystem` maintains a registration dictionary. Each install or uninstall rebuilds `CommandSystemState` under the registration lock, then replaces the current snapshot. External readers consume that snapshot through `Volatile.Read`, so they always see a consistent state. Registration IDs keep ordering stable across rebuilds, which matters for help text and completion candidate stability.
1557+
1558+
`ValidateConflicts` runs during install to catch duplicate root names and aliases before any user can hit the ambiguity at runtime.
1559+
1560+
#### Static Controllers and the No-State Rule
1561+
1562+
`CommandSystemDiscovery` requires controllers to be `static` classes with static methods. This is a deliberate constraint: controllers are declaration containers, not service instances. Runtime state flows in through injected parameters (`CommandInvocationContext`, `CancellationToken`, and `[FromAmbientContext]` values such as `ServerContext` or `TSExecutionContext`). This makes the catalog easy to snapshot, rebuild, and unload, and it prevents plugins from smuggling mutable instance state into the command lifecycle.
1563+
1564+
#### Endpoint Binding Validation
1565+
1566+
The endpoint compiler validates parameter constraints at compile time. If an action requires a server-scoped ambient value such as `[FromAmbientContext] ServerContext`, but the endpoint can't provide one, the compile fails at install time rather than at dispatch time. This catches mismatches before any user can trigger them.
1567+
1568+
#### Mismatch Handlers
1569+
1570+
Each controller can have at most one `[MismatchHandler]`. It fires when the root matches but the action path or parameters don't. Mismatch handlers can only consume injected values — they can't declare user-bound parameters. This prevents error handling from becoming a second command parser.
1571+
1572+
#### Parameter Binding Sources
1573+
1574+
| Source | Description |
1575+
|--------|-------------|
1576+
| User tokens | Positional arguments from the command line |
1577+
| Remaining text / args | Everything after the last bound token, as string or `string[]` |
1578+
| Flags | Named options extracted from any position in the token list |
1579+
| Invocation context | `CommandInvocationContext` |
1580+
| Cancellation token | For long-running or background operations |
1581+
| Ambient context | `[FromAmbientContext]` values such as `ServerContext`, `TSExecutionContext`, and background activity feedback objects |
1582+
1583+
Scalar types (`string`, `bool`, `int`, numeric types, `enum`) bind directly from tokens. Complex types use `CommandBindingAttribute` subclasses. TShock adds domain-specific binders for `TSPlayer`, `Group`, `Region`, `Warp`, `UserAccount`, and more. `CommandInvocationContext` and `CancellationToken` are recognized implicitly; other runtime values come from the ambient execution context when explicitly marked.
1584+
1585+
</details>
1586+
1587+
#### Best Practices
1588+
1589+
1. **Keep controllers stateless** — inject context through parameters, not fields
1590+
2. **Declare permissions on TShock actions** — the dispatcher enforces them before binding
1591+
3. **Prefer the TShock integration when that's your target surface** — plugins like `CommandTeleport` can use the TShock-facing V2 facilities directly instead of building a second command-access layer
1592+
4. **Use implicit bindings for common domain types** — register them once in the binding registry
1593+
5. **Add a mismatch handler** — it's the user-facing error surface for your command tree
1594+
6. **Let the catalog drive help text** — don't maintain separate usage strings
1595+
7. **Validate at install time** — declare context requirements so the compiler catches mismatches early
1596+
1597+
<a id="28-atelier-repl"></a>
1598+
### 2.8 Atelier REPL
1599+
1600+
Atelier is a Roslyn-based interactive C# workspace embedded in the Unifier runtime. It goes well beyond "run a script" — it provides persistent sessions with incremental compilation, real-time diagnostics, code completion, signature help, semantic highlighting, virtual bracket pairing, console output redirection, background task tracking, and a Surface-based windowed UI.
1601+
1602+
<details>
1603+
<summary><strong>Expand Atelier REPL implementation deep dive</strong></summary>
1604+
1605+
#### Session Model
1606+
1607+
`ReplSession` (`src/Plugins/Atelier/Session`) maintains a Roslyn `ScriptState` that grows incrementally with each submission. Two execution modes are available:
1608+
1609+
- **Persistent submit**: compiles on top of the current state and updates session state. Variables, imports, and definitions persist across submissions.
1610+
- **Transient run**: executes against the current state but discards the result. Useful for one-off queries or validation without polluting session state.
1611+
1612+
Sessions can target the launcher context or a specific running server. The target determines which globals are in scope — every session gets `Launcher`, `Log`, `HostLabel`, `TargetLabel`, `Cancellation`, `PendingTasks`, and `LastTask`, while server-targeted sessions additionally expose `Server`, a wrapper around the target `ServerContext` with dispatcher, peer, and snapshot helpers.
1613+
1614+
#### Roslyn Workspace Integration
1615+
1616+
The REPL maintains a Roslyn workspace alongside the execution state (`src/Plugins/Atelier/Session/Roslyn`). The workspace tracks the current document and provides language services:
1617+
1618+
- **Diagnostics**: real-time error/warning markers as you type, before you submit
1619+
- **Completion**: context-aware candidate lists triggered by `.` or manual request
1620+
- **Signature help**: method overload display when you open a call's parenthesis
1621+
- **Semantic highlighting**: type-aware coloring beyond syntax
1622+
1623+
A warmup pass runs after initialization to pre-load Roslyn's core assemblies, reducing first-use latency.
1624+
1625+
#### Draft vs. Source
1626+
1627+
The REPL maintains two text buffers:
1628+
1629+
- **Draft**: what the user sees, including virtual bracket hints and other visual aids
1630+
- **Source**: the actual source text used for compilation and execution
1631+
1632+
This separation lets the UI provide rich visual feedback without corrupting the code that Roslyn actually compiles.
1633+
1634+
#### Surface Window Lifecycle
1635+
1636+
The REPL window uses `ISurfaceSession` and an interaction scope (`src/Plugins/Atelier/Presentation/Window`). On close, the system terminates the scope, unsubscribes events, unbinds the console, cancels pending reads, drains the command queue, cancels processing, disposes the surface session, and finally releases the `ReplSession`. This sequence ensures clean resource release with no dangling references.
1637+
1638+
#### Plugin Assembly References
1639+
1640+
The REPL can reference currently loaded plugin assemblies, giving scripts direct access to plugin public APIs. When a referenced assembly changes (plugin reload or unload), the session is marked invalid. Users see a prompt explaining why and can open a fresh session against the updated assemblies.
1641+
1642+
#### Meta-Commands
1643+
1644+
Meta-commands start with `:` and control the REPL itself rather than executing C# code:
1645+
1646+
| Command | Effect |
1647+
|---------|--------|
1648+
| `:help` | Show available meta-commands |
1649+
| `:reset` | Reset session state |
1650+
| `:clear` | Clear the output area |
1651+
| `:imports` | List baseline/effective imports and reference paths |
1652+
| `:target` | Show the current target and invocation host |
1653+
| `:paste [on|off]` | Toggle paste mode for multi-line submissions |
1654+
| `:transient <code>` | Run transient code without committing it |
1655+
1656+
Background work is tracked through `PendingTasks` and `LastTask`; there is no separate `:jobs` or `:cancel` meta-command in the current implementation.
1657+
1658+
</details>
1659+
1660+
#### Best Practices
1661+
1662+
1. **Use transient mode for queries** — it doesn't pollute session state
1663+
2. **Target the right context** — launcher context for global state, server context for world-specific operations
1664+
3. **Watch for session invalidation** — plugin reloads will mark your session stale
1665+
4. **Use background tasks for long operations** — the REPL stays responsive while they run
1666+
5. **Prefer `:reset` over closing and reopening** — it's faster when you just want a clean slate
14991667

15001668
- `ServerContext` inherits USP's `RootContext`, plugging Unifier services into the context (custom console, packet receiver, logging metadata). Everything that touches Terraria world/game state goes through this context.
15011669
- The networking patcher (`UnifiedNetworkPatcher`) detours `NetplaySystemContext` functions to share buffers and coordinate send/receive paths across servers.

0 commit comments

Comments
 (0)