diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-16 23:28:05 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-16 23:29:28 +0300 |
| commit | 4a3302d19a2baa3a33e910e0088c24e780905b90 (patch) | |
| tree | dcaf76fae1c82102d933c9225b17ca7ac5d3a847 /vendor/libghostty-vt/src/renderer/State.zig | |
| parent | 550e9f8cc085f428429f5cf2da0bb2dba897e8b8 (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.zig | 73 |
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 |
