Hello. It's been awhile since I've posted here, but I've just encountered an issue I'd be grateful for some advice on.
I started a new project, and I'm using the same overall architecture I used for a game I did some years ago, which made use of Core Video Display Link (https://developer.apple.com/documentation/corevideo/cvdisplaylink-k0k) for v-sync, which should result in a nice stable 60fps on any modern display.
This results in two threads that are running the app (not including auxiliary worker threads): the main thread that starts the application, and then the display link thread. For the most part, this was fine and worked well for my game. One issue I had to deal with, though, was input handling, since the method I used for that called a macOS API that must be used on the main thread only (NSApp.nextEvent(...)
).
The issue was that the main thread and display link threads would run at different rates, and this wasn't going to work with handling input, especially when I have input buffering. So in order to make the two threads run at the same rate, I used a condition variable to make them run lock-step. This actually worked out really well, and I had no issues with it in my game.
In my new project, however, I'm using Dear ImGui for the UI, and it has to be updated from the main thread. So now I have two views in my application window, one is being updated from the display link thread, and the view with ImGui is being updated on the main thread. While it is working, and for the most part seems perfectly fine, I do have shared data between the two views, which will inevitably require some kind of synchronization (i.e. the ImGui view will update data that is read by the view on the display link thread).
Ideally I would like to avoid this by having both views being updated by the main thread, so I'm curious about other options to make this work.
One thought I've had is to move all work onto the main thread and use the display link thread solely as a "driver" of the main thread (i.e. use it to control its rate/speed). Perhaps I can still use a condition variable for that. Are there any other options, or better ways to do this?
Here is some code to illustrate the architecture I have right now. This is the main.swift file, that starts the application:
let cocoaApp = FSCocoaApp.initApp() let window = MinuetWindow(title: "Minuet") window.startDisplayLink() window.makeKeyAndOrderFront(nil) var running = true while running { cocoaApp.processEvents() // Calls NSApp.nextEvent(...) if cocoaApp.isKeyPressed(keycode: Keycodes.vkEscape.rawValue) { running = false } window.syncWithMainThread() cocoaApp.resetInput() } window.stopDisplayLink() cocoaApp.shutdown()
The window's syncWithMainThread()
method updates the UI/ImGui view, and waits on a condition variable to be signalled by the display link thread:
func syncWithMainThread() { uiView?.update() pthread_mutex_lock(&frameLock) pthread_cond_wait(&frameEnd, &frameLock) pthread_mutex_unlock(&frameLock) }
The display link thread calls this next method every time it needs a frame, updating the main view and then signalling the condition variable for the main thread:
private func getFrame(outputTime: UnsafePointer<CVTimeStamp>) -> CVReturn { let timeStamp = outputTime.pointee let dt = Double(timeStamp.hostTime - lastFrameTime) / CVGetHostClockFrequency() lastFrameTime = timeStamp.hostTime let input = FSCocoaApp.shared.getInput() minuetView?.update(input: input, dt: Float(dt)) pthread_mutex_lock(&frameLock) pthread_cond_signal(&frameEnd) pthread_mutex_unlock(&frameLock) return kCVReturnSuccess }