chore: Adds AI instructions and SKILLs for ModernUO codebase (#2347)
Summary
- Adds CLAUDE.md at repo root with 14 terse code audit rules (always loaded, low token cost)
- Adds pointer files for other AI tools: AGENTS.md (Codex), GEMINI.md, .github/COPILOT-INSTRUCTIONS.md (Copilot), .cursorrules (Cursor) — all redirect to CLAUDE.md as single source of truth
- Gitignores /.claude so personal AI config isn't distributed
- Moves Claude skills to dev-docs/claude-skills/ (opt-in, not auto-loaded)
- Adds 14 dev-docs covering codebase conventions
Code Audit Rules (in CLAUDE.md)
1. LINQ tiered rules (Tier 1 free, Tier 2 warm, Tier 3 forbidden)
2. No Console.WriteLine — use LogFactory.GetLogger()
3. No concurrency primitives in game code
4. No World.Mobiles/World.Items iteration
5. Clean up refs in OnDelete()/OnAfterDelete()
6. Cancel timers in OnDelete()/OnAfterDelete()
7. STArrayPool<T>.Shared not ArrayPool<T>.Shared
8. PooledRefList<T> not new List<T>() on hot paths
9. Serialization: partial class, [Constructible], no serialized TimerExecutionToken
10. No Task.Run/new Thread() in game code
11. Never assume era — ask which expansion
12. _camelCase fields, PascalCase properties/methods
13. No empty gumps — use DisplayTo() pattern
14. PropertyList string literals must be {} holes, cliloc-as-argument uses :#
This commit is contained in:
parent
e77a566f32
commit
1391c563fe
34 changed files with 8549 additions and 0 deletions
162
dev-docs/claude-skills/modernuo-threading.md
Normal file
162
dev-docs/claude-skills/modernuo-threading.md
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
---
|
||||
name: modernuo-threading
|
||||
description: >
|
||||
Trigger when discussing async patterns, world saves, game loop, or reviewing code for threading issues. When using await, Task, or any concurrency-related code in game logic.
|
||||
---
|
||||
|
||||
# ModernUO Threading & Event Loop
|
||||
|
||||
## When This Activates
|
||||
- Reviewing code for threading issues
|
||||
- Discussing async/await patterns
|
||||
- Working with world saves
|
||||
- Any mention of `Task.Run`, `Thread`, `lock`, `ConcurrentDictionary`
|
||||
- Understanding the game loop
|
||||
|
||||
## CRITICAL RULE: Single-Threaded Game Logic
|
||||
|
||||
ModernUO uses a **single-threaded game loop**. All game logic runs on one thread. There are NO exceptions for game code.
|
||||
|
||||
## Forbidden in Game Code
|
||||
|
||||
```csharp
|
||||
// ALL of these are WRONG in Projects/UOContent/ code:
|
||||
Task.Run(() => ProcessItems()); // Background thread
|
||||
new Thread(BackgroundWork).Start(); // Manual thread
|
||||
ThreadPool.QueueUserWorkItem(Work); // Thread pool
|
||||
lock (_syncObj) { ... } // Locking
|
||||
Monitor.Enter(obj); // Monitor
|
||||
volatile int _counter; // Volatile
|
||||
ConcurrentDictionary<int, Item> _items; // Concurrent collections
|
||||
ConcurrentQueue<T> _queue; // Concurrent collections
|
||||
Interlocked.Increment(ref _count); // Atomics
|
||||
Mutex mutex; // OS mutex
|
||||
Semaphore sem; // Semaphore
|
||||
ReaderWriterLockSlim rwl; // RW lock
|
||||
```
|
||||
|
||||
**Why**: The game loop is single-threaded. Concurrency primitives add overhead for no benefit, and background threads would cause data races with game state.
|
||||
|
||||
## Why await Is Safe
|
||||
|
||||
`EventLoopContext` implements `SynchronizationContext` and routes all `await` continuations back to the main thread:
|
||||
|
||||
```csharp
|
||||
// This is SAFE in game code:
|
||||
await Timer.Pause(TimeSpan.FromMilliseconds(100));
|
||||
// Continuation runs on the game thread, not a thread pool thread
|
||||
```
|
||||
|
||||
The flow:
|
||||
1. `await` captures `EventLoopContext` as the synchronization context
|
||||
2. When the awaited task completes, the continuation is posted to `EventLoopContext._queue`
|
||||
3. `LoopContext.ExecuteTasks()` runs those continuations on the main thread during the next game loop tick
|
||||
|
||||
## Game Loop Structure
|
||||
|
||||
```csharp
|
||||
// Simplified from Projects/Server/Main.cs
|
||||
while (!Closing)
|
||||
{
|
||||
_tickCount = GetTimestamp();
|
||||
_now = DateTime.UtcNow;
|
||||
|
||||
Mobile.ProcessDeltaQueue(); // Send mobile state changes to clients
|
||||
Item.ProcessDeltaQueue(); // Send item state changes to clients
|
||||
Timer.Slice(_tickCount); // Execute due timers
|
||||
NetState.Slice(); // Process network I/O
|
||||
LoopContext.ExecuteTasks(); // Run async continuations (Timer.Pause, etc.)
|
||||
Timer.CheckTimerPool(); // Refill timer pool if needed
|
||||
}
|
||||
```
|
||||
|
||||
## EventLoopContext Details
|
||||
|
||||
```csharp
|
||||
public sealed class EventLoopContext : SynchronizationContext
|
||||
{
|
||||
private readonly ConcurrentQueue<Action> _queue; // Normal tasks
|
||||
private readonly ConcurrentQueue<Action> _priorityQueue; // High priority
|
||||
private readonly int _maxPerFrame; // Default: 128
|
||||
|
||||
// Posts run on next ExecuteTasks() call
|
||||
public void Post(Action d, Priority priority = Priority.Normal);
|
||||
|
||||
// Send blocks if called from another thread, immediate if on game thread
|
||||
public override void Send(SendOrPostCallback d, object state);
|
||||
|
||||
// Called once per game loop tick
|
||||
public void ExecuteTasks();
|
||||
}
|
||||
```
|
||||
|
||||
## Memory: STArrayPool vs ArrayPool
|
||||
|
||||
In game code, use `STArrayPool<T>.Shared` (single-threaded, no locks):
|
||||
```csharp
|
||||
// GOOD - no locking overhead
|
||||
var buffer = STArrayPool<byte>.Shared.Rent(1024);
|
||||
try { /* use buffer */ }
|
||||
finally { STArrayPool<byte>.Shared.Return(buffer); }
|
||||
```
|
||||
|
||||
`ArrayPool<T>.Shared` uses locks for thread safety -- unnecessary overhead in single-threaded context.
|
||||
|
||||
## Memory: PooledRefList
|
||||
|
||||
```csharp
|
||||
// Stack-allocated list using pooled arrays
|
||||
using var list = PooledRefList<Mobile>.Create();
|
||||
list.Add(mobile);
|
||||
// Automatically returns array to pool on Dispose
|
||||
|
||||
// For multi-threaded contexts (rare):
|
||||
using var list = PooledRefList<Mobile>.CreateMT();
|
||||
```
|
||||
|
||||
## World Saves
|
||||
|
||||
World saves DO involve background work, but this is handled by server infrastructure:
|
||||
1. **Serialization** happens on the main thread (safe access to game state)
|
||||
2. **Disk I/O** may happen on background threads (no game state access)
|
||||
3. Main thread blocks briefly during serialization snapshot
|
||||
|
||||
```csharp
|
||||
// From World.cs -- save flow:
|
||||
World.Save();
|
||||
→ Preserialize() on thread pool (allocate heaps)
|
||||
→ Snapshot() on main thread (serialize game state)
|
||||
→ WriteFiles() on thread pool (disk I/O only)
|
||||
```
|
||||
|
||||
## Exceptions: Server Infrastructure
|
||||
|
||||
These files MAY use threading (they're server infrastructure, not game logic):
|
||||
- `Projects/Server/Main.cs` - Event loop, thread setup
|
||||
- `Projects/Server/World/World.cs` - World save I/O
|
||||
- `Projects/Server/Network/` - Network I/O
|
||||
- `Projects/Server/Timer/Timer.Pool.cs` - Pool refill
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Pattern | Problem | Solution |
|
||||
|---|---|---|
|
||||
| `Task.Run(...)` | Runs on thread pool, races with game state | Use `Timer.StartTimer()` |
|
||||
| `new Thread(...)` | Same as above | Use `Timer.StartTimer()` |
|
||||
| `lock(obj)` | Unnecessary overhead, no contention exists | Remove lock, use plain code |
|
||||
| `ConcurrentDictionary` | Lock-free but still overhead | Use `Dictionary<K,V>` |
|
||||
| `volatile` | Memory barriers not needed on single thread | Use plain field |
|
||||
| `Thread.Sleep()` | Blocks entire game loop | Use `await Timer.Pause()` |
|
||||
| `ArrayPool<T>.Shared` | Uses locks | Use `STArrayPool<T>.Shared` |
|
||||
|
||||
## Real Examples
|
||||
- Game loop: `Projects/Server/Main.cs` (RunEventLoop)
|
||||
- EventLoopContext: `Projects/Server/EventLoopTasks.cs`
|
||||
- STArrayPool: `Projects/Server/Buffers/STArrayPool.cs`
|
||||
- PooledRefList: `Projects/Server/Collections/PooledRefList.cs`
|
||||
- World save: `Projects/Server/World/World.cs`
|
||||
|
||||
## See Also
|
||||
- `dev-docs/threading-model.md` - Complete threading documentation
|
||||
- `dev-docs/claude-skills/modernuo-code-audit.md` - Threading audit rules
|
||||
- `dev-docs/claude-skills/modernuo-timers.md` - Timer-based scheduling
|
||||
Loading…
Add table
Add a link
Reference in a new issue