render: implement double buffered screen for rendering
This lets us efficiently render by only updating cells that have changed since last render Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
parent
1e7c82fe44
commit
58bc6864cb
5 changed files with 59 additions and 13 deletions
|
@ -39,6 +39,7 @@ pub fn main() !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
const win = vx.window();
|
const win = vx.window();
|
||||||
|
win.clear();
|
||||||
const child = win.initChild(win.width / 2 - msg.len / 2, win.height / 2, .expand, .expand);
|
const child = win.initChild(win.width / 2 - msg.len / 2, win.height / 2, .expand, .expand);
|
||||||
for (msg, 0..) |_, i| {
|
for (msg, 0..) |_, i| {
|
||||||
const cell: Cell = .{ .char = .{ .grapheme = msg[i .. i + 1] } };
|
const cell: Cell = .{ .char = .{ .grapheme = msg[i .. i + 1] } };
|
||||||
|
|
|
@ -7,16 +7,16 @@ const log = std.log.scoped(.screen);
|
||||||
|
|
||||||
const Screen = @This();
|
const Screen = @This();
|
||||||
|
|
||||||
width: usize,
|
width: usize = 0,
|
||||||
height: usize,
|
height: usize = 0,
|
||||||
|
|
||||||
buf: []Cell = undefined,
|
buf: []Cell = undefined,
|
||||||
|
|
||||||
pub fn init() Screen {
|
/// sets each cell to the default cell
|
||||||
return Screen{
|
pub fn init(self: *Screen) void {
|
||||||
.width = 0,
|
for (self.buf, 0..) |_, i| {
|
||||||
.height = 0,
|
self.buf[i] = .{};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Screen, alloc: std.mem.Allocator) void {
|
pub fn deinit(self: *Screen, alloc: std.mem.Allocator) void {
|
||||||
|
@ -27,9 +27,6 @@ pub fn resize(self: *Screen, alloc: std.mem.Allocator, w: usize, h: usize) !void
|
||||||
log.debug("resizing screen: width={d} height={d}", .{ w, h });
|
log.debug("resizing screen: width={d} height={d}", .{ w, h });
|
||||||
alloc.free(self.buf);
|
alloc.free(self.buf);
|
||||||
self.buf = try alloc.alloc(Cell, w * h);
|
self.buf = try alloc.alloc(Cell, w * h);
|
||||||
for (self.buf, 0..) |_, i| {
|
|
||||||
self.buf[i] = .{};
|
|
||||||
}
|
|
||||||
self.width = w;
|
self.width = w;
|
||||||
self.height = h;
|
self.height = h;
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,6 +173,11 @@ pub fn run(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Writer = std.io.Writer(os.fd_t, os.WriteError, os.write);
|
||||||
|
|
||||||
|
pub fn writer(self: *Tty) Writer {
|
||||||
|
return .{ .context = self.fd };
|
||||||
|
}
|
||||||
/// write to the tty
|
/// write to the tty
|
||||||
//
|
//
|
||||||
// TODO: buffer the writes
|
// TODO: buffer the writes
|
||||||
|
|
|
@ -67,6 +67,16 @@ pub fn writeCell(self: Window, col: usize, row: usize, cell: Cell) void {
|
||||||
self.screen.writeCell(col + self.x_off, row + self.y_off, cell);
|
self.screen.writeCell(col + self.x_off, row + self.y_off, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear(self: Window) void {
|
||||||
|
var row: usize = self.y_off;
|
||||||
|
while (row < (self.height + self.y_off)) : (row += 1) {
|
||||||
|
var col: usize = self.x_off;
|
||||||
|
while (col < (self.width + self.x_off)) : (col += 1) {
|
||||||
|
self.screen.writeCell(col, row, .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "Window size set" {
|
test "Window size set" {
|
||||||
var parent = Window{
|
var parent = Window{
|
||||||
.x_off = 0,
|
.x_off = 0,
|
||||||
|
|
|
@ -34,6 +34,9 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
tty: ?Tty,
|
tty: ?Tty,
|
||||||
|
|
||||||
screen: Screen,
|
screen: Screen,
|
||||||
|
// The last screen we drew. We keep this so we can efficiently update on
|
||||||
|
// the next render
|
||||||
|
screen_last: Screen,
|
||||||
|
|
||||||
alt_screen: bool,
|
alt_screen: bool,
|
||||||
|
|
||||||
|
@ -42,7 +45,8 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
return Self{
|
return Self{
|
||||||
.queue = .{},
|
.queue = .{},
|
||||||
.tty = null,
|
.tty = null,
|
||||||
.screen = Screen.init(),
|
.screen = .{},
|
||||||
|
.screen_last = .{},
|
||||||
.alt_screen = false,
|
.alt_screen = false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -59,7 +63,10 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
}
|
}
|
||||||
tty.deinit();
|
tty.deinit();
|
||||||
}
|
}
|
||||||
if (alloc) |a| self.screen.deinit(a);
|
if (alloc) |a| {
|
||||||
|
self.screen.deinit(a);
|
||||||
|
self.screen_last.deinit(a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// spawns the input thread to start listening to the tty for input
|
/// spawns the input thread to start listening to the tty for input
|
||||||
|
@ -94,6 +101,10 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
/// freed when resizing
|
/// freed when resizing
|
||||||
pub fn resize(self: *Self, alloc: std.mem.Allocator, winsize: Winsize) !void {
|
pub fn resize(self: *Self, alloc: std.mem.Allocator, winsize: Winsize) !void {
|
||||||
try self.screen.resize(alloc, winsize.cols, winsize.rows);
|
try self.screen.resize(alloc, winsize.cols, winsize.rows);
|
||||||
|
// we only init our current screen. This has the effect of redrawing
|
||||||
|
// every cell
|
||||||
|
self.screen.init();
|
||||||
|
try self.screen_last.resize(alloc, winsize.cols, winsize.rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns a Window comprising of the entire terminal screen
|
/// returns a Window comprising of the entire terminal screen
|
||||||
|
@ -127,8 +138,30 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
pub fn render(self: *Self) !void {
|
pub fn render(self: *Self) !void {
|
||||||
var tty = self.tty orelse return;
|
var tty = self.tty orelse return;
|
||||||
|
|
||||||
|
// TODO: optimize writes
|
||||||
|
|
||||||
|
// Send the cursor to 0,0
|
||||||
_ = try tty.write(ctlseqs.home);
|
_ = try tty.write(ctlseqs.home);
|
||||||
for (self.screen.buf) |cell| {
|
var reposition: bool = false;
|
||||||
|
var row: usize = 0;
|
||||||
|
var col: usize = 0;
|
||||||
|
for (self.screen.buf, 0..) |cell, i| {
|
||||||
|
col += 1;
|
||||||
|
if (col == self.screen.width) {
|
||||||
|
row += 1;
|
||||||
|
col = 0;
|
||||||
|
}
|
||||||
|
// If cell is the same as our last frame, we don't need to do
|
||||||
|
// anything
|
||||||
|
if (std.meta.eql(cell, self.screen_last.buf[i])) {
|
||||||
|
reposition = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Set this cell in the last frame
|
||||||
|
self.screen_last.buf[i] = cell;
|
||||||
|
if (reposition) {
|
||||||
|
try std.fmt.format(tty.writer(), ctlseqs.cup, .{ row + 1, col + 1 });
|
||||||
|
}
|
||||||
_ = try tty.write(cell.char.grapheme);
|
_ = try tty.write(cell.char.grapheme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue