94 lines
3.5 KiB
Markdown
94 lines
3.5 KiB
Markdown
|
|
# 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<T>`, `Recorded<T>`, `Timed<T>` 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);
|
|
}
|