Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GUI overhaul #194

Draft
wants to merge 78 commits into
base: main
Choose a base branch
from
Draft

GUI overhaul #194

wants to merge 78 commits into from

Conversation

samizdatco
Copy link
Owner

@samizdatco samizdatco commented Dec 1, 2024

Allow GUI windows to use the node event loop for timing

Adds an eventLoop property to the App global which can be set to node as well as native (the default). When using a native OS event loop, the Node event loop is frozen in place while graphical windows are active. As a result functions like setTimeout and setInterval won't fire until the final window is closed.

When using a node event loop, the app's event-loop is suspended and resumed from node, allowing for the normal use of timers within user scripts. The node event loop mode is possible due to a new feature in winit that comes with quite a few caveats, so sticking with native mode is recommended unless you really need access to the node event loop's features.

App.launch() now returns a Promise

The promise resolves when the final window is closed, so you can either await it or use a then() handler to run clean-up code before quitting (similar to the blocking behavior of launch() when in native eventLoop mode).

New events on the App global

The App object is now an event emitter with on()/off/once() methods. You can add event listeners for open and close events that are emitted when a new window is created or an old one is destroyed.

Event fixes

  • The input event (reporting new keyboard input) now distinguishes between insertions, deletion, and IME composition via its inputType attribute.
  • Repeated mouse events are no longer coalesced down to a single instance per animation frame (whose length is set by App.fps). Event listeners will now receive as many mousemove/mousedown/etc. events as were detected by the OS.
  • Setting window.cursor to "none" now correctly hides the cursor (fixing a regression in 2.0.0)
  • Spurious moved window events are no longer emitted during window resizes

- the separation isn't necessary now that there's no longer a thread boundary to be crossed
- drop CanvasEvents that were just used for passing WindowSpec updates
- proxies no longer need to be handed to window objects
- also dropped paint arg in renderer's Draw handler, meaning an intermediate compositing layer will no longer be created
- prevent them from being evicted by an in-flight roundtrip before being initialized on the rust side (using undefined top/left coords as an indicator)
- the initial event-loop run doesn't actually draw from the proxied events queue so misses any queued window creation events
- dropping a Window object runs its destructor but the os window needs another loop run to actually close
- in pump_events mode it now uses unref() on the frame timer so the event loop won't keep the process running forever
- polling for the last ~ms increases timing accuracy significantly
- pushing it into the next pass means ~2ms when using the node event loop
- using the winit loop, this can be handled by switching to Poll mode
- for the node event loop, sleep the thread rather than waiting for the next cycle (which will likely be longer than the sleep time)
- removes the need for windows to reference a preexisting GlobalApp
- UI events are delivered as they arrive, but screen updates only happen at App.fps frequency, even if no `draw` or `frame` listeners are active
- Cadence no longer considers `active` state, it always runs
- Cadence now uses `spin_sleep` crate for frame timing when using the node event loop
- switch to using request_redraw after suspension rather that redrawing immediately
- queue up a redraw when the window background color is changed, since it may show through
- the previous approach using NSView's `wantsLayer` was creating a 'layer-hosting' rather than 'layer-backed' view, disabling the AppKit redraw behavior winit expects
- can now stop requesting redraw in resize handler and rely on the automatic request
- the first format suggested by the device may not be supported (e.g., `B8G8R8A8_SRGB` in #200)
- allows it to call `pre_present_notify` at the proper time between rendering and presentation
- moved the dpr canvas scaling into the backend as well
- running them in the background was causing too many problems in scenarios like window-resizing where the OS expects the redraw to be synchronous
- skia has an internal restriction against using MSAA on intel GPUs (because of crashes, bad rendering quality, etc.) and refuses to generate MSAA surfaces even when the GPU claims it supports them
- the sample count is now clamped by the `DirectContext:: max_surface_sample_count_for_color_type` value
- there is a `ContextOptions::allow_msaa_on_new_intel` flag that can be set, but in the skia source they claim it's only reliable with D3D so it is not enabled here
- this *may* make the `VulkanContext::works` test unnecessary, but keeping it until its error message stops appearing in bug reports
- when a software renderer like `llvmpipe` is the active driver `canvas.engine.renderer` will still be `GPU` in order to allow the `canvas.gpu` toggle to select between the vulkan path and the pure-CPU path
- the `activate` rust method now runs a background thread that schedules winit event-loop cycles on the main node event loop
- App.launch() returns a promise that will resolve to `undefined` when the last active window is closed
- the 'roundtrip' callback is now registered once at startup rather than passed with every `activate` call
- most of the roundtrip packing/unpacking now lives in the `dispatch_events` helper function
- mirrors the default App.#fps
- prevents potential infinite loop where the next-frame time is updated with an uninitialized `render` duration
- the mac event loop expects the redraw method not to return until the window has been updated during resizes
- use static methods on App rather than dealing with the global RefCell
- likewise embed the active keyboard modifiers at the time of the event rather than waiting until dispatch to merge the final set in
- necessitated by switching to only delivering events at frame time rather than as they arrive (and accounting for the possibility of a low `App.fps` setting)
- defer event delivery until the next frame event (but then deliver all queued events)
- consolidate icon & visibility attrs and store the cursor in WindowSpec as a string
- let lack of a "none" value in `CursorIcon::from_str` handle creating the None option
@samizdatco samizdatco changed the title Allow GUI windows to use the node event loop for timing GUI overhaul Dec 15, 2024
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.

1 participant