You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Enforce: player outbound via `PlayerPresenceGrain.SendComposerAsync`; no direct handler socket sends.
@@ -64,13 +65,57 @@ Default output format:
64
65
- Prefer deterministic handlers/services with clear guard clauses.
65
66
- Preserve cancellation and async flow where it already exists.
66
67
- Handle failure paths explicitly; do not ship happy-path-only changes.
67
-
- Avoid dead code, unused allocations, and broad catch blocks that hide errors.
68
+
- Avoid dead code, unused allocations, and broad catch blocks that hide errors (see **Orleans grain development rules** for specifics).
68
69
- For revision compatibility work, prefer restoring/adding missing incoming message contracts in `Turbo.Primitives/Messages/Incoming/**` before mutating serializer/composer payload behavior.
69
70
- Do not alter serializer/composer behavior by replacing real payload writes with placeholder constants (for example, unconditional `WriteInteger(0)`) unless explicitly requested.
70
71
- If work references `Revision<id>` parsers/serializers, edit the plugin repo path:
If a grain method calls its own player's `GetSummaryAsync` inside a loop, hoist the call before the loop.
92
+
Same result every iteration = wasted round-trips.
93
+
94
+
### Batch DB operations
95
+
Do not loop `ExecuteDeleteAsync` per entity. Use a single `WHERE ... IN (...)` query.
96
+
Same for composer fan-out: collect all updates, send once.
97
+
98
+
### Use timer-based flush for housekeeping writes
99
+
Follow the `RoomPersistenceGrain` pattern: queue dirty state, flush with `RegisterGrainTimer` on interval, and flush on `OnDeactivateAsync`.
100
+
Do not issue per-event DB writes that block the grain turn.
101
+
102
+
### Do not hardcode limits in grains
103
+
Handlers already read configuration values (e.g. `Turbo:FriendList:UserFriendLimit`) from `IConfiguration` and pass them to grains.
104
+
Magic numbers like `Take(50)`, `Take(20)`, or `maxIgnoreCapacity = 100` must come from configuration parameters on the grain interface method.
105
+
A 10,000 user hotel needs different tuning than a 10 player dev server.
106
+
107
+
### Use tracked deletes for atomicity
108
+
`ExecuteDeleteAsync` commits immediately and bypasses the EF change tracker.
109
+
If a delete + insert must succeed or fail together, use `FirstOrDefaultAsync` + `Remove` so both go through one `SaveChangesAsync`.
110
+
111
+
### Replace .Ignore() with a LogAndForget helper
112
+
Orleans `.Ignore()` makes cross-grain failures invisible. Use a `LogAndForget` extension that calls `ContinueWith(OnlyOnFaulted)` to log the exception.
113
+
Still fire-and-forget, but failures are visible in production logs.
114
+
115
+
### Bound session/history collections
116
+
Any in-memory collection that grows per-message (e.g. conversation history) must have a configurable cap.
Copy file name to clipboardExpand all lines: CONTEXT.md
+11Lines changed: 11 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,6 +51,17 @@
51
51
- Lifecycle:
52
52
- inactive grains are Orleans-managed and can deactivate automatically unless explicitly marked `[KeepAlive]`.
53
53
54
+
## Grain runtime patterns
55
+
- Every grain that does cross-grain calls or DB work must inject `ILogger<T>` and log caught exceptions. No bare `catch { }`.
56
+
- Independent grain calls (e.g. checking online status for N friends) must use `Task.WhenAll`, not sequential `await` in a loop.
57
+
- Identical grain calls must not repeat inside loops — hoist before the loop.
58
+
- DB batch operations use single `WHERE ... IN (...)` queries, not per-entity `ExecuteDeleteAsync` loops.
59
+
- Housekeeping writes (e.g. delivered flags) follow the timer-flush pattern: queue dirty state, flush with `RegisterGrainTimer`, flush on `OnDeactivateAsync`. See `RoomPersistenceGrain` for reference.
60
+
- Do not hardcode limits (`Take(N)`, capacity constants) in grains. Pass them from handlers via `IConfiguration`.
61
+
- When a delete + insert must be atomic, use EF tracked operations (`Remove` + `SaveChangesAsync`), not `ExecuteDeleteAsync`.
62
+
- Replace `.Ignore()` on grain tasks with a `LogAndForget` helper that logs faulted continuations.
63
+
- In-memory collections that grow per-event (message history, queues) must have a configurable cap.
64
+
54
65
## Placement rules
55
66
- New host startup/wiring behavior:
56
67
-`Turbo.Main/` (usually `Program.cs`, `Extensions/`, or `Console/`)
0 commit comments