aboutsummaryrefslogtreecommitdiff
path: root/internal/engine/tick.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-15 14:18:14 -0400
committerhistoria <[not public]>2026-09-15 14:18:14 -0400
commit396c3facb8287b9a34076cae8f3d7b300d366d99 (patch)
tree941f280d051fac474fa68ca08318a8d16adc8d0f /internal/engine/tick.go
parentb3568f03e4103dd46db2ccc680fbab0a6454d604 (diff)
downloadthehouseoficarus-396c3facb8287b9a34076cae8f3d7b300d366d99.tar.gz
fix: atomic saves, casino RNG/config isolation, tick lifecycle
Diffstat (limited to 'internal/engine/tick.go')
-rw-r--r--internal/engine/tick.go16
1 files changed, 13 insertions, 3 deletions
diff --git a/internal/engine/tick.go b/internal/engine/tick.go
index 7b9666b..00a9c60 100644
--- a/internal/engine/tick.go
+++ b/internal/engine/tick.go
@@ -17,6 +17,7 @@ type subscriber struct {
}
type Engine struct {
+ tickMu sync.Mutex // serializes manual ticks and successive ticker runs
mu sync.Mutex
subscribers map[uint64]*subscriber
nextID uint64
@@ -61,19 +62,24 @@ func (e *Engine) Start(tickLengthMs int) {
}
e.ticker = time.NewTicker(time.Duration(tickLengthMs) * time.Millisecond)
e.stopCh = make(chan struct{})
+ // Capture this run's resources. A quick Stop/Start must not make the
+ // old goroutine consume the replacement ticker or stop channel.
+ ticker, stopCh := e.ticker, e.stopCh
go func() {
for {
select {
- case <-e.ticker.C:
+ case <-ticker.C:
e.processTick()
- case <-e.stopCh:
+ case <-stopCh:
return
}
}
}()
}
+// Stop requests that the ticker loop exit. It is idempotent, but does not
+// wait for an in-flight callback; it is not a graceful-shutdown save barrier.
func (e *Engine) Stop() {
e.mu.Lock()
defer e.mu.Unlock()
@@ -83,14 +89,18 @@ func (e *Engine) Stop() {
e.running = false
if e.stopCh != nil {
close(e.stopCh)
+ e.stopCh = nil
}
}
// Tick fires every due subscriber synchronously and returns. Subscribers
-// are invoked in ascending subscription-ID (registration) order.
+// are invoked in ascending subscription-ID (registration) order. Tick calls
+// are serialized; a callback must not call Tick recursively.
func (e *Engine) Tick() { e.processTick() }
func (e *Engine) processTick() {
+ e.tickMu.Lock()
+ defer e.tickMu.Unlock()
e.mu.Lock()
snapshot := make(map[uint64]*subscriber, len(e.subscribers))
for id, sub := range e.subscribers {