Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-timers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kit": patch
---

Guard non-positive `Room.Every` durations so they return no timer instead of panicking, and fix stale package metadata.
4 changes: 2 additions & 2 deletions host/sdk/room.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ func (rt *roomRuntime) handleHibernate(c command) {
c.reply <- err // freeze failed: room stays live, ordering immaterial
return
}
c.reply <- nil // success reply BEFORE disposal cancels the ctx
c.reply <- nil // success reply BEFORE disposal cancels the ctx
rt.disposeHibernated() // cancels rt.ctx; reply already delivered
}

Expand Down Expand Up @@ -819,7 +819,7 @@ func (rt *roomRuntime) scheduleTimer(id TimerID, d time.Duration) {
}

func (h *roomHandle) Every(d time.Duration, fn func(r Room)) TimerID {
if !h.valid() {
if !h.valid() || d <= 0 {
return 0
}
rt := h.rt
Expand Down
69 changes: 69 additions & 0 deletions host/sdk/room_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,75 @@ func TestCapacityEnforced(t *testing.T) {
<-ctl.Done()
}

type timerEdgeHandler struct {
Base
everyZeroID TimerID
everyNegID TimerID
afterZeroID TimerID
calls chan string
}

func (h *timerEdgeHandler) OnStart(r Room) {
h.everyZeroID = r.Every(0, func(Room) { h.calls <- "every-zero" })
h.everyNegID = r.Every(-time.Second, func(Room) { h.calls <- "every-neg" })
h.afterZeroID = r.After(0, func(Room) { h.calls <- "after-zero" })
}

func TestEveryNonPositiveIsNoop(t *testing.T) {
h := &timerEdgeHandler{calls: make(chan string, 4)}
ctl := NewRoomRuntime("timer-edge", h, RoomConfig{Mode: ModeSolo, Capacity: 1}, Services{})
defer ctl.Close()

select {
case got := <-h.calls:
if got != "after-zero" {
t.Fatalf("first timer callback=%q, want after-zero", got)
}
case <-time.After(time.Second):
t.Fatal("After(0) did not fire")
}
if h.everyZeroID != 0 {
t.Fatalf("Every(0) id=%d, want 0", h.everyZeroID)
}
if h.everyNegID != 0 {
t.Fatalf("Every(-d) id=%d, want 0", h.everyNegID)
}
if h.afterZeroID == 0 {
t.Fatal("After(0) returned 0")
}
select {
case got := <-h.calls:
t.Fatalf("unexpected non-positive Every callback %q", got)
case <-time.After(50 * time.Millisecond):
}
}

func TestTestRoomEveryNonPositiveIsNoop(t *testing.T) {
h := &timerEdgeHandler{calls: make(chan string, 4)}
r := NewTestRoomFor(h, RoomConfig{Mode: ModeSolo, Capacity: 1}, Services{})
r.Start()
if h.everyZeroID != 0 {
t.Fatalf("Every(0) id=%d, want 0", h.everyZeroID)
}
if h.everyNegID != 0 {
t.Fatalf("Every(-d) id=%d, want 0", h.everyNegID)
}
r.Advance(0)
select {
case got := <-h.calls:
if got != "after-zero" {
t.Fatalf("first timer callback=%q, want after-zero", got)
}
default:
t.Fatal("After(0) did not fire in TestRoom")
}
select {
case got := <-h.calls:
t.Fatalf("unexpected non-positive Every callback %q", got)
default:
}
}

func recvFrame(t *testing.T, ch <-chan Frame) Frame {
t.Helper()
select {
Expand Down
3 changes: 3 additions & 0 deletions host/sdk/testroom.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ func (t *TestRoom) After(d time.Duration, fn func(r Room)) TimerID {
return t.nextID
}
func (t *TestRoom) Every(d time.Duration, fn func(r Room)) TimerID {
if d <= 0 {
return 0
}
t.nextID++
t.timers[t.nextID] = testTimer{fireAt: t.Clock.Add(d), period: d, fn: fn, every: true}
return t.nextID
Expand Down
2 changes: 1 addition & 1 deletion kit.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// the ABI's code form).
//
// A game implements Game + Handler and calls Main(game) from main(), plus the
// eight //go:export trampolines for the wasm build — run `gamekit new` for a
// eight //go:export trampolines for the wasm build — run `shellcade-kit new` for a
// working scaffold, and see GUIDE.md for the full authoring guide.
//
// This package is a curated facade over internal/game; the implementation is
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading