Fix notification handling from publishing while View is updating (#3177)

I was originally looking into this issue:
https://github.com/ghostty-org/ghostty/issues/3109

When running the logic and triggering a config reload, Xcode warns us
about updating publishable properties from within the notification
callback functions:
<img width="924" alt="Screenshot 2024-12-26 at 5 46 19 PM"
src="https://github.com/user-attachments/assets/38000a09-ffad-4dda-9e2d-a37e5283ff89"
/>

I believe this is because `SurfaceView` is being used as both a bridged
NSView (inside `Surface`) and also an `ObservableObject`, so it's
possible for the notification callback to happen while a SwiftUI render
loop is occurring. The notification delivery happens on whatever thread
posted the message. The better solution long-term is likely to separate
the `ObservableObject` logic into its own class to avoid mixing with the
View logic.

The solution here is to simply move the publishable mutation out of the
current loop via `DispatchQueue.main.async`. I confirmed the warning
goes away with this, and I didn't notice any odd behavior while
reloading config changes.
This commit is contained in:
Mitchell Hashimoto
2024-12-27 11:54:50 -08:00
committed by GitHub

View File

@ -361,17 +361,23 @@ extension Ghostty {
@objc private func onUpdateRendererHealth(notification: SwiftUI.Notification) { @objc private func onUpdateRendererHealth(notification: SwiftUI.Notification) {
guard let healthAny = notification.userInfo?["health"] else { return } guard let healthAny = notification.userInfo?["health"] else { return }
guard let health = healthAny as? ghostty_action_renderer_health_e else { return } guard let health = healthAny as? ghostty_action_renderer_health_e else { return }
healthy = health == GHOSTTY_RENDERER_HEALTH_OK DispatchQueue.main.async { [weak self] in
self?.healthy = health == GHOSTTY_RENDERER_HEALTH_OK
}
} }
@objc private func ghosttyDidContinueKeySequence(notification: SwiftUI.Notification) { @objc private func ghosttyDidContinueKeySequence(notification: SwiftUI.Notification) {
guard let keyAny = notification.userInfo?[Ghostty.Notification.KeySequenceKey] else { return } guard let keyAny = notification.userInfo?[Ghostty.Notification.KeySequenceKey] else { return }
guard let key = keyAny as? Ghostty.KeyEquivalent else { return } guard let key = keyAny as? Ghostty.KeyEquivalent else { return }
keySequence.append(key) DispatchQueue.main.async { [weak self] in
self?.keySequence.append(key)
}
} }
@objc private func ghosttyDidEndKeySequence(notification: SwiftUI.Notification) { @objc private func ghosttyDidEndKeySequence(notification: SwiftUI.Notification) {
keySequence = [] DispatchQueue.main.async { [weak self] in
self?.keySequence = []
}
} }
@objc private func ghosttyConfigDidChange(_ notification: SwiftUI.Notification) { @objc private func ghosttyConfigDidChange(_ notification: SwiftUI.Notification) {
@ -381,7 +387,9 @@ extension Ghostty {
] as? Ghostty.Config else { return } ] as? Ghostty.Config else { return }
// Update our derived config // Update our derived config
self.derivedConfig = DerivedConfig(config) DispatchQueue.main.async { [weak self] in
self?.derivedConfig = DerivedConfig(config)
}
} }
@objc private func ghosttyColorDidChange(_ notification: SwiftUI.Notification) { @objc private func ghosttyColorDidChange(_ notification: SwiftUI.Notification) {
@ -391,7 +399,9 @@ extension Ghostty {
switch (change.kind) { switch (change.kind) {
case .background: case .background:
self.backgroundColor = change.color DispatchQueue.main.async { [weak self] in
self?.backgroundColor = change.color
}
default: default:
// We don't do anything for the other colors yet. // We don't do anything for the other colors yet.
@ -413,7 +423,9 @@ extension Ghostty {
// We also just trigger a backing property change. Just in case the screen has // We also just trigger a backing property change. Just in case the screen has
// a different scaling factor, this ensures that we update our content scale. // a different scaling factor, this ensures that we update our content scale.
// Issue: https://github.com/ghostty-org/ghostty/issues/2731 // Issue: https://github.com/ghostty-org/ghostty/issues/2731
viewDidChangeBackingProperties() DispatchQueue.main.async { [weak self] in
self?.viewDidChangeBackingProperties()
}
} }
// MARK: - NSView // MARK: - NSView