You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -77,6 +77,8 @@ These are reachable directions, even though the launcher does not currently ship
77
77
| 🛡 **Bundled TShock port**| Ships with a USP-adapted TShock baseline ready for use |
78
78
| 💻 **Per-context console isolation**| Independent, auto-reconnecting console I/O windows for each world context, plus semantic readline prompts and live status bars |
| ⚡ **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 |
80
82
81
83
---
82
84
@@ -555,6 +557,8 @@ graph LR
555
557
|**Config registration**| Configs stored in `config/<PluginName>/`, supports auto-reload (`TriggerReloadOnExternalChange(true)`) |
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
+
558
562
→ Full guide: [Plugin Development Guide](./docs/dev-plugin.md)
559
563
560
564
---
@@ -596,6 +600,8 @@ This table reflects the currently maintained/documented packaging targets, not e
596
600
|`linux-arm`| ⚠️ Partial support / needs manual verification |
597
601
|`osx-x64`| ✅ Supported |
598
602
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.
@@ -55,6 +57,8 @@ Welcome! This doc walks you through how the UnifierTSL runtime is put together o
55
57
-`ModuleAssemblyLoader` – takes care of module staging, dependency extraction, collectible load contexts, and unload order.
56
58
-`EventHub` – the central event registry that bridges MonoMod detours into priority-sorted event pipelines.
57
59
-`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.
58
62
59
63
## 2. Core Services & Subsystems
60
64
@@ -1495,7 +1499,171 @@ For more logging examples, see `src/Plugins/TShockAPI/TShock.cs` and `src/Unifie
1495
1499
- 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.
1496
1500
- Root-config hot reload applies `launcher.serverPassword`, `launcher.joinServer`, additive `launcher.autoStartServers`, `launcher.listenPort` (via listener rebind), `launcher.colorfulConsoleStatus`, and `launcher.consoleStatus`.
1497
1501
1498
-
## 3. USP Integration Points
1502
+
<aid="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.
`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
+
<aid="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
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
1499
1667
1500
1668
-`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.
1501
1669
- The networking patcher (`UnifiedNetworkPatcher`) detours `NetplaySystemContext` functions to share buffers and coordinate send/receive paths across servers.
0 commit comments