aboutsummaryrefslogtreecommitdiff
path: root/internal/engine/tick_test.go
blob: 520ef198890e86d4ce5a3a657575c771052c5dc1 (plain)
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)
	}
}