I am trying to understand Racket's GUI windowing model. I have tried reading the documentation and digging through bits of the code to understand how both windowing and event spaces are handled, but it is rather complex. There also seems to be a lot history, since it seems Racket originaly used wxWidgets, then bindings to the invidiual native platform window APIs, and now uses GTK for windowing and Cairo for 2D graphics, if I understand correctly from this blog post: Rebuilding Racket’s Graphics Layer (racket-lang.org).
I am wanting to better understand how Racket is doing everything under the hood, if even at just a high level. Additionally, I am writing bindings to GLFW in another language, and I want to recreate how cleanly Racket handles windowing and events on those windows. What I want is a system that:
- Allows the creation of a window from any actual thread
- Allows any window to have separate event handlers (potentially zero, one, or multiple) from other windows. In other words, each window can be basically its own standalone application within one program.
(It's my understanding this is how Racket's windowing and event spaces work. For what it's worth, this is also basically how LabVIEW's window and event handling works.)
However, one thing I am struggling with is how to manage the limitation that windows must be created by the main thread in a program. For example, from SDL's documentation:
You should not expect to be able to create a window, render, or receive events on any thread other than the main one.
From GLFW's glfwCreateWindow
docs:
This function must only be called from the main thread.
Thus, a lot of windowing libraries, like Windows Forms, and WPF, GTK, etc. have something like this in the main thread:
create/define windows
create/define application
application.run(windows)
I can create windows in Racket like this:
#lang racket/gui
(define frame1 (new frame%
[label "Example"]
[width 200]
[height 200]))
(define frame2 (new frame%
[label "Example"]
[width 200]
[height 200]))
(send frame1 show #t)
(send frame2 show #t)
or like this:
#lang racket/gui
(thread (lambda () (begin (define frame1 (new frame%
[label "Example"]
[width 200]
[height 200]))
(send frame1 show #t))))
(thread (lambda () (begin (define frame2 (new frame%
[label "Example"]
[width 200]
[height 200]))
(send frame2 show #t))))
using Racket threads (which aren't actual threads in the usual sense, i.e., not OS threads). I don't know if this works using futures or places though.
It seems Racket must be doing some bundling of this code somewhere such that these frames (i.e., windows) are getting created and managed only on the main thread and that these frame creation and show are "messages" to that main thread bundle. Is that true? How does this work? I would have to assume it's something like this. LabVIEW can create and manage windows from any thread and assign event handler loops (however many you want for a single window), but I do know that there, all the GUI stuff is still happening on the single main thread.
- Can someone explain to me or sketch out how Racket handles managing several windows in a single program?
- Is there somewhere I can read how event spaces work outside of this documentation?
Thank you!