# SharpLib and general coding guidelines ## 1. CORE PHILOSOPHY * **Leaky Abstractions:** All abstractions are always leaky. Do not hide complexity. Expose failure modes, costs, and "why" parameters. * **Visual Cost:** "Expensive things must look expensive." No hidden RPCs. Use explicit Message structs and `Send` methods. * **Fast & Introspectable:** Code must be highly performant (hot-path aware) but deeply debuggable (`log`, `DebugOld`, `reason` strings). ## 2. NAMING & SEMANTICS * **Leaf Libraries:** Core libs (`io`, `imm`, `net`, `log`) are 2-5 chars. **NEVER** use `using` for them. Always type the prefix (e.g., `imm.Process`). * **No Redundancy:** * **No "Get":** `Player()` not `GetPlayer()`. * **No Echo:** `Send(msg)` not `SendMsg(msg)`. * **No "I" Prefix:** Interfaces are `Renderer`, not `IRenderer`. * **Architecture:** Slow/Pausable Realtime. Entity Component System (Pub/Sub). ## 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) * **Functional Tracing:** * `var x = log.info( $"Got {log.var(Calc())} from the calculation" );` // This both logs the info and prints what happened * **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.logProps(obj, "Header")` and `log.exception(ex, "Context")`. * 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`, `Recorded`, `Timed` are **IMMUTABLE**. * **Change Pipeline:** MUST use `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 `Process` log. * **History:** `obj.DebugOld` is for **DEBUG ONLY**. Do not base game logic on it. ## 5. Visual Cost * Unless something is a function call, it should look like a function call. For example RPCs are bad, sending a message is good. * **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))` ## FEW-SHOT EXAMPLES (Strictly Imitate) ```csharp // 1. NAMING & LOGGING // Explicit 'log' usage, no 'Get', no 'Console' public void Init() { log.startup( "log/current_project.log" ); var waitTime = 1.0f; log.info( $"Waiting for {waitTime}" ); try { obj.SomeOperation(); } catch( Exception ex ) { log.exception( ex, $"" ); } // 2. IMMUTABLE PROCESS // Reason "Init" passed down. Ref pattern used. imm.Process(ref _state, s => s with { Ready = true }, "Init"); } // 3. 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)); } } // 5. INTROSPECTION API // "Punching a hole" with the reason parameter public void Equip(Item i, string reason = "Equip") { imm.Process( ref inv, s => s.Add(i), reason); }