Skip to content

Refactor window events - #47

Merged
levovix0 merged 20 commits into
levovix0:masterfrom
elcritch:refactor-window-events
Aug 6, 2026
Merged

Refactor window events#47
levovix0 merged 20 commits into
levovix0:masterfrom
elcritch:refactor-window-events

Conversation

@elcritch

@elcritch elcritch commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

This PR adds an application-global, efficiently blocking event loop for macOS, Windows, X11, and Wayland. It reduces CPU usage vs the previous event polling design.

Applications can now wait once for all native window events, wake the loop from worker threads or external queues, and service one or more windows without fixed-delay polling. This matches the underlying event design on MacOS and Windows as well.

I worked to make this API additive while keeping the previous tick behavior and semantics. The new APIs can be leveraged for applications to gain efficient blocking loop behavior. A future a breaking release could change the default tick behavior.

I've manually verified this works and reduces CPU usage to 0% on MacOS, X11, and Wayland. The event loops are also tested in CI as well.

Other Updates

  • All the internal timing functions have been converted to use MonoTime to avoid wall clock drifts/changes.
  • Siwin Global's data uses a new shared data implementation using atomic's to work under refc / arc / orc.

New API

proc pollEvents*(globals: SiwinGlobals): bool
proc waitEvents*(globals: SiwinGlobals)
proc waitEvents*(
  globals: SiwinGlobals,
  timeout: Duration,
): EventWaitResult

proc eventLoopWaker*(globals: SiwinGlobals): EventLoopWaker
proc wake*(waker: EventLoopWaker) {.gcsafe, raises: [].}
proc wakeEventLoop*(globals: SiwinGlobals) {.gcsafe, raises: [].}

method serviceWindow*(window: Window)

EventLoopWaker is a narrow, copyable capability suitable for worker
threads. Producers enqueue their work first and then call wake;
notifications may be coalesced, while the application queue remains the
source of truth.

Timed waits can also use the next animation deadline instead of polling
every 16 ms.

Platform implementations

macOS

  • Blocks with nextEventMatchingMask.
  • Drains default, tracking/live-resize, and modal run-loop modes.
  • Wakes through a coalesced application-defined NSEvent.
  • Keeps AppKit dispatch and callbacks on the application thread.

Windows

  • Blocks with MsgWaitForMultipleObjectsEx.
  • Waits on both the Win32 message queue and an auto-reset wake event.
  • Uses SetEvent for thread-safe wakeups.

X11

  • Uses poll() on the X11 connection and a self-pipe.
  • Routes global XPending/XNextEvent batches through a window registry.
  • Preserves same-window key-repeat lookahead.
  • Does not require calling Xlib from producer threads or enabling
    XInitThreads.

Wayland

  • Uses poll() on the Wayland display, libdecor, and a self-pipe.

  • Implements the race-safe prepare_read / flush / poll / read-or-cancel
    sequence.

  • Incorporates keyboard-repeat deadlines into timed waits.

  • Loads the runtime libwayland-client.so.0 SONAME when the development
    symlink is unavailable.

Compatibility

The existing run, runMultiple, and no-argument Window.step() APIs remain
available. step() is now a compatibility wrapper around the global wait
and nonblocking window service.

No wake callback was added to WindowEventsHandler, so its existing
layout remains unchanged.

Additive C API functions are also included:

  • siwin_poll_events
  • siwin_wait_events
  • siwin_wake_event_loop
  • siwin_window_service

Examples and documentation

  • Updates text_input_demo.nim to use deadline-driven waits for cursor
    blinking and scroll decay.

  • Documents worker/queue wakeups and animation scheduling.

  • Adds a README example for the global event-loop API.

Testing

The cross-platform integration test covers:

  • Nonblocking polling and zero-duration waits.
  • Queued and coalesced wakeups.
  • Wakeups from copied wakers on worker threads.
  • Idle blocking without CPU spinning.
  • Waker lifetime safety.
  • Nonblocking serviceWindow.
  • Existing step() compatibility.

Linux CI exercises both display backends:

  • X11 under Xvfb.
  • Wayland under headless Weston.

CI passes on Ubuntu, macOS, and Windows.

Comment thread src/siwin/platforms/any/window.nim Outdated
Comment thread src/siwin/platforms/x11/window.nim Outdated
hasFollowingEvent: bool,
) =
var
ev = event

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like unnecessary copies

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the caller doesn't have var ev type since it uses an expression if nextIndex >= 0: events[nextIndex] else: XEvent(). The ev var needs to be modified to loop later.

Comment thread src/siwin/platforms/any/window.nim
Comment thread README.md Outdated
<h2 align="center">manual main cycle</h2>
<h2 align="center">efficient application event loop</h2>

`runEventDriven` is the simplest efficiently blocking loop. It waits once for native input or an explicit wake and then services every window. The existing `run` remains available for compatibility and preserves its continuously delivered `onTick` events.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readme is not a changelog. Describe how runEventDriven deals with onTick and how it is diffirent from the regular run

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Updated it.

Comment thread src/siwin/platforms/x11/siwinGlobals.nim Outdated
@elcritch
elcritch requested a review from levovix0 August 6, 2026 07:19
@levovix0
levovix0 merged commit 40ed4d4 into levovix0:master Aug 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants