aboutsummaryrefslogtreecommitdiff
path: root/internal/engine
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-18 01:50:17 -0400
committerhistoria <[not public]>2026-09-18 01:50:17 -0400
commit87d08f0151e643951769e323995f1c5ed89ee200 (patch)
treedc42a0b30ebb5a00dd7ba505cc4eaa44f4f308bc /internal/engine
parent32a3c59cebf2a16277e54d715a67adfdbe00b456 (diff)
downloadthehouseoficarus-87d08f0151e643951769e323995f1c5ed89ee200.tar.gz
fix: harden economy recovery, shutdown concurrency, world validation, and inventory-loss paths
Diffstat (limited to 'internal/engine')
-rw-r--r--internal/engine/tick.go17
1 files changed, 14 insertions, 3 deletions
diff --git a/internal/engine/tick.go b/internal/engine/tick.go
index aa10a73..68dd54f 100644
--- a/internal/engine/tick.go
+++ b/internal/engine/tick.go
@@ -26,6 +26,7 @@ type Engine struct {
ticker *time.Ticker
running bool
stopCh chan struct{}
+ loopDone chan struct{}
}
func New() *Engine {
@@ -67,8 +68,11 @@ func (e *Engine) Start(tickLengthMs int) {
// 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
+ loopDone := make(chan struct{})
+ e.loopDone = loopDone
go func() {
+ defer close(loopDone)
for {
select {
case <-ticker.C:
@@ -100,10 +104,17 @@ func (e *Engine) Stop() {
// exited. Use this for graceful shutdown, where the final tick must finish
// before owned state is saved.
func (e *Engine) StopAndWait() {
+ e.mu.Lock()
+ loopDone := e.loopDone
+ e.mu.Unlock()
e.Stop()
- // Join any in-flight tick: processTick holds tickMu for its whole body,
- // so acquiring the mutex blocks until the running callback completes;
- // holding it until return keeps that guarantee for the caller.
+ // Wait for the ticker loop goroutine to exit. This is the real save
+ // barrier: the loop may already have received from the ticker and be
+ // about to call processTick, which locking tickMu alone would not catch.
+ if loopDone != nil {
+ <-loopDone
+ }
+ // Also join a direct Tick() that is mid-callback on another goroutine.
e.tickMu.Lock()
defer e.tickMu.Unlock()
}