hal/sdl/canvas.lisp
NIL width or height means "whatever suits the display this window opens on"; the native size is resolved once, at window creation, when SDL can finally be asked about the desktop.
The loop publishes where it is and when it got there. Everything the watchdog knows, and everything ./sly status reports about a window that has stopped answering, is read out of these three slots.
What has gone wrong on this loop, kept: the newest first, each with the backtrace it was caught with. A frame failure also parks here, in FRAME-FAILURE, and holds further frames until it is resumed.
Run body with the floating-point environment expected by native drivers.
SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV.
The signals SBCL's runtime handles itself, by number.
SDL's KMSDRM backend mutes the console keyboard and registers an emergency restore handler over every fatal signal, SIGSEGV included. SBCL takes ordinary SIGSEGVs as part of running Lisp -- garbage collection's write barrier and the control stack guard both arrive that way -- and SDL's handler answers one by re-raising it with PTHREAD_KILL, whose SIGINFO carries no faulting address at all. What reaches SBCL is then a memory fault at (UID << 32) | PID: a CORRUPTION WARNING and a MEMORY-FAULT-ERROR in place of a collection.
Room for one struct sigaction, which is 152 bytes on x86-64 Linux.
The bytes are only ever moved between the kernel and this buffer.
Call function, restoring SBCL's own fatal-signal handlers afterwards.
Wrap the SDL calls that may install console-keyboard cleanup handlers: the muting itself is worth keeping on a virtual console, but the handlers are not SDL's to take. SDL_Quit and the process's own exit still restore the keyboard.
Whether this process has a Linux virtual console as its controlling terminal.
Choose KMSDRM on a real console, otherwise a safe headless SDL backend.
An explicit SDL_VIDEODRIVER, DISPLAY, or WAYLAND_DISPLAY always wins. This must run before SDL video initialization: it covers standalone executables as well as processes started through the Nix development shell.
Call function where synchronous SDL canvas work can use the native main thread.
On Darwin, a batch Lisp normally evaluates its toplevel form on the process
main thread. Opening a canvas from that continuation would let Cocoa replace
the continuation with its durable event loop. This boundary moves function
to a worker while the calling thread runs TRIVIAL-MAIN-THREAD, then restores
the caller with function's values or condition after native teardown. Calls
from an existing worker (including SLY and standalone program workers) already
have a main-thread host and execute directly.
Prepare the native application host before SDL_Init.
Show and activate canvas after its SDL window exists.
Release canvas's native application presence after SDL_Quit.
Claim any process-global native host required before canvas starts.
Release a host previously claimed for canvas.
Raising is a request to the window manager and may legitimately be denied by focus-stealing policy, so it is not an open failure.
cl-sdl3's KEYCODE enum is currently incomplete (and mistypes ]), while SDL keycodes are Unicode values by design. Keep the raw integer at this boundary.
Return the immutable SDL window flags required by presentation-api.
Return the SDL presentation policy required by GPU provider.
On macOS a point already names a sufficiently fine game-rendering pixel. Asking SDL for high density doubles both drawable axes on Retina displays, quadrupling scene work and making point-sized embedded UI unnecessarily small. Other platforms retain their native high-density drawable.
The SDL window flags canvas needs: its presentation API plus its own state.
The widest shape an unsized window takes before it stops following the display. An ultrawide desktop is a place to put several windows, not a reason to open one 3.6:1 window nobody can look across.
Return a comfortable window size for the primary display, in points.
SDL_GetDisplayUsableBounds already excludes the menu bar and the dock, so a fraction of it is a window that fits wherever the desktop actually is. The height leads and the width follows it at an ordinary aspect, never wider than the same fraction of the desktop. The answer is in logical points, which is what SDL_CreateWindow wants: a Retina window is the same physical size as its low-density twin and simply resolves finer, so the density belongs in the drawable rather than in this number.
Use the active display mode required by SDL's direct-display Vulkan WSI.
Unlike a desktop window system, KMSDRM presents Vulkan through VK_KHR_display. SDL can only create that surface at a mode the display actually advertises; arbitrary window dimensions commonly fail in SDL_Vulkan_CreateSurface.
Fill in whichever of canvas's dimensions were left for the display to pick.
Whether canvas is hosted directly by SDL's KMSDRM video driver.
Return canvas's current display refresh rate, or NIL when SDL omits it.
Drawable acquisition has already happened when this is queried. A cadence frame is intended for its following beat; direct display is paced by FIFO image release and uses the panel's real refresh interval.
Acquisition may have blocked since the event loop cached its coherent NOW. Prediction starts from a fresh underlying sample, then the backend temporarily overrides logical time with it.
Construct an unrealized SDL canvas.
width or height may be NIL, which asks the display for a size when the window
is finally created.
Return the native-loop failure callers need, preserving its original type.
Wake canvas's native SDL event loop after cross-thread work arrives.
Bounded, because the alternative is not "wait a little longer" but "hang forever with nothing on screen". The canvas thread services this queue once a frame; if it has stopped doing that, no amount of waiting will help and every caller after this one queues behind the same block.
Top-level placement is only a request: Wayland and some other window managers are entitled to reject it.
The clipboard belongs to the video subsystem, which only answers on the thread that initialized it.
The new extent arrives as an ordinary resize event; letting SDL
settle here keeps canvas-size honest for a caller that looks
immediately after toggling.
Dynamic bindings do not follow a closure onto another thread. Preserve the opt-in trace explicitly so a caller can measure the real native frame rather than only the time spent waiting for its request. #OHNIWM
What went wrong, kept.
The pump itself must not die of application errors: a frame that throws,
a key handler that throws, a paint that throws. Each of those runs under
a guard that catches the condition with its backtrace still standing,
retains it on the canvas, logs it, and lets the loop go on servicing the
window. A failed frame parks the frame clock -- rendering into a broken
world every 16 ms would only bury the evidence -- until someone fixes the
cause and asks for resume-canvas-frames. Anyone who changed the world
and wants to know what the next frame made of it can fence-canvas and
read what was retained since.
One error caught on a canvas loop, with everything needed to read it later.
A count of every failure retained on any canvas, so a caller can mark a moment and later ask what failed after it.
Every SDL canvas whose loop is running, so a fence or a hold can find them without being handed one.
The canvases whose native loops are running right now.
Frames at which a retained backtrace stops: below them is the pump and the image's own toplevel, the same in every failure and never the point.
The current backtrace as text, from the frame that asked for it down to the pump: forms abbreviated, the frames of the capture itself left out.
Keep condition, caught in phase on canvas with backtrace, and log it.
Every retained failure on any open canvas newer than serial, oldest first.
The failure serial as of now: pass it later to canvas-failures-since.
Print failure for a person: when, where, what, and the backtrace.
&~2,'0D:~2,'0D:~2,'0D canvas ~S failed in ~(~A) ~
(loop iteration ~D):~% ~A~%
Run body on canvas's loop in PHASE; an error is retained, not raised.
Returns body's values, or NIL and the failure as a second value.
Let canvas run frames again after a frame failure parked them.
Stop canvas running frames or delivering events; the window is still
pumped. For the duration of a redefinition the loop must not run through.
Call function with every canvas in canvases holding its frames and events.
Wait until canvas has run frames more frames -- or, with no frame clock,
come round its loop twice more -- and return :DONE; :FAILED as soon as a
frame failure parks it; :CLOSED if it closes; :timeout after timeout
seconds. This is how a caller who just changed the world finds out what
the next frame made of the change.
fence-canvas every open canvas; an alist of canvas to outcome.
Turn one SDL wheel event into a canvas one.
SDL reports a flipped direction rather than flipped amounts when the platform is set to natural scrolling, so the sign is corrected here and consumers only ever see what the user meant.
The struct reader hands the direction back as the enum's keyword when it can translate it and as the raw integer when it cannot, so both spellings of "flipped" are honoured; anything else is normal.
Translate SDL's physical key names into luv's portable vocabulary.
Translate SDL's left/right modifier bitfield into logical modifiers.
SDL's KMSDRM backend never learns the console's keymap, so a Dvorak console gets QWERTY characters from SDL_GetKeyFromScancode. The layout override translates scancodes itself; keys it does not list (digits, space, editing keys) fall through to SDL, whose QWERTY answer matches.
Whether the Caps Lock key acts as Control, as the console's
ctrl:swapcaps option intends: T, NIL, or :ENVIRONMENT to read
luv_KEYBOARD_SWAP_CAPS_CONTROL at first use. The physical Control
keys stay Control; nobody wants Caps Lock in a game.
Whether the Caps Lock key, remapped to Control, is currently held. SDL's modifier state reports Caps Lock as a toggle rather than a held key, so the swap has to track the key itself.
Logical modifiers for modifiers with the Caps-as-Control swap applied.
Return scancode's character under the override layout, or NIL to ask SDL.
Return scancode's character under the configured layout and modifiers.
Read fields directly from SDL_Event. Materializing cl-sdl3's
KEYBOARD-EVENT would translate its KEY slot through the broken enum even
though luv intentionally derives characters from SCANCODE and mod.
Offer a native close to canvas and apply the default immediate policy.
An application returning :DEFER-CANVAS-CLOSE has begun orderly teardown on
another thread; its eventual close-canvas call ends the loop. This lets it
release resources layered above the presentation context before SDL destroys
that context. Every other handler retains the ordinary native close policy.
Publish SDL's current logical size when it changed without RESIZED first.
Keep the raw event tag: SDL_RegisterEvents may return a value absent from cl-sdl3's static enum. Decode only the native events we understand.
macOS Command-Q is SDL_EVENT_quit rather than a window-close event.
Give it the identical application teardown path.
Wayland may report the new pixel extent before the corresponding logical resize. Querying here keeps layout ahead of repaint.
Where the loop is, published for anyone who wants to know whether it is still moving. Two slot writes per phase: this runs on the thread whose responsiveness is the whole question, so it may not allocate, lock, or call out.
Record that canvas's native loop has just entered phase.
The time is written first: a reader that catches the pair mid-update then sees the old phase with a fresh clock, and so underestimates a stall by one phase rather than inventing one.
The longest a canvas ever stays inside SDL waiting for an event.
A loop that can block indefinitely cannot be observed: it looks exactly like a loop that has died, and a demand-clock canvas used to do precisely that by asking SDL_WaitEvent for no deadline at all. Every wait is now a step this long at most, so the loop always comes up for air, always republishes where it is, and a watchdog can tell a parked canvas from a wedged one by the only evidence that matters: whether the loop came back.
Handling belongs to its own phase: an event handler runs application code -- a keystroke into the terminal wall, a click into an overlay -- and is a place the loop can be lost for reasons that have nothing to do with waiting.
How many milliseconds this iteration may spend inside SDL's event wait.
Service the clock once, under guard. A frame that fails parks the clock in FRAME-FAILURE; a frame that runs counts.
Held or parked, the loop still pumps the window; it only stops running the application through it.
The next event or loop turn deserves a fresh real time. If an event handler asks first, its answer remains stable through the following request and frame phases.
The watchdog.
A window whose loop has stopped is not merely idle: macOS paints the beachball over it, it answers nothing, and the only way out is finding the process and killing it by hand. Every stall reaching the desktop this way has cost someone a hunt, so the loop is now watched from a thread that cannot be caught by the same block -- the phases above are all bounded, so a phase that stops advancing means the loop is gone, and that is a fact rather than a guess.
Three deadlines, each doing strictly more than the last: say so, take a native sample while the evidence still exists, and finally end the process so that no beachballing window outlives the Lisp that made it.
How long one phase may last before the watchdog starts saying so.
Nothing in a healthy loop takes this long: a frame is milliseconds, a
request is serviced within one, and a wait is capped by
*CANVAS-EVENT-WAIT-SLICE*. Two seconds is already far past anything that
has ever been legitimate here.
How long a stall lasts before the watchdog samples the process natively.
The sample is the part that is impossible to recover afterwards: once the image is killed, or restarted by whoever notices the window, the stack that would have named the deadlock is gone.
How long a stall lasts before the image ends itself, or NIL to never.
A wedged canvas thread on Darwin is the main thread, so the image is already unusable: every canvas call times out and the window answers nothing. Dying is what keeps the desktop clean, and ./sly start brings back a working image in seconds.
A function of no arguments; true means never end the process.
An attached debugger stopped inside a frame callback is a stall by every measure this file can take, and is also exactly what someone debugging the render loop wants. SLY-SERVER.LISP points this at a live Slynk connection check, so an image someone is holding open only ever gets logged at.
Whether phase is one the event loop guarantees to leave promptly.
How long canvas may stall before the image ends itself, or NIL for never.
Only a canvas with a window on screen is worth dying over: that window is the thing that beachballs, outlives its Lisp in the user's face, and has to be hunted down and killed by hand. A hidden canvas -- a capture, a benchmark, a smoke test -- is watched and sampled just the same, but a slow first frame there is somebody's build, not somebody's desktop.
Write a native sample of this process while the stall is still happening.
canvas ~S has been in ~S for ~,1F s; ending this image so its window ~
does not outlive it. Bind luv:*canvas-watchdog-fatal-seconds* to NIL to keep ~
a stalled image for inspection.
Unwinding would need the very thread that is stuck, and every ordinary teardown path goes through it. Leave abruptly instead: the window dies with the process, which is the entire point.
Watch canvas's native loop until it closes, escalating while it is stuck.
The watchdog leaves on its own once the state is no longer open; it only ever sleeps for an interval, so this join is bounded by that.
KMSDRM's console keyboard support installs its own fatal-signal handlers here.
Wayland and some other window managers deliberately deny applications control over top-level placement.
Teardown is deliberately outside the watchdog's reach: it runs on this same thread with the loop already gone, so its phases would look like a stall, and killing the image in the middle of releasing a device would trade a clean window for a dirty exit.
The window is gone from SDL; give the native loop a few turns so the desktop learns it too, rather than leaving a ghost that beachballs until the process ends.
If event dispatch or rendering killed the native loop, wake every synchronous caller with that exact condition. Replacing it with a generic :CANVAS-CLOSED made the actionable error available only by inspecting SDL-CANVAS-STARTUP-ERROR after the fact.
close-canvas's completion is also permission for its caller to open
a replacement. Publish native-host release before waking it.
Keep a rejected host claim out of the :OPENING state. In particular,
callers such as realize-mirror may reasonably attempt cleanup after
open-canvas signals; close-canvas must not wait for a thread that was
never started.
A native destination with a lifetime and frame clock.
A MuPDF context: its allocator, its store, and its error stack.
A GPU presentation relationship configured for a canvas.
(&body body)Logical conjunction of tests and raw truth values.
Logical negation of one test or raw truth value.
Logical disjunction of tests and raw truth values.
The signals SBCL's runtime handles itself, by number. SDL's KMSDRM backend mutes the console keyboard and registers an emergency restore handler over every fatal signal, SIGSEGV included. SBCL takes ordinary SIGSEGVs as part of running Lisp -- garbage collection's write barrier and the control stack guard both…
Room for one struct sigaction, which is 152 bytes on x86-64 Linux. The bytes are only ever moved between the kernel and this buffer.
(function)Call FUNCTION, restoring SBCL's own fatal-signal handlers afterwards. Wrap the SDL calls that may install console-keyboard cleanup handlers: the muting itself is worth keeping on a virtual console, but the handlers are not SDL's to take. SDL_Quit and the process's own exit still restore the keyboard.
Multiplication and scalar scaling.
(&body body)()Choose KMSDRM on a real console, otherwise a safe headless SDL backend. An explicit SDL_VIDEODRIVER, DISPLAY, or WAYLAND_DISPLAY always wins. This must run before SDL video initialization: it covers standalone executables as well as processes started through the Nix development shell.
(function)Call FUNCTION where synchronous SDL canvas work can use the native main thread. On Darwin, a batch Lisp normally evaluates its toplevel form on the process main thread. Opening a canvas from that continuation would let Cocoa replace the continuation with its durable event loop. This boundary moves FUNCTION to a…
(canvas)Release CANVAS's native application presence after SDL_Quit.
(canvas)Claim any process-global native host required before CANVAS starts.
(class key-name &key character modifiers repeat-p)(presentation-api)Return the immutable SDL window flags required by PRESENTATION-API.
(provider)Return the SDL presentation policy required by GPU PROVIDER.
(canvas)How much of a display's usable area an unsized window asks for.
The widest shape an unsized window takes before it stops following the display. An ultrawide desktop is a place to put several windows, not a reason to open one 3.6:1 window nobody can look across.
The window size to open when SDL cannot describe any display.
()Return a comfortable window size for the primary display, in points. SDL_GetDisplayUsableBounds already excludes the menu bar and the dock, so a fraction of it is a window that fits wherever the desktop actually is. The height leads and the width follows it at an ordinary aspect, never wider than the same fraction…
The maximum of compatible quantities.
The minimum of compatible quantities.
(canvas)Use the active display mode required by SDL's direct-display Vulkan WSI. Unlike a desktop window system, KMSDRM presents Vulkan through VK_KHR_display. SDL can only create that surface at a mode the display actually advertises; arbitrary window dimensions commonly fail in SDL_Vulkan_CreateSurface.
(canvas)(canvas)(canvas function)(canvas)Division of two represented quantities.
Convert one scalar float or unsigned value to a 32-bit float.
(canvas)Predict when a frame acquired now will become visible on CANVAS.
A policy object deciding when a canvas should run frames.
A clock which calls a frame function at a regular cadence.
A clock paced by presentation availability inside its frame function. It requests exactly one frame per event-loop turn. A direct-display backend blocks acquiring that frame until FIFO scanout releases an image, so no independent host timer competes with the display's cadence.
(canvas)Addition over compatible quantities.
(&key (title "luv canvas") (width 800) (height 600)
x y (visible-p t) (fullscreen-p nil)
(high-pixel-density-p nil)
(clock (make-demand-clock))
(time (make-lazy-clock))
(presentation-api :vulkan))Construct an unrealized SDL canvas. WIDTH or HEIGHT may be NIL, which asks the display for a size when the window is finally created.
(&key (source #'monotonic-seconds))(canvas enabled)Capture or release relative pointer motion for CANVAS.
(canvas)Return true when the caller is CANVAS's native event/frame thread.
(canvas)(name-and-options lambda-list &body body)Define a function whose complete dynamic extent is an ambient trace zone. The inferred name is PACKAGE/FUNCTION. Use (NAME :ZONE ZONE-NAME) to provide a more semantic name and :VALUE FORM to attach a Tracy work count.
(canvas condition)(canvas)(canvas)How long a cross-thread canvas call waits before deciding it is stuck. Work handed to the canvas thread is serviced once a frame, so it completes in milliseconds whenever that thread is running at all. Anything approaching this many seconds does not mean slow, it means the thread is not coming back -- and a caller…
A cross-thread canvas call gave up rather than hang. This is deliberately an error and not a longer wait. A silent unbounded wait on the canvas thread is indistinguishable from a crash and destroys the one thing an interactive image is for.
(canvas operation function)(canvas)The text on the system clipboard, or NIL when there is none or the window system has no clipboard to ask.
(canvas enabled)Give CANVAS its whole display, or hand it back to the window manager.
(canvas function)Run FUNCTION with a timestamp on CANVAS's native frame/event thread. The initial native implementation is synchronous: the caller waits for the function's values. The protocol leaves room for a real frame scheduler.
The current opt-in CPU trace, or NIL on the ordinary execution path.
(canvas)(canvas)(canvas &key (frames 1) (timeout 5.0))Wait until CANVAS has run FRAMES more frames -- or, with no frame clock, come round its loop twice more -- and return :DONE; :FAILED as soon as a frame failure parks it; :CLOSED if it closes; :TIMEOUT after TIMEOUT seconds. This is how a caller who just changed the world finds out what the next frame made of the…
One error caught on a canvas loop, with everything needed to read it later.
A count of every failure retained on any canvas, so a caller can mark a moment and later ask what failed after it.
How many failures a canvas keeps; the oldest are forgotten.
How many frames of backtrace a retained failure carries.
Every SDL canvas whose loop is running, so a fence or a hold can find them without being handed one.
(canvas)(canvas)Frames at which a retained backtrace stops: below them is the pump and the image's own toplevel, the same in every failure and never the point.
(&optional (depth *canvas-failure-backtrace-depth*))The current backtrace as text, from the frame that asked for it down to the pump: forms abbreviated, the frames of the capture itself left out.
Test whether two compatible scalars are equal.
Test whether one compatible scalar is less than another.
(canvas phase condition backtrace)(category control &rest arguments)Write one timestamped line about CATEGORY, formatted from CONTROL. CATEGORY is a keyword naming the subsystem -- :canvas, :watchdog, :vulkan -- so a reader can tell at a glance which machine is talking.
(canvas)(serial)Test whether one compatible scalar is greater than another.
(failure &optional (stream *standard-output*))((canvas phase) &body body)Run BODY on CANVAS's loop in PHASE; an error is retained, not raised. Returns BODY's values, or NIL and the failure as a second value.
(canvas)Stop CANVAS running frames or delivering events; the window is still pumped. For the duration of a redefinition the loop must not run through.
(canvas)(function &optional (canvases (open-canvases)))(() &body body)A clock which owns a continuously animated frame function.
Test whether one compatible scalar is at least another.
(&key (frames 1) (timeout 5.0))(canvas event)(canvas window-id)(button)(canvas event class)(canvas event)(canvas event class)(canvas event)Turn one SDL wheel event into a canvas one. SDL reports a flipped direction rather than flipped amounts when the platform is set to natural scrolling, so the sign is corrected here and consumers only ever see what the user meant.
A scroll, carrying where the pointer was and how far the wheel turned. The amounts are in wheel notches rather than pixels, positive up and right, already corrected for a natural-scrolling platform -- what the window system says the user asked for, not what the hardware reported.
(canvas event class)(scancode)(modifiers)Dvorak characters by physical scancode; unlisted keys match QWERTY.
The US shift pairs, which Dvorak shares for its printing characters.
Character layout override: :DVORAK, NIL to trust SDL's keymap, or :ENVIRONMENT to read LUV_KEYBOARD_LAYOUT at first use.
How the children of a list are arranged.
Whether the Caps Lock key acts as Control, as the console's ctrl:swapcaps option intends: T, NIL, or :ENVIRONMENT to read LUV_KEYBOARD_SWAP_CAPS_CONTROL at first use. The physical Control keys stay Control; nobody wants Caps Lock in a game.
Whether the Caps Lock key, remapped to Control, is currently held. SDL's modifier state reports Caps Lock as a toggle rather than a held key, so the swap has to track the key itself.
(modifiers)(scancode modifiers)(scancode modifiers)Test whether one compatible scalar is at most another.
The non-negative remainder of integer division.
Headings, paragraphs, figures and their IDs, mentions, marks.
(canvas event class)(canvas event class)(canvas timestamp)Offer a native close to CANVAS and apply the default immediate policy. An application returning :DEFER-CANVAS-CLOSE has begun orderly teardown on another thread; its eventual CLOSE-CANVAS call ends the loop. This lets it release resources layered above the presentation context before SDL destroys that context. …
(canvas timestamp)(canvas)(canvas phase)(canvas)Subtraction or unary negation.
The longest a canvas ever stays inside SDL waiting for an event. A loop that can block indefinitely cannot be observed: it looks exactly like a loop that has died, and a demand-clock canvas used to do precisely that by asking SDL_WaitEvent for no deadline at all. Every wait is now a step this long at most, so the…
(canvas timeout)(canvas timestamp)(clock timestamp)Return milliseconds until CLOCK is due, or NIL to wait indefinitely.
Service the clock once, under guard. A frame that fails parks the clock in FRAME-FAILURE; a frame that runs counts.
(clock canvas timestamp)Run any frame CLOCK has made due at TIMESTAMP; true if one ran.
(canvas)True on a canvas loop while its frames are held or parked by a failure: the window is still pumped, but no event reaches the application.
(canvas)How often the watchdog looks at the canvas it is watching.
How long one phase may last before the watchdog starts saying so. Nothing in a healthy loop takes this long: a frame is milliseconds, a request is serviced within one, and a wait is capped by *CANVAS-EVENT-WAIT-SLICE*. Two seconds is already far past anything that has ever been legitimate here.
How long a stall lasts before the watchdog samples the process natively. The sample is the part that is impossible to recover afterwards: once the image is killed, or restarted by whoever notices the window, the stack that would have named the deadlock is gone.
How long a stall lasts before the image ends itself, or NIL to never. A wedged canvas thread on Darwin is the main thread, so the image is already unusable: every canvas call times out and the window answers nothing. Dying is what keeps the desktop clean, and ./sly start brings back a working image in seconds.
A function of no arguments; true means never end the process. An attached debugger stopped inside a frame callback is a stall by every measure this file can take, and is also exactly what someone debugging the render loop wants. SLY-SERVER.LISP points this at a live Slynk connection check, so an image someone is…
(canvas)How long CANVAS may stall before the image ends itself, or NIL for never. Only a canvas with a window on screen is worth dying over: that window is the thing that beachballs, outlives its Lisp in the user's face, and has to be hunted down and killed by hand. A hidden canvas -- a capture, a benchmark, a smoke test --…
(canvas)Return how long CANVAS's loop has been in one phase past its deadline. NIL means the loop is healthy: either it is cycling, or it is parked in a bounded wait that the window system is pumping for it.
(canvas)Return a plist describing whether CANVAS's native loop is still alive. The keys are :STATE, :PHASE, :PHASE-SECONDS, :TICKS, and :STALLED-P. A caller reads this to answer the question a beachballing window raises -- is anything servicing that window at all -- without attaching a debugger.
(canvas seconds)(canvas seconds)(canvas)(canvas)(canvas)(canvas)(canvas)((port luv-port) (sheet mirrored-sheet-mixin))Intent: make a coarse frame explanation one ordinary benchmark away, with an opt-in macro whose disabled path neither constructs events nor changes backend control flow. Evidence: – The trace buffer records a zone name, parent, depth, monotonic start/end ticks, allocated bytes, garbage-collection time, and collections…
SDL realization of the native canvas protocol.