mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
Move the render to a timer that slows down under load
This commit is contained in:
23
src/App.zig
23
src/App.zig
@ -85,31 +85,12 @@ pub fn run(self: App) !void {
|
||||
}).callback);
|
||||
|
||||
while (!self.window.shouldClose()) {
|
||||
// Mark this so we're in a totally different "frame"
|
||||
tracy.frameMark();
|
||||
|
||||
// Track the render part of the frame separately.
|
||||
{
|
||||
const frame = tracy.frame("render");
|
||||
defer frame.end();
|
||||
try self.window.run();
|
||||
}
|
||||
|
||||
// Block for any glfw events. This may also be an "empty" event
|
||||
// posted by the libuv watcher so that we trigger a libuv loop tick.
|
||||
try glfw.waitEvents();
|
||||
|
||||
// If the window wants the event loop to wakeup, then we "kick" the
|
||||
// embed thread to wake up. I'm not sure why we have to do this in a
|
||||
// loop, this is surely a lacking in my understanding of libuv. But
|
||||
// this works.
|
||||
if (self.window.wakeup) {
|
||||
self.window.wakeup = false;
|
||||
while (embed.sleeping.load(.SeqCst) and embed.terminate.load(.SeqCst) == false) {
|
||||
try async_h.send();
|
||||
try embed.loopRun();
|
||||
}
|
||||
}
|
||||
// Mark this so we're in a totally different "frame"
|
||||
tracy.frameMark();
|
||||
|
||||
// Run the libuv loop
|
||||
const frame = tracy.frame("libuv");
|
||||
|
@ -17,7 +17,11 @@ const Pty = @import("Pty.zig");
|
||||
const Command = @import("Command.zig");
|
||||
const Terminal = @import("terminal/Terminal.zig");
|
||||
const SegmentedPool = @import("segmented_pool.zig").SegmentedPool;
|
||||
const frame = @import("tracy/tracy.zig").frame;
|
||||
const trace = @import("tracy/tracy.zig").trace;
|
||||
const max_timer = @import("max_timer.zig");
|
||||
|
||||
const RenderTimer = max_timer.MaxTimer(renderTimerCallback);
|
||||
|
||||
const log = std.log.scoped(.window);
|
||||
|
||||
@ -49,6 +53,9 @@ terminal: Terminal,
|
||||
/// Timer that blinks the cursor.
|
||||
cursor_timer: libuv.Timer,
|
||||
|
||||
/// Render at least 60fps.
|
||||
render_timer: RenderTimer,
|
||||
|
||||
/// The reader/writer stream for the pty.
|
||||
pty_stream: libuv.Tty,
|
||||
|
||||
@ -180,6 +187,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop) !*Window {
|
||||
.command = cmd,
|
||||
.terminal = term,
|
||||
.cursor_timer = timer,
|
||||
.render_timer = try RenderTimer.init(loop, self, 16, 64),
|
||||
.pty_stream = stream,
|
||||
};
|
||||
|
||||
@ -211,6 +219,8 @@ pub fn destroy(self: *Window) void {
|
||||
}
|
||||
}).callback);
|
||||
|
||||
self.render_timer.deinit();
|
||||
|
||||
// We have to dealloc our window in the close callback because
|
||||
// we can't free some of the memory associated with the window
|
||||
// until the stream is closed.
|
||||
@ -231,21 +241,6 @@ pub fn shouldClose(self: Window) bool {
|
||||
return self.window.shouldClose();
|
||||
}
|
||||
|
||||
pub fn run(self: Window) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
// Set our background
|
||||
gl.clearColor(0.2, 0.3, 0.3, 1.0);
|
||||
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Render the grid
|
||||
try self.grid.render();
|
||||
|
||||
// Swap
|
||||
try self.window.swapBuffers();
|
||||
}
|
||||
|
||||
fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
@ -281,8 +276,7 @@ fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
|
||||
log.err("error updating OpenGL viewport err={}", .{err});
|
||||
|
||||
// Draw
|
||||
win.run() catch |err|
|
||||
log.err("error redrawing window during resize err={}", .{err});
|
||||
win.render_timer.schedule() catch unreachable;
|
||||
}
|
||||
|
||||
fn charCallback(window: glfw.Window, codepoint: u21) void {
|
||||
@ -369,6 +363,7 @@ fn focusCallback(window: glfw.Window, focused: bool) void {
|
||||
defer tracy.end();
|
||||
|
||||
const win = window.getUserPointer(Window) orelse return;
|
||||
win.render_timer.schedule() catch unreachable;
|
||||
if (focused) {
|
||||
win.wakeup = true;
|
||||
win.cursor_timer.start(cursorTimerCallback, 0, win.cursor_timer.getRepeat()) catch unreachable;
|
||||
@ -389,6 +384,7 @@ fn cursorTimerCallback(t: *libuv.Timer) void {
|
||||
const win = t.getData(Window) orelse return;
|
||||
win.grid.cursor_visible = !win.grid.cursor_visible;
|
||||
win.grid.updateCells(win.terminal) catch unreachable;
|
||||
win.render_timer.schedule() catch unreachable;
|
||||
}
|
||||
|
||||
fn ttyReadAlloc(t: *libuv.Tty, size: usize) ?[]u8 {
|
||||
@ -434,6 +430,9 @@ fn ttyRead(t: *libuv.Tty, n: isize, buf: []const u8) void {
|
||||
|
||||
// Update the cells for drawing
|
||||
win.grid.updateCells(win.terminal) catch unreachable;
|
||||
|
||||
// Schedule a render
|
||||
win.render_timer.schedule() catch unreachable;
|
||||
}
|
||||
|
||||
fn ttyWrite(req: *libuv.WriteReq, status: i32) void {
|
||||
@ -450,3 +449,29 @@ fn ttyWrite(req: *libuv.WriteReq, status: i32) void {
|
||||
|
||||
//log.info("WROTE: {d}", .{status});
|
||||
}
|
||||
|
||||
fn renderTimerCallback(t: *libuv.Timer) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const win = t.getData(Window).?;
|
||||
|
||||
// Set our background
|
||||
gl.clearColor(0.2, 0.3, 0.3, 1.0);
|
||||
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Render the grid
|
||||
win.grid.render() catch |err| {
|
||||
log.err("error rendering grid: {}", .{err});
|
||||
return;
|
||||
};
|
||||
|
||||
// Swap
|
||||
win.window.swapBuffers() catch |err| {
|
||||
log.err("error swapping buffers: {}", .{err});
|
||||
return;
|
||||
};
|
||||
|
||||
// Record our run
|
||||
win.render_timer.tick();
|
||||
}
|
||||
|
88
src/max_timer.zig
Normal file
88
src/max_timer.zig
Normal file
@ -0,0 +1,88 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const libuv = @import("libuv/main.zig");
|
||||
|
||||
/// A coalescing timer that forces a run after a certain maximum time
|
||||
/// since the last run. This is used for example by the renderer to try
|
||||
/// to render at a high FPS but gracefully fall back under high IO load so
|
||||
/// that we can process more data and increase throughput.
|
||||
pub fn MaxTimer(comptime cb: fn (*libuv.Timer) void) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
/// The underlying libuv timer.
|
||||
timer: libuv.Timer,
|
||||
|
||||
/// The maximum time between timer calls. This is best effort based on
|
||||
/// event loop load. If the event loop is busy, the timer will be run on
|
||||
/// the next available tick.
|
||||
max: u64,
|
||||
|
||||
/// The fastest the timer will ever run.
|
||||
min: u64,
|
||||
|
||||
/// The last time this timer ran.
|
||||
last: u64 = 0,
|
||||
|
||||
pub fn init(
|
||||
loop: libuv.Loop,
|
||||
data: ?*anyopaque,
|
||||
min: u64,
|
||||
max: u64,
|
||||
) !Self {
|
||||
const alloc = loop.getData(Allocator).?.*;
|
||||
var timer = try libuv.Timer.init(alloc, loop);
|
||||
timer.setData(data);
|
||||
|
||||
// The maximum time can't be less than the interval otherwise this
|
||||
// will just constantly fire.
|
||||
if (max < min) return error.MaxShorterThanTimer;
|
||||
return Self{
|
||||
.timer = timer,
|
||||
.min = min,
|
||||
.max = max,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.timer.close((struct {
|
||||
fn callback(t: *libuv.Timer) void {
|
||||
const alloc = t.loop().getData(Allocator).?.*;
|
||||
t.deinit(alloc);
|
||||
}
|
||||
}).callback);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// This should be called from the callback to update the last called time.
|
||||
pub fn tick(self: *Self) void {
|
||||
self.timer.loop().updateTime();
|
||||
self.last = self.timer.loop().now();
|
||||
self.timer.stop() catch unreachable;
|
||||
}
|
||||
|
||||
/// Schedule the timer to run. If the timer is not started, it'll
|
||||
/// run on the next min tick. If the timer is started, this will
|
||||
/// delay the timer up to max time since the last run.
|
||||
pub fn schedule(self: *Self) !void {
|
||||
// If the timer hasn't been started, start it now and schedule
|
||||
// a tick as soon as possible.
|
||||
if (!try self.timer.isActive()) {
|
||||
try self.timer.start(cb, self.min, self.min);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are past the max time, we run the timer now.
|
||||
try self.timer.stop();
|
||||
self.timer.loop().updateTime();
|
||||
if (self.timer.loop().now() - self.last > self.max) {
|
||||
@call(.{ .modifier = .always_inline }, cb, .{&self.timer});
|
||||
return;
|
||||
}
|
||||
|
||||
// We still have time, restart the timer so that it is min time away.
|
||||
try self.timer.start(cb, self.min, 0);
|
||||
}
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user