# Optimizing Fabrica-Util's Delayer for Time-Sensitive Game Event Scheduling

> Optimize fabrica-util's Delayer for game events. Reuse Delayer instances, use Wait channel for lock-free expiry, and query TimeRemaining to throttle intensity without GC pauses.

- Repository: [Pantheon/fabrica-util](https://github.com/go-pantheon/fabrica-util)
- Tags: performance
- Published: 2026-03-02

---

**Reuse `xsync.Delayer` instances across game frames, select on the buffered `Wait()` channel for lock-free expiry detection, and query `TimeRemaining()` to dynamically throttle workload intensity without triggering garbage collection pauses.**

The `go-pantheon/fabrica-util` repository provides a specialized concurrency primitive designed for deterministic, high-frequency timing operations. Optimizing fabrica-util's Delayer for time-sensitive game event scheduling requires leveraging its zero-allocation reset patterns and non-blocking notification architecture to maintain consistent frame rates in demanding simulation loops.

## Core Architecture in xsync/delayer.go

The **Delayer** type in [`xsync/delayer.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/delayer.go) wraps a `time.Timer` with a thread-safe control layer. At its core, it maintains an `expiryTime` protected by a `sync.RWMutex`, allowing concurrent reads of timing state without contention.

The implementation uses a **buffered channel** (`tick chan struct{}`) with capacity 1 to signal expiry. This buffering is critical: when the underlying `time.Timer` fires, the send operation on `tick` never blocks, preventing goroutine leaks even if the consumer is temporarily busy. A separate `stopCh` and `stopped` flag coordinate graceful termination between the public API and the internal expiry-handling goroutine.

Key methods include:
- **`SetExpiryTime(t time.Time)`** – Atomically updates the target time and resets the internal timer. If `t` is in the past, it emits a tick immediately.
- **`Wait() chan struct{}`** – Returns the receive-only tick channel for use in `select` statements.
- **`Reset()`** – Stops the pending timer, clears the expiry state, and marks the Delayer as stopped without deallocating the structure.
- **`Close()`** – Like `Reset`, but additionally signals the internal goroutine via `stopCh` to terminate permanently.

## Why Delayer Excels for Game Loops

Standard library timers often force a trade-off between allocation overhead (creating new timers per frame) or complex lifecycle management (stopping and draining channels). The Delayer eliminates this through **fast reuse** via `Reset()`, avoiding GC pressure in 60Hz or 120Hz loops where per-frame allocations cause stutter.

The design provides **low-latency notification** through the buffered `tick` channel. Unlike unbuffered channels that require both sender and receiver to be ready, Delayer's size-1 buffer ensures the timer callback completes instantly, moving synchronization overhead to the consumer side where `select` blocks efficiently.

Concurrent safety is enforced through strategic locking in [`xsync/delayer.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/delayer.go). The `RWMutex` allows multiple goroutines to call `IsExpired()` or `TimeRemaining()` simultaneously, while exclusive locks protect timer resets. This pattern is validated by the race detector tests in [`xsync/delayer_test.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/delayer_test.go).

## Optimization Strategies for High-Frequency Scheduling

### Reuse Delayers to Eliminate GC Pressure

Allocate Delayers once during system initialization, then reset them each frame. This pattern prevents the garbage collector from pausing your game loop to reclaim timer objects.

```go
// Initialize once at startup
frameTimer := xsync.NewDelayer()
defer frameTimer.Close()

// Main loop at 60 FPS (16.67ms)
for {
    targetTime := time.Now().Add(16 * time.Millisecond)
    frameTimer.SetExpiryTime(targetTime)

    select {
    case <-frameTimer.Wait():
        updateGameState()
        renderFrame()
    case <-shutdownCh:
        return
    }
}

```

### Leverage TimeRemaining for Adaptive Workloads

When frame budgets vary, use `TimeRemaining()` to scale AI complexity or physics fidelity dynamically. This prevents frame overruns that cause visible stuttering.

```go
frameTimer.SetExpiryTime(time.Now().Add(16 * time.Millisecond))

// Guaranteed work
processInput()
updateAnimations()

// Optional work based on budget
if frameTimer.TimeRemaining() > 5*time.Millisecond {
    runExpensivePathfinding()
} else {
    runSimplePathfinding()
}

<-frameTimer.Wait() // Synchronize to frame boundary

```

### Implement Safety Checks with IsExpired

For long-running operations that might exceed their time slice, poll `IsExpired()` to implement cooperative cancellation. This is crucial for procedurally generated content or complex physics calculations that must yield to the next frame.

```go
deadline := xsync.NewDelayer()
deadline.SetExpiryTime(time.Now().Add(8 * time.Millisecond))

for _, entity := range worldEntities {
    if deadline.IsExpired() {
        break // Defer remaining work to next frame
    }
    updateEntityPhysics(entity)
}

```

### Ensure Graceful Shutdown with Close()

Always invoke `Close()` when terminating game servers or unloading levels. This drains the internal `stopCh` and terminates the background goroutine, preventing resource leaks during hot-reloads or level transitions.

```go
func (s *Server) Shutdown() {
    s.tickTimer.Close() // Signals stopCh and cleans up
    s.wg.Wait()
}

```

## Production Code Examples

### Fixed-Rate Physics Tick (20 Hz)

For deterministic simulation steps independent of frame rate, use a consistent tick duration:

```go
const physicsRate = 50 * time.Millisecond

physicsTimer := xsync.NewDelayer()
defer physicsTimer.Close()

for {
    physicsTimer.SetExpiryTime(time.Now().Add(physicsRate))
    
    <-physicsTimer.Wait()
    stepPhysicsSimulation()
}

```

### One-Shot World Event Scheduling

For sporadic events like boss spawns or weather changes, Delayer provides cleaner semantics than raw timers:

```go
bossSpawn := xsync.NewDelayer()
defer bossSpawn.Close()

bossSpawn.SetExpiryTime(time.Now().Add(30 * time.Second))

go func() {
    <-bossSpawn.Wait()
    spawnWorldBoss()
}()

```

### Concurrent State Monitoring

Multiple systems can safely query timing state without synchronization overhead:

```go
// System A: Rendering
if frameTimer.TimeRemaining() < 2*time.Millisecond {
    skipParticleEffects()
}

// System B: Networking
if frameTimer.IsExpired() {
    sendQueuedPackets()
}

```

## Benchmarking and Validation

The repository includes targeted benchmarks in [`xsync/delayer_test.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/delayer_test.go) to verify performance under load. Run `BenchmarkDelayer_SetExpiryTime` to measure the latency of timer resets, and `BenchmarkDelayer_ConcurrentAccess` to validate that read-heavy workloads (multiple goroutines calling `IsExpired()`) scale without mutex contention.

Use these benchmarks to determine the maximum concurrency your game loop can sustain before timing precision degrades. The read-write mutex design typically supports thousands of concurrent reads per millisecond, sufficient for MMO-scale entity management.

## Summary

- **Reuse instances**: Call `Reset()` between frames rather than allocating new Delayers to eliminate GC pressure in tight loops.
- **Non-blocking waits**: Select on `Wait()` to receive instant notifications via the buffered channel architecture.
- **Dynamic scaling**: Query `TimeRemaining()` to adjust workload complexity and maintain consistent frame times.
- **Safe termination**: Always invoke `Close()` during shutdown to prevent goroutine leaks.
- **Validate performance**: Reference `BenchmarkDelayer_ConcurrentAccess` in [`xsync/delayer_test.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/delayer_test.go) to ensure your concurrency model matches production load.

## Frequently Asked Questions

### How does Delayer prevent goroutine leaks compared to standard time.Timer?

Delayer uses a **buffered tick channel with capacity 1** in [`xsync/delayer.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/delayer.go), ensuring that when the underlying `time.Timer` fires, the send operation completes immediately without blocking the timer's internal goroutine. Additionally, the `Close()` method signals a dedicated `stopCh` that terminates the expiry-monitoring goroutine, whereas raw timers require manual stopping and channel draining to avoid leaks.

### Can multiple goroutines safely check timer status simultaneously?

Yes. The implementation guards `expiryTime` with a `sync.RWMutex`, allowing concurrent calls to `IsExpired()` and `TimeRemaining()` without blocking each other. Only write operations like `SetExpiryTime()` acquire exclusive locks, making the Delayer suitable for read-heavy game architectures where hundreds of entities query timing state concurrently.

### What is the difference between Reset() and Close()?

`Reset()` stops the active timer and clears the expiry state, preparing the Delayer for immediate reuse while keeping the internal goroutine alive. `Close()` performs the same cleanup but additionally writes to `stopCh`, signaling the background goroutine to terminate permanently. Use `Reset()` between frames in recurring loops, and `Close()` only when permanently discarding the Delayer instance.

### How precise is SetExpiryTime for sub-millisecond scheduling?

`SetExpiryTime` offers microsecond-level precision limited only by the Go runtime's timer resolution and OS scheduling. When passed a timestamp in the past, it emits a tick immediately through the buffered channel. For physics simulations requiring sub-millisecond accuracy, pair Delayer with `time.Now()` and `time.Since()` calculations, validating timing variance using the benchmarks provided in [`xsync/delayer_test.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/delayer_test.go).