118 lines
4.8 KiB
Markdown
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>
|