class Termisu::Event::Loop

Overview

Central event loop multiplexer.

Manages multiple Event::Source instances and provides a unified output channel for all events. Handles lifecycle management and graceful shutdown.

Usage

loop = Termisu::Event::Loop.new

# Add event sources
loop.add_source(input_source)
loop.add_source(resize_source)
loop.add_source(timer_source)

# Start all sources
loop.start

# Receive events from unified channel
while event = loop.output.receive?
  case event
  when Termisu::Event::Key
    break if event.key.escape?
  when Termisu::Event::Tick
    # Handle animation frame
  end
end

# Stop all sources and cleanup
loop.stop

Thread Safety

The Event::Loop uses Atomic(Bool) for thread-safe state management. All public methods are safe to call from multiple fibers.

Shutdown Behavior

When #stop is called:

  1. Running state is set to false (atomic)
  2. All sources are stopped
  3. Brief wait for fibers to exit gracefully
  4. Output channel is closed

The shutdown timeout prevents hanging on misbehaving sources.

Defined in:

termisu/event/loop.cr

Constant Summary

DEFAULT_BUFFER_SIZE = 32

Default event channel buffer size. 32 events ~= 0.5 seconds of activity at 60 FPS.

Log = Termisu::Logs::Event
SHUTDOWN_TIMEOUT_MS = 100

Graceful shutdown timeout in milliseconds. Sources have this long to finish before channel is closed.

Constructors

Instance Method Summary

Constructor Detail

def self.new(buffer_size : Int32 = DEFAULT_BUFFER_SIZE) #

Creates a new Event::Loop with the specified buffer size.

The buffer size determines how many events can be queued before send operations block. Larger buffers reduce blocking but use more memory.


[View source]

Instance Method Detail

def add_source(source : Source) : self #

Adds an event source to the loop.

If the loop is already running, the source is started immediately. Otherwise, it will be started when #start is called.

Returns self for method chaining.


[View source]
def output : Channel(Any) #

Returns the event output channel.

Use this channel to receive events from all sources:

while event = loop.output.receive?
  handle(event)
end

[View source]
def remove_source(source : Source) : self #

Removes an event source from the loop.

If the source is running, it is stopped before removal. Removing a non-existent source is a no-op.

Returns self for method chaining.


[View source]
def running? : Bool #

Returns true if the event loop is currently running.


[View source]
def source_names : Array(String) #

Returns the names of all registered sources.

Useful for logging and debugging.


[View source]
def start : self #

Starts the event loop and all registered sources.

Each source begins producing events to the shared output channel. This is a non-blocking operation - sources run in their own fibers.

Returns self for method chaining.


[View source]
def stop : self #

Stops the event loop and all sources.

Performs graceful shutdown:

  1. Sets running state to false
  2. Stops all sources
  3. Waits briefly for fibers to exit
  4. Closes the output channel

This unblocks any receivers waiting on the channel. Safe to call multiple times (idempotent).

Returns self for method chaining.


[View source]