ghostty/src/termio/message.zig
2022-11-14 17:25:35 -08:00

127 lines
4.0 KiB
Zig

const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const renderer = @import("../renderer.zig");
const terminal = @import("../terminal/main.zig");
/// The messages that can be sent to an IO thread.
///
/// This is not a tiny structure (~40 bytes at the time of writing this comment),
/// but the messages are IO thread sends are also very few. At the current size
/// we can queue 26,000 messages before consuming a MB of RAM.
pub const Message = union(enum) {
/// Resize the window.
resize: struct {
/// The grid size for the given screen size with padding applied.
grid_size: renderer.GridSize,
/// The full screen (drawable) size. This does NOT include padding.
/// This should be sent on to the renderer.
screen_size: renderer.ScreenSize,
/// The padding, so that the terminal implementation can subtract
/// this to send to the pty.
padding: renderer.Padding,
},
/// Write where the data fits in the union.
write_small: WriteReq.Small,
/// Write where the data pointer is stable.
write_stable: []const u8,
/// Write where the data is allocated and must be freed.
write_alloc: WriteReq.Alloc,
/// Return a write request for the given data. This will use
/// write_small if it fits or write_alloc otherwise. This should NOT
/// be used for stable pointers which can be manually set to write_stable.
pub fn writeReq(alloc: Allocator, data: anytype) !Message {
switch (@typeInfo(@TypeOf(data))) {
.Pointer => |info| {
assert(info.size == .Slice);
assert(info.child == u8);
// If it fits in our small request, do that.
if (data.len <= WriteReq.Small.Max) {
var buf: WriteReq.Small.Array = undefined;
std.mem.copy(u8, &buf, data);
return Message{
.write_small = .{
.data = buf,
.len = @intCast(u8, data.len),
},
};
}
// Otherwise, allocate
var buf = try alloc.dupe(u8, data);
errdefer alloc.free(buf);
return Message{
.write_alloc = .{
.alloc = alloc,
.data = buf,
},
};
},
else => unreachable,
}
}
/// Represents a write request.
pub const WriteReq = union(enum) {
pub const Small = struct {
pub const Max = 38;
pub const Array = [Max]u8;
data: Array,
len: u8,
};
pub const Alloc = struct {
alloc: Allocator,
data: []u8,
};
/// A small write where the data fits into this union size.
small: Small,
/// A stable pointer so we can just pass the slice directly through.
/// This is useful i.e. for const data.
stable: []const u8,
/// Allocated and must be freed with the provided allocator. This
/// should be rarely used.
alloc: Alloc,
};
};
test {
std.testing.refAllDecls(@This());
}
test {
// Ensure we don't grow our IO message size without explicitly wanting to.
const testing = std.testing;
try testing.expectEqual(@as(usize, 40), @sizeOf(Message));
}
test "Message.writeReq small" {
const testing = std.testing;
const alloc = testing.allocator;
const input = "hello!";
const io = try Message.writeReq(alloc, @as([]const u8, input));
try testing.expect(io == .write_small);
}
test "Message.writeReq alloc" {
const testing = std.testing;
const alloc = testing.allocator;
const input = "hello! " ** 100;
const io = try Message.writeReq(alloc, @as([]const u8, input));
try testing.expect(io == .write_alloc);
io.write_alloc.alloc.free(io.write_alloc.data);
}