1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
package engine
import (
"sync"
"testing"
)
// TestProcessTickFiresInSubscriptionOrder asserts the engine invokes due
// tick subscribers in ascending subscription-ID order, i.e. the order in
// which they registered. This is the determinism contract that callers
// (notably the game's master subscriber at cmd/thoi/main.go, which subscribes
// first as ID 1 and must fire before combat/aggro/respawn per-tick callbacks)
// increasingly rely on.
func TestProcessTickFiresInSubscriptionOrder(t *testing.T) {
e := New()
var mu sync.Mutex
var order []int
// Three interval-1 subscribers. Each appends its own index. All return
// true to stay subscribed (we Unsubscribe at the end).
id1 := e.Subscribe(1, func() bool {
mu.Lock()
order = append(order, 1)
mu.Unlock()
return true
})
id2 := e.Subscribe(1, func() bool {
mu.Lock()
order = append(order, 2)
mu.Unlock()
return true
})
id3 := e.Subscribe(1, func() bool {
mu.Lock()
order = append(order, 3)
mu.Unlock()
return true
})
if id1 >= id2 || id2 >= id3 {
t.Fatalf("ID monotonicity broken: got %d, %d, %d", id1, id2, id3)
}
// processTick is synchronous: it invokes every due interval-1 subscriber
// inline in turn before returning. So `order` is fully populated here.
e.processTick()
mu.Lock()
got := append([]int(nil), order...)
mu.Unlock()
e.Unsubscribe(id1)
e.Unsubscribe(id2)
e.Unsubscribe(id3)
if len(got) != 3 {
t.Fatalf("expected 3 subscriber firings, got %d: %v", len(got), got)
}
if got[0] != 1 || got[1] != 2 || got[2] != 3 {
t.Errorf("subscribers fired out of registration order: got %v, want [1 2 3]", got)
}
}
|