ghostty/src/terminal/new/size.zig
2024-03-22 20:27:16 -07:00

114 lines
3.7 KiB
Zig

const std = @import("std");
const assert = std.debug.assert;
/// The maximum size of a page in bytes. We use a u16 here because any
/// smaller bit size by Zig is upgraded anyways to a u16 on mainstream
/// CPU architectures, and because 65KB is a reasonable page size. To
/// support better configurability, we derive everything from this.
pub const max_page_size = 65_536;
/// The int type that can contain the maximum memory offset in bytes,
/// derived from the maximum terminal page size.
pub const OffsetInt = std.math.IntFittingRange(0, max_page_size - 1);
/// The int type that can contain the maximum number of cells in a page.
pub const CellCountInt = u16; // TODO: derive
//
/// The offset from the base address of the page to the start of some data.
/// This is typed for ease of use.
///
/// This is a packed struct so we can attach methods to an int.
pub fn Offset(comptime T: type) type {
return packed struct(OffsetInt) {
const Self = @This();
offset: OffsetInt = 0,
/// Returns a pointer to the start of the data, properly typed.
pub fn ptr(self: Self, base: anytype) [*]T {
// The offset must be properly aligned for the type since
// our return type is naturally aligned. We COULD modify this
// to return arbitrary alignment, but its not something we need.
assert(@mod(self.offset, @alignOf(T)) == 0);
return @ptrFromInt(intFromBase(base) + self.offset);
}
};
}
/// Get the offset for a given type from some base pointer to the
/// actual pointer to the type.
pub fn getOffset(
comptime T: type,
base: anytype,
ptr: *const T,
) Offset(T) {
const base_int = intFromBase(base);
const ptr_int = @intFromPtr(ptr);
const offset = ptr_int - base_int;
return .{ .offset = @intCast(offset) };
}
fn intFromBase(base: anytype) usize {
return switch (@typeInfo(@TypeOf(base))) {
.Pointer => |v| switch (v.size) {
.One,
.Many,
.C,
=> @intFromPtr(base),
.Slice => @intFromPtr(base.ptr),
},
else => @compileError("invalid base type"),
};
}
test "Offset" {
// This test is here so that if Offset changes, we can be very aware
// of this effect and think about the implications of it.
const testing = std.testing;
try testing.expect(OffsetInt == u16);
}
test "Offset ptr u8" {
const testing = std.testing;
const offset: Offset(u8) = .{ .offset = 42 };
const base_int: usize = @intFromPtr(&offset);
const actual = offset.ptr(&offset);
try testing.expectEqual(@as(usize, base_int + 42), @intFromPtr(actual));
}
test "Offset ptr structural" {
const Struct = struct { x: u32, y: u32 };
const testing = std.testing;
const offset: Offset(Struct) = .{ .offset = @alignOf(Struct) * 4 };
const base_int: usize = std.mem.alignForward(usize, @intFromPtr(&offset), @alignOf(Struct));
const base: [*]u8 = @ptrFromInt(base_int);
const actual = offset.ptr(base);
try testing.expectEqual(@as(usize, base_int + offset.offset), @intFromPtr(actual));
}
test "getOffset bytes" {
const testing = std.testing;
var widgets: []const u8 = "ABCD";
const offset = getOffset(u8, widgets.ptr, &widgets[2]);
try testing.expectEqual(@as(OffsetInt, 2), offset.offset);
}
test "getOffset structs" {
const testing = std.testing;
const Widget = struct { x: u32, y: u32 };
const widgets: []const Widget = &.{
.{ .x = 1, .y = 2 },
.{ .x = 3, .y = 4 },
.{ .x = 5, .y = 6 },
.{ .x = 7, .y = 8 },
.{ .x = 9, .y = 10 },
};
const offset = getOffset(Widget, widgets.ptr, &widgets[2]);
try testing.expectEqual(
@as(OffsetInt, @sizeOf(Widget) * 2),
offset.offset,
);
}