3.5 KiB
3.5 KiB
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
Sendmethods. - Fast & Introspectable: Code must be highly performant (hot-path aware) but deeply debuggable (
log,DebugOld,reasonstrings).
2. NAMING & SEMANTICS
- Leaf Libraries: Core libs (
io,imm,net,log) are 2-5 chars. NEVER useusingfor them. Always type the prefix (e.g.,imm.Process). - No Redundancy:
- No "Get":
Player()notGetPlayer(). - No Echo:
Send(msg)notSendMsg(msg). - No "I" Prefix: Interfaces are
Renderer, notIRenderer.
- No "Get":
- Architecture: Slow/Pausable Realtime. Entity Component System (Pub/Sub).
3. LOGGING (log static class)
- Constraint: NO
Console.WriteLine,Debug.WriteLine, orILogger. - 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")andlog.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");
- Ref Helper (Preferred):
- The "Hole Punch": Always propagate a
string reasonparameter in your methods to feed theProcesslog. - History:
obj.DebugOldis 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))
- Bad:
FEW-SHOT EXAMPLES (Strictly Imitate)
// 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);
}