aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/renderer/State.zig
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:28:05 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:29:28 +0300
commit4a3302d19a2baa3a33e910e0088c24e780905b90 (patch)
treedcaf76fae1c82102d933c9225b17ca7ac5d3a847 /vendor/libghostty-vt/src/renderer/State.zig
parent550e9f8cc085f428429f5cf2da0bb2dba897e8b8 (diff)
chore: update vendored libghostty-vt
refs #276
Diffstat (limited to 'vendor/libghostty-vt/src/renderer/State.zig')
-rw-r--r--vendor/libghostty-vt/src/renderer/State.zig73
1 files changed, 73 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/renderer/State.zig b/vendor/libghostty-vt/src/renderer/State.zig
index 22cae8fe..8693458f 100644
--- a/vendor/libghostty-vt/src/renderer/State.zig
+++ b/vendor/libghostty-vt/src/renderer/State.zig
@@ -1,6 +1,9 @@
//! This is the render state that is given to a renderer.
+const State = @This();
+
const std = @import("std");
+const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Inspector = @import("../inspector/main.zig").Inspector;
const terminalpkg = @import("../terminal/main.zig");
@@ -30,6 +33,76 @@ preedit: ?Preedit = null,
/// need about the mouse.
mouse: Mouse = .{},
+/// The number of threads currently waiting to acquire `mutex` via
+/// `lockDemand`. This is not protected by the mutex; it is read by
+/// hot lock/unlock loops (the IO parse thread) in `yieldToDemand` to
+/// decide whether to hand the mutex off before relocking it.
+demand: std.atomic.Value(u32) = .init(0),
+
+/// Handoff generation counter. Incremented (with a futex wake) by
+/// `unlockDemand` after a demanding waiter releases the mutex, so that
+/// `yieldToDemand` knows the waiter had its turn.
+handoff_gen: std.atomic.Value(u32) = .init(0),
+
+/// How long `yieldToDemand` sleeps waiting for a demanding waiter to
+/// take its turn before giving up. This bounds how long the IO parse
+/// thread can stall if a wake is lost or the waiter is descheduled; a
+/// demanding critical section (the renderer's frame snapshot) is
+/// microseconds, so one millisecond is generous.
+const handoff_timeout_ns = 1 * std.time.ns_per_ms;
+
+/// Acquire `mutex` while signaling demand for it. Use this instead of
+/// locking the mutex directly on threads that must not be starved by
+/// a hot lock/unlock loop (the renderer's frame snapshot). Must be
+/// released with `unlockDemand`; releasing with `mutex.unlock` keeps
+/// the data safe but makes parked `yieldToDemand` callers wait out
+/// their full timeout.
+///
+/// Both `std.Thread.Mutex` and os_unfair_lock are unfair: a running
+/// thread that unlocks and immediately relocks beats a sleeping
+/// waiter every time, because the waiter must first be woken and
+/// scheduled. Under sustained pty output the IO parse thread is
+/// exactly such a loop, so without this signal the renderer can
+/// starve for as long as the output lasts.
+pub fn lockDemand(self: *State) void {
+ _ = self.demand.fetchAdd(1, .monotonic);
+ self.mutex.lock();
+ const prev = self.demand.fetchSub(1, .monotonic);
+ assert(prev > 0);
+}
+
+/// Release `mutex` acquired via `lockDemand` and notify hot loops
+/// parked in `yieldToDemand` that the demanding waiter had its turn.
+pub fn unlockDemand(self: *State) void {
+ self.mutex.unlock();
+ _ = self.handoff_gen.fetchAdd(1, .monotonic);
+ std.Thread.Futex.wake(&self.handoff_gen, 1);
+}
+
+/// Called by hot lock/unlock loops between critical sections, with
+/// `mutex` NOT held: if a `lockDemand` waiter exists, sleep until it
+/// has acquired and released the mutex (or the timeout passes). This
+/// is the handoff that unfair mutexes never do on their own.
+///
+/// The orderings here are all monotonic because these atomics are a
+/// scheduling heuristic, not a synchronization boundary: the mutex
+/// itself orders the protected data, and the timeout bounds any
+/// staleness.
+pub fn yieldToDemand(self: *State) void {
+ if (self.demand.load(.monotonic) == 0) return;
+
+ // Snapshot the generation before rechecking demand: if the waiter
+ // acquires and releases between our check and the wait below, the
+ // generation no longer matches and timedWait returns immediately.
+ const gen = self.handoff_gen.load(.monotonic);
+ if (self.demand.load(.monotonic) == 0) return;
+ std.Thread.Futex.timedWait(
+ &self.handoff_gen,
+ gen,
+ handoff_timeout_ns,
+ ) catch {};
+}
+
pub const Mouse = struct {
/// The point on the viewport where the mouse currently is. We use
/// viewport points to avoid the complexity of mapping the mouse to