This adds a new homemade package `pkg/objc` that wraps the Objective-C runtime so that Zig can interface with ObjC. This doesn't have 100% API coverage but all the hard problems (mainly `objc_msgSend`) are done. We can merge this now, start working on ObjC stuff, and expand APIs as we need them.
The `objc` package is of course only available to Mac builds.
This moves the OpenGL renderer out to a dedicated thread. The immediate effect
is noticeably improved FPS under high load scenarios. But the future impact is
also important: (1) required for multiple windows, tabs, etc. one day (2) forced
a refactor to support pluggable renderers.
== Architectural Notes
Windowing events and IO remain on the main thread for now, but I plan to move
IO to a dedicated thread in a future PR. Windowing events have to remain on the
main thread for eternity since some platforms force all Window events onto one
thread.
This is one of the reasons that this will be required for multi-window. As more
windows are opened, the windowing system will send more and more window events
to our main thread, taking more and more time away from other tasks on the main
thread. Windowing events are effectively free, so if the thread is ONLY handling
windowing events, this can scale basically forever.
Further, with OpenGL, each window must have its own "context." A context is the
state used to draw. OpenGL mandates at most one context is active at any given
time _per OS thread_. For multiple windows, this means that a single thread
would have to draw one window at a time in serial. With multiple rendering
threads, each thread can have its own context activated and draw in parallel.
The renderer thread and main thread **do have shared state** and will have some
shared state forever. The terminal screen state for example is shared memory.
An immutable data structure or double buffering would be too expensive (I think,
I'll check one day) so we use a mutex to protect access to the terminal state.
The primary instigator of terminal state change is PTY read. This is the
primary motivator to eventually move IO to its own dedicated thread as well.
As multiple windows come up, each PTY under heavy load conditions will block
only its own thread and hold the lock only for its own terminal state (and
therefore its own renderer thread).
We can continue to optimize critical areas from this point forward though and
lower any contention.
== Performance Notes
I haven't done any hard benchmarking on this, only did some cursory
memory measurements, CPU measurements, etc. I also ran superficial test
programs such as `cat`-ing large files, scrolling through large buffers
in vim, etc.
In any case, it seems that responsivity is generally higher, although it
appears that `cat`-ing large files slowed down by roughly 10%. This is
probably due to the lock overhead with the renderer and optimizing for
higher framerates with this setup.