Skip to content

Commit 369bd40

Browse files
committed
doc: add README & dev-guide
1 parent 9f99dd0 commit 369bd40

File tree

2 files changed

+370
-0
lines changed

2 files changed

+370
-0
lines changed

README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# OTAPI Unified Server Process (USP)
2+
3+
OTAPI Unified Server Process (USP) is a re‑engineered Terraria server core that converts global static state into per‑server context. This lets you run multiple fully isolated server instances in a single process, while exposing a cleaner, safer API for networking, world data, and gameplay systems.
4+
5+
USP compiles a patched Terraria core into two assemblies:
6+
- OTAPI.dll — the rewritten core with context‑bound systems
7+
- OTAPI.Runtime.dll — runtime hooks (On.*) generated for extension points
8+
9+
10+
## Install via NuGet (recommended)
11+
Most consumers can add USP directly from NuGet, which provides patched `OTAPI.dll` and `OTAPI.Runtime.dll` binaries alongside XML docs and symbols.
12+
13+
```
14+
dotnet add package OTAPI.USP
15+
```
16+
17+
You only need a local source build when experimenting with patches or contributing upstream changes.
18+
19+
20+
## Why USP
21+
- Multiple servers, one process: Each server instance owns its own RootContext. No shared singletons; no cross‑instance bleed‑through.
22+
- Safer APIs: Critical systems (Main, Netplay, NetMessage, Collision, etc.) are accessed through a context rather than static globals.
23+
- Protocol ergonomics: TrProtocol provides strong‑typed packet models and a source generator for fast serializers.
24+
- World data revamp: TileProvider replaces legacy ITile/Tile with TileData/RefTileData and TileCollection for predictable references and better memory access patterns.
25+
- Server‑first trimming: Non‑server code paths are aggressively removed to simplify execution on dedicated servers.
26+
27+
28+
## Quick Start
29+
1) Restore and build
30+
- `dotnet restore src/OTAPI.UnifiedServerProcess.sln`
31+
- `dotnet build src/OTAPI.UnifiedServerProcess.sln -c Release`
32+
33+
2) Produce OTAPI.dll / OTAPI.Runtime.dll
34+
- `dotnet run -c Debug -p src/OTAPI.UnifiedServerProcess`
35+
- Output: `src/OTAPI.UnifiedServerProcess/bin/<Config>/net9.0/output/`
36+
37+
3) Try the Global Network demo (multi‑server in one process)
38+
- `dotnet run -c Debug -p src/OTAPI.UnifiedServerProcess.GlobalNetwork`
39+
- Entrypoint: `src/OTAPI.UnifiedServerProcess.GlobalNetwork/Program.cs`
40+
- The demo starts two servers and a simple router on one port.
41+
42+
43+
## Architecture at a Glance
44+
- RootContext: Per‑server root that holds the instance‑bound systems you previously accessed as statics (e.g., Main, Netplay, NetMessage, Collision). See `src/OTAPI.UnifiedServerProcess/Mods/RootContext.cs` and `src/OTAPI.UnifiedServerProcess/Core/PatchingLogic.cs`.
45+
- Context‑bound systems: USP’s patching pass rewrites static state and call sites to live under the context. From a mod/plugin perspective you use `ctx.Main`, `ctx.Netplay`, `ctx.NetMessage`, `ctx.Collision`, etc., instead of global `Terraria.*` statics.
46+
- GlobalNetwork sample: Demonstrates sharing global sockets while routing per‑client processing to the correct server context. See `src/OTAPI.UnifiedServerProcess.GlobalNetwork/Network/Router.cs` and `src/OTAPI.UnifiedServerProcess.GlobalNetwork/Servers/ServerContext.cs`.
47+
- TrProtocol: Strong‑typed packet models under `src/TrProtocol/NetPackets/*` plus a source generator for fast (de)serialization.
48+
- TileProvider: Replaces ITile/Tile with TileData + RefTileData and a context‑aware TileCollection. See `src/OTAPI.UnifiedServerProcess/Mods/TileProviderMod.cs`.
49+
50+
51+
## Working with RootContext
52+
RootContext is the per‑server entry point. Derive from it (or use the provided `ServerContext`) to configure world metadata, networking, and gameplay systems per instance instead of touching static `Terraria.*` members.
53+
54+
```csharp
55+
public class MyServer : RootContext
56+
{
57+
public MyServer(string name, string worldPath) : base(name)
58+
{
59+
Main.ActiveWorldFileData = WorldFile.GetAllMetadata(worldPath, false);
60+
Netplay.ListenPort = -1; // let a global router own the port if needed
61+
}
62+
}
63+
64+
var ctx = new MyServer("World-A", "/path/to/World-A.wld");
65+
ctx.Console.WriteLine($"[{ctx.Name}] booting…");
66+
ctx.Main.gameMode = 3;
67+
```
68+
69+
Tile interactions stay performant by working with `TileCollection` and `RefTileData`:
70+
71+
```csharp
72+
var tiles = ctx.Main.tile;
73+
74+
for (int x = 0; x < ctx.Main.maxTilesX; x++)
75+
for (int y = 0; y < ctx.Main.maxTilesY; y++)
76+
{
77+
ref var tile = ref tiles[x, y];
78+
if (tile.active)
79+
{
80+
tile.type = 1;
81+
}
82+
}
83+
```
84+
85+
86+
## Why TSAPI/TShock Don’t Work on USP
87+
USP is not a drop‑in for the previous OTAPI stacks. Launchers like TSAPI and plugins like TShock assume a single global server (static state) and a specific set of hook signatures and types. USP breaks those assumptions by design:
88+
89+
- Static → Context: Most global singletons are rewritten into per‑server members. Code that calls static `Terraria.Main`, `Terraria.Netplay`, `Terraria.Collision`, etc., will not interact with the correct server unless rewritten to use the context (e.g., `ctx.Main`, `ctx.Netplay`).
90+
- Hook signatures changed: Runtime hooks now live on context‑scoped systems. Many On.* hooks include or imply a `root` (RootContext) and operate on instance members. Old hook points and Harmony patches targeting static methods often no longer match.
91+
- World/tile API changed: Legacy ITile/Tile arrays are replaced by TileData/RefTileData and TileCollection. Any code that takes hard dependencies on the old tile storage or expects reference semantics from the old types will break.
92+
- Network paths adjusted: USP’s pruning and routing alter how `NetMessage` and `Netplay` progress work is scheduled and dispatched. Code that assumed vanilla update order or directly touched static buffers will not function correctly.
93+
- Server‑only trimming: USP removes client‑only branches and narrows the execution graph for dedicated servers, which invalidates some hook locations expected by older frameworks.
94+
95+
Result: TSAPI/TShock would need targeted porting to the USP context model. Running them “as‑is” on USP is unsupported.
96+
97+
98+
## Intended Audience
99+
This project is intended for developers who integrate OTAPI with a unified server process, server operators evaluating such integrations, and contributors who want to explore or extend USP.
100+
Note: This is a development/engineering project; APIs and features may evolve over time.
101+
- Plugin authors who need multi‑server isolation and strong context semantics.
102+
- Server operators wanting to scale multiple worlds in one process with consistent resource usage.
103+
- Developers who want a robust, typed protocol and a safer world data model.
104+
105+
106+
## USP Ecosystem Projects
107+
- [UnifierTSL](https://github.com/CedaryCat/UnifierTSL): A modern launcher that embeds USP, providing an upgraded startup experience and bundling TShock plugins already ported to the unified context model.
108+
109+
110+
## Key References
111+
- RootContext: `src/OTAPI.UnifiedServerProcess/Mods/RootContext.cs`
112+
- Patching pipeline: `src/OTAPI.UnifiedServerProcess/Core/PatchingLogic.cs`
113+
- GlobalNetwork components: `Program.cs`, `Network/Router.cs`, `Servers/ServerContext.cs` under `src/OTAPI.UnifiedServerProcess.GlobalNetwork`
114+
- TileProvider: `Mods/TileProviderMod.cs`
115+
- TrProtocol: models and generators under `src/TrProtocol` and `src/TrProtocol.SerializerGenerator`
116+
117+
External resources for deeper technical detail:
118+
- DeepWiki: `https://deepwiki.com/CedaryCat/OTAPI.UnifiedServerProcess`
119+
- Developer Guide: `docs/Developer-Guide.md`

docs/Developer-Guide.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# Developing on OTAPI Unified Server Process (USP)
2+
3+
## Quick Start
4+
- Install: Use the NuGet package `OTAPI.USP` to get `OTAPI.dll` and `OTAPI.Runtime.dll`.
5+
- Build: Restore and build locally if you need to work from source.
6+
- Run: See the Build from Source section for commands.
7+
8+
## Table of Contents
9+
- [OTAPI Ecosystem and USP](#otapi-ecosystem-and-usp)
10+
- [Install via NuGet (recommended)](#install-via-nuget-recommended)
11+
- [Build from Source (secondary)](#build-from-source-secondary)
12+
- [Core Concepts](#core-concepts)
13+
- [Creating and Using a Context](#creating-and-using-a-context)
14+
- [World Data: TileProvider](#world-data-tileprovider)
15+
- [Protocol: TrProtocol (IL-merged)](#protocol-trprotocol-il-merged)
16+
- [Collision (pre-patch ref parameters for performance)](#collision-pre-patch-ref-parameters-for-performance)
17+
- [Networking in Multi-Server: GlobalNetwork](#networking-in-multi-server-globalnetwork)
18+
- [Hooks and Extensions](#hooks-and-extensions)
19+
- [Compatibility Note (USP vs. TSAPI/TShock)](#compatibility-note-usp-vs-tsapitshock)
20+
- [Next Steps](#next-steps)
21+
- [References](#references)
22+
23+
This guide explains how to build against USP’s context‑bound server core, how to migrate code from older OTAPI/TShock style statics, and how to leverage TrProtocol, TileProvider, and Collision efficiently. It also clarifies where USP sits in the OTAPI ecosystem.
24+
25+
26+
## OTAPI Ecosystem and USP
27+
- Historical flow: Launchers like TSAPI (TShock.Launcher) start the server, TShock runs as a plugin; both depend on the OTAPI NuGet package that ships `OTAPI.dll` and `OTAPI.Runtime.dll`.
28+
- What USP does: `OTAPI.UnifiedServerProcess` performs a second IL patch over an existing OTAPI.dll and regenerates a new `OTAPI.Runtime.dll`, while keeping the original assembly names for compatibility with tooling and habits.
29+
30+
31+
## Install via NuGet (recommended)
32+
Use the published package `OTAPI.USP` which includes both `OTAPI.dll` and `OTAPI.Runtime.dll`.
33+
34+
```
35+
cd YourProject
36+
dotnet add package OTAPI.USP
37+
```
38+
39+
After restore/build, the assemblies are available from NuGet; you don’t need to build USP locally. If you do build USP, note that `OTAPI.dll` and `OTAPI.Runtime.dll` are emitted to the build configuration’s `output` folder under the project bin.
40+
41+
## Build from Source (secondary)
42+
```
43+
cd OTAPI.UnifiedServerProcess
44+
dotnet restore src/OTAPI.UnifiedServerProcess.sln
45+
dotnet build src/OTAPI.UnifiedServerProcess.sln -c Release
46+
dotnet run -c Debug -p src/OTAPI.UnifiedServerProcess
47+
# Output: src/OTAPI.UnifiedServerProcess/bin/Debug/net9.0/output/OTAPI.dll (+ OTAPI.Runtime.dll)
48+
```
49+
50+
51+
## Core Concepts
52+
### Root Context
53+
- The per‑server root object. Systems that used to be static on Terraria (e.g., `Main`, `Netplay`, `NetMessage`) are accessed through the context instance.
54+
### Context-Aware Hooks
55+
- On.* hooks operate with or within the context. Signatures and targets may differ from older OTAPI.
56+
### Multi-Server Isolation
57+
- Avoid direct static access to `Terraria.*` when correctness across multiple servers matters. Prefer context-bound access to maintain isolation.
58+
59+
60+
## Creating and Using a Context
61+
Minimal example based on `ServerContext`:
62+
```csharp
63+
using UnifiedServerProcess;
64+
using Terraria;
65+
66+
public class MyServer : RootContext
67+
{
68+
public MyServer(string name, string worldPath) : base(name)
69+
{
70+
Main.ActiveWorldFileData = WorldFile.GetAllMetadata(worldPath, false);
71+
Main.maxNetPlayers = byte.MaxValue;
72+
Netplay.ListenPort = -1; // Let a global router own the port if needed
73+
}
74+
}
75+
76+
// Create and use the context
77+
var ctx = new MyServer("World-A", "/path/to/World-A.wld");
78+
ctx.Console.WriteLine($"[{ctx.Name}] server booting...");
79+
// Access instance‑bound systems through the context
80+
ctx.Main.gameMode = 3;
81+
```
82+
83+
Migrating static access:
84+
```csharp
85+
// Before
86+
Terraria.Main.player[i].active = true;
87+
Terraria.NetMessage.SendData(25, -1, -1, NetworkText.Empty);
88+
89+
// After (context‑bound)
90+
ctx.Main.player[i].active = true;
91+
ctx.NetMessage.SendData(25, -1, -1, Terraria.Localization.NetworkText.Empty);
92+
```
93+
94+
95+
### World Data: TileProvider
96+
USP replaces legacy `ITile`/`Tile` usage with `TileData` (struct), `RefTileData` (ref‑like holder), and a context‑bound `TileCollection`.
97+
98+
### Ref-Friendly Iteration:
99+
```csharp
100+
// Iterate tiles with ref access to minimize copies
101+
var tiles = ctx.Main.tile; // TileCollection
102+
103+
for (int x = 0; x < ctx.Main.maxTilesX; x++)
104+
for (int y = 0; y < ctx.Main.maxTilesY; y++)
105+
{
106+
ref TileData tile = ref tiles[x, y] // ref TileData
107+
if (tile.active) {
108+
tile.type = 1; // Example write
109+
}
110+
}
111+
```
112+
113+
Passing stable handles across APIs:
114+
```csharp
115+
Tuple<int, int, RefTileData> heapStorage = new(0, 0, default) // heap object
116+
heapStorage.Item1 = 100;
117+
heapStorage.Item2 = 200;
118+
heapStorage.Item3 = ctx.Main.tile.GetRefTile(100, 200); // storage reference on heap safety
119+
120+
ref tile = ref heapStorage.Item3.Data; // RefTileData -> TileData
121+
```
122+
123+
Reference: `src/OTAPI.UnifiedServerProcess/Mods/TileProviderMod.cs`
124+
125+
126+
## Protocol: TrProtocol (IL-Merged)
127+
USP IL‑merges `TrProtocol` into the server core and merges public type metadata with OTAPI types. This enables seamless serialization for shared models. For example, `Terraria.DataStructures.PlayerDeathReason` gains generated read/write that operate directly on unmanaged pointers.
128+
129+
- Read/Write behavior
130+
- The pack entities provide basic pointer-based deserialization: `void ReadContent(ref void* ptr)` and `void WriteContent(ref void* ptr)`.
131+
- For a packet instance, the packet type is fixed. Therefore deserialization begins from the position dictated by the type metadata: normal packets read starting at the 2-byte length + 1-byte type header; Module packets read from 2-byte length + 1-byte type + 2-byte ModuleType.
132+
- WriteContent writes the type information into the buffer as part of serialization.
133+
134+
### Serialize/Deserialize Example
135+
```csharp
136+
unsafe
137+
{
138+
byte* buf = stackalloc byte[256];
139+
void* ptr = buf + 2;
140+
141+
var p = new TrProtocol.NetPackets.SpawnPlayer(_PlayerSlot: 1, ...);
142+
p.WriteContent(ref ptr);
143+
short len = (short)((byte*)ptr - buf);
144+
*((short*)buf) = len;
145+
146+
void* rptr = buf + 2;
147+
var p2 = new TrProtocol.NetPackets.SpawnPlayer(ref rptr); //
148+
149+
void* rptr = buf + 2;
150+
var p3 = new TrProtocol.NetPackets.SpawnPlayer(); //
151+
p3.ReadContent(ref rptr);
152+
}
153+
```
154+
- ILengthAware and IExtraData
155+
- ILengthAware packets know their binary length and thus participate in contexts where trailing or compressed data exists (e.g., `ExtraData`). Such packets explicitly implement `ReadContent(ref void* ptr, void* end_ptr)` to read up to the end boundary, and `WriteContent(ref void* ptr)` to write payloads. The plain `ReadContent(ref void* ptr)` API is hidden via explicit interface implementation to avoid surface API that ignores the boundary.
156+
- IExtraData extends ILengthAware and adds an `ExtraData` byte[] property. The generator will add this property on types that implement `IExtraData` and handle copying any remaining bytes into it during deserialization.
157+
- For example, `TileSection` (which uses compression) relies on knowing its data range and thus participates in the length-aware path.
158+
- These rules are enforced by the generated serializers. They explicitly hide the non-boundary `ReadContent` via explicit implementation like:
159+
``` csharp
160+
/// <summary>
161+
/// This operation is not supported and always throws a System.NotSupportedException.
162+
/// </summary>
163+
[Obsolete]
164+
void IBinarySerializable.ReadContent(ref void* ptr) => throw new NotSupportedException();
165+
```
166+
167+
// Public surface
168+
void ReadContent(ref void* ptr, void* end_ptr);
169+
- ISideSpecific and server/client roles
170+
- Data packets implementing ISideSpecific require the correct execution role before de/serialization. The IsServerSide property must reflect the current context; there is no extra parameter for ReadContent/WriteContent to indicate the role. Some packets use attributes such as [A2BOnly] to designate end-specific logic.
171+
- NetTextModule demonstrates this with two fields:
172+
``` csharp
173+
[C2SOnly]
174+
public TextC2S? TextC2S;
175+
[S2COnly]
176+
public TextS2C? TextS2C;
177+
```
178+
- Developers manually ensure `IsServerSide` is set correctly on ISideSpecific packets. The source generator does not modify that behavior.
179+
180+
- Constructors and ergonomics
181+
- Packets expose convenient constructors generated by the source generator. The simple constructor maps to the public fields/properties (a direct one-to-one assignment).
182+
- For ISideSpecific packets, an additional constructor variant accepts a boolean `isServerSide` to set `IsServerSide` accordingly when desired.
183+
- If a type uses `InitDefaultValue` on fields, the generator will also provide a second constructor that prioritizes parameter order and default values, easing usage for common initialization.
184+
- Example: WorldData
185+
```csharp
186+
public partial struct WorldData : INetPacket
187+
{
188+
public readonly MessageID Type => MessageID.WorldData;
189+
[InitDefaultValue] public int Time;
190+
[InitDefaultValue] public BitsByte DayAndMoonInfo;
191+
[InitDefaultValue] public byte MoonPhase;
192+
public short MaxTileX;
193+
public short MaxTileY;
194+
public short SpawnX;
195+
public short SpawnY;
196+
//
197+
}
198+
```
199+
The generator will produce two constructors, for example:
200+
- `public WorldData(int _Time, BitsByte _DayAndMoonInfo, byte _MoonPhase, short _MaxTileX, ...)` and
201+
- `public WorldData(short _MaxTileX, short _MaxTileY, ..., int _Time = default, BitsByte _DayAndMoonInfo = default, byte _MoonPhase = default, ...)`
202+
to simplify user code.
203+
204+
- Example usage remains valid, with the generated constructors reducing boilerplate.
205+
206+
## Collision (pre-patch ref parameters for performance)
207+
`PatchCollision` transforms `Terraria.Collision` static fields (e.g., `up`, `down`, `left`, `right`, etc.) into method `ref` parameters and updates all call sites. These fields were temporary variables and outputs; rewriting them early avoids deep context field dereferences and improves performance (stack allocation, clearer data flow, fewer indirections). Because of this, Collision internals are no longer runtimemodified fields and are not converted into context instances.
208+
209+
Usage pattern (illustrative):
210+
211+
```csharp
212+
using Microsoft.Xna.Framework;
213+
using Terraria;
214+
215+
float up = 0, down = 0, left = 0, right = 0;
216+
var vel = new Vector2(4, 0);
217+
var pos = new Vector2(100, 200);
218+
219+
// Signatures vary by method; pass ref outputs as required
220+
// e.g., Collision.TileCollision(pos, vel, width, height, fallThrough, canJump, gravDir, ref up, ref down, ref left, ref right);
221+
```
222+
223+
Reference: `src/OTAPI.UnifiedServerProcess/Core/PatchCollision.cs`
224+
225+
## Networking in Multi‑Server: GlobalNetwork
226+
`GlobalNetwork/Network/Router` demonstrates shared sockets and per‑client routing to the correct server:
227+
- Global arrays: `Router.globalClients`, `Router.globalMsgBuffers` are shared.
228+
- Per‑client context: `GetClientCurrentlyServer(i)` returns the owning context.
229+
- Scheduling: Only processes messages for clients belonging to the current server’s context.
230+
- Transfer: `Router.ServerTransfer(byte plr, ServerContext to)` moves a player by swapping `Main.player[plr]`, resetting sections, and sending join/leave sync.
231+
232+
Reference: `src/OTAPI.UnifiedServerProcess.GlobalNetwork/Network/Router.cs`, `src/OTAPI.UnifiedServerProcess.GlobalNetwork/Servers/ServerContext.cs`
233+
234+
235+
## Hooks and Extensions
236+
- Runtime hooks are in `OTAPI.Runtime.dll` (On.* style), targeting context‑bound or updated systems. Review signatures against your USP build.
237+
- Prefer hooking the context versions over legacy static targets.
238+
239+
240+
## Compatibility Note (USP vs. TSAPI/TShock)
241+
USP’s context model is incompatible with frameworks that depend on global static state and legacy tile/network APIs. Porting requires:
242+
- Rewriting static accesses to context members
243+
- Updating hook targets/signatures to the context versions
244+
- Migrating world/tile interactions to `TileData`/`RefTileData`/`TileCollection`
245+
- Adapting to USP’s pruned server execution paths
246+
247+
248+
## Next Steps
249+
- Explore the demo in `src/OTAPI.UnifiedServerProcess.GlobalNetwork`.
250+
- Design APIs that pass or encapsulate `RootContext` explicitly.
251+
- Use TrProtocol for custom packets and TileProvider for world operations to maximize performance and clarity.

0 commit comments

Comments
 (0)