sharplib/AGENTS.md
2026-03-21 13:59:27 -07:00

118 lines
4.8 KiB
Markdown

# SharpLib and general coding guidelines
## 0. PERFORMANCE. Title it 0. AXIOM: HIGH-PERFORMANCE / ZERO-ALLOCATION
* **Zero Allocation Default:** Assume all code runs in a 60hz+ game loop or high-throughput pipeline unless marked `[ColdPath]`.
* **NO LINQ:** `using System.Linq;` is strictly forbidden in core logic. Use `for` loops, arrays, or `Span<T>`/`ReadOnlySpan<T>`.
* **NO Hidden Closures:** Do not use lambdas or delegates that capture local variables. If a lambda is necessary, make it `static` and pass state explicitly.
* **Struct Semantics:**
* Pass readonly structs using `in` or `ref readonly`.
* Do not pass `struct` to interfaces unless constrained by generics (`where T : struct`), to prevent boxing.
* **String Allocation:** NO string interpolation (`$""`) on the hot path. Use static strings, `string.Create`, or format into pooled buffers.
* **Async/Task Overhead:** Default to synchronous execution for immediate state changes. If async is mandatory on a hot path, use `ValueTask<T>`, never `Task<T>`.
* **Collections:** No `List<T>.Add()` inside tight loops without pre-sizing the capacity. Prefer array pooling (`ArrayPool<T>.Shared`).
## 1. CORE PHILOSOPHY
* **Leaky Abstractions:** All abstractions are always leaky. Do not hide complexity. Expose failure modes, costs, and "reason" parameters.
* **Visual Cost:** "Expensive things must look expensive." No hidden RPCs. Use explicit Message structs and `Send` methods instead of Rpcs
* **Fast & Introspectable:** Code must be highly performant (hot-path aware) but deeply debuggable (`log`, `DebugOld`, `reason` strings). These are there to make debugging
from console or log file possible
## 2. NAMING & SEMANTICS
* **Leaf Libraries:** Core libs (`io`, `imm`, `net`, `log`) are 2-5 chars. **NEVER** include using {namespace}; for core libs or any short namespace
* **No Redundancy:In Names**
* **No "Get":** `Player()` not `GetPlayer()`.
* **No Echo:** `Send(msg)` not `SendMsg(msg)`.
* **No "I" Prefix:** Interfaces are `Renderer`, not `IRenderer`.
## 3. LOGGING (`log` static class)
* **Constraint:** **NO** `Console.WriteLine`, `Debug.WriteLine`, or `ILogger`.
* **Mechanism:** The logging system is very fast. It only queues the log on the main thread. It collects the caller debug info, and uses the directory name of the file for a default category (this can be overridden)
* **Standard:** `log.info`, `log.debug`, `log.warn`, `log.error`.
* **Finer grained:** `log.trace`, `log.high`
* Use log.exception( ex, $"[what was trying to be done]" );
* **Introspection:** `log.props(obj, "Header")` and `log.exception(ex, "Context")`.
* **Information** put important info as far to the left as possible, even at the cost of poor wording
## 4. IMMUTABLE STATE (`io`, `imm`)
* **Rule:** Objects inheriting `Versioned<T>`, `Recorded<T>`, `Timed<T>` are **IMMUTABLE**.
* **Change Pipeline:** MUST use `imm.Process`.
* **Ref Helper (Preferred):** `imm.Process(ref _state, s => s with { X = 1 }, "Reason");`
* **Instance:** `_state = _state.Process(s => s with { X = 1 }, "Reason");`
* **The "Hole Punch":** Always propagate a `string reason` parameter in your methods to feed the `imm.Process` log.
* **History:** `obj.DebugOld` is for **DEBUG ONLY**. Do not base program logic on it.
## 5. Visual Cost
* Network/IO/Expensive operations MUST NOT masquerade as local function calls. Require explicit structural allocation.
* **No RPCs:** Do not make network calls look like functions.
* **Pattern:** Construct Struct -> Send.
* *Bad:* `proxy.Move(x,y)`
* *Good:* `Net.Send(new MoveMsg(x,y))`
## 6. FEW-SHOT EXAMPLES (Strictly Imitate)
<csharp_examples>
```csharp
// ** NAMING & LOGGING
// Explicit 'log' usage, no 'Get', no 'Console'
public void Init()
{
log.startup( "log/current_project.log", log.Endpoints.All );
var waitTime = 1.0f;
log.info( $"Waiting for {waitTime}" );
try
{
obj?.SomeOperation();
}
catch( Exception ex )
{
//Logs the exception name, message, the reason, the stack
log.exception( ex, $"SomeOperation failed" );
}
// ** IMMUTABLE PROCESS
// Reason "Init" passed down. Ref pattern used.
imm.Process(ref _state, s => s with { Ready = true }, "Init");
}
// ** LOGIC FLOW
public void Update(float dt)
{
// 'Player()' not 'GetPlayer()'
var p = Player();
// Visual Cost: Explicit allocation of message struct
if (p.Active)
{
Net.Send(new HeartbeatMsg(dt));
}
}
// ** "Punching a hole" with the reason parameter
public void Equip(Item i, string reason /*No default so a reason will be passed in*/ )
{
imm.Process( ref inv, s => s.Add(i), reason);
}
. . .
// Somewhere else in project
public void PlayerSwappedWeapons( Player pl )
{
Equip( _item, $"Player {pl.Name} swapped weapons (PlayerSwappedWeapons)" );
}
```
<csharp_examples>