use non-extern unions so we get safety checks

This commit is contained in:
Mitchell Hashimoto
2022-08-31 16:01:16 -07:00
parent 10ec5f509e
commit cb06bf4873

View File

@ -17,6 +17,7 @@
const Screen = @This(); const Screen = @This();
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert; const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
@ -43,10 +44,7 @@ pub const Cursor = struct {
/// This is a single item within the storage buffer. We use a union to /// This is a single item within the storage buffer. We use a union to
/// have different types of data in a single contiguous buffer. /// have different types of data in a single contiguous buffer.
/// const StorageCell = union {
/// Note: the union is extern so that it follows the same memory layout
/// semantics as C, which allows us to have a tightly packed union.
const StorageCell = extern union {
header: RowHeader, header: RowHeader,
cell: Cell, cell: Cell,
@ -59,22 +57,36 @@ const StorageCell = extern union {
// @sizeOf(StorageCell), // @sizeOf(StorageCell),
// @alignOf(StorageCell), // @alignOf(StorageCell),
// }); // });
}
// We want to be at most the size of a cell always. We have WAY comptime {
// more cells than other fields, so we don't want to pay the cost // We only check this during ReleaseFast because safety checks
// of padding due to other fields. // have to be disabled to get this size.
try std.testing.expectEqual(@sizeOf(Cell), @sizeOf(StorageCell)); if (builtin.mode == .ReleaseFast) {
// We want to be at most the size of a cell always. We have WAY
// more cells than other fields, so we don't want to pay the cost
// of padding due to other fields.
assert(@sizeOf(Cell) == @sizeOf(StorageCell));
} else {
// Extra u32 for the tag for safety checks. This is subject to
// change depending on the Zig compiler...
assert((@sizeOf(Cell) + @sizeOf(u32)) == @sizeOf(StorageCell));
}
} }
}; };
/// The row header is at the start of every row within the storage buffer. /// The row header is at the start of every row within the storage buffer.
/// It can store row-specific data. /// It can store row-specific data.
pub const RowHeader = struct { pub const RowHeader = struct {
dirty: bool, /// Used internally to track if this row has been initialized.
init: bool = false,
/// True if one of the cells in this row has been changed
dirty: bool = false,
/// If true, this row is soft-wrapped. The first cell of the next /// If true, this row is soft-wrapped. The first cell of the next
/// row is a continuous of this row. /// row is a continuous of this row.
wrap: bool, wrap: bool = false,
}; };
/// Cell is a single cell within the screen. /// Cell is a single cell within the screen.
@ -163,6 +175,7 @@ pub const Row = struct {
/// Get a single immutable cell. /// Get a single immutable cell.
pub fn getCell(self: Row, x: usize) Cell { pub fn getCell(self: Row, x: usize) Cell {
assert(self.header().init);
assert(x < self.storage.len - 1); assert(x < self.storage.len - 1);
return self.storage[x + 1].cell; return self.storage[x + 1].cell;
} }
@ -172,19 +185,31 @@ pub const Row = struct {
/// next call to re-render this cell. Any change detection to avoid /// next call to re-render this cell. Any change detection to avoid
/// this should be done prior. /// this should be done prior.
pub fn getCellPtr(self: Row, x: usize) *Cell { pub fn getCellPtr(self: Row, x: usize) *Cell {
assert(self.header().init);
assert(x < self.storage.len - 1); assert(x < self.storage.len - 1);
return &self.storage[x + 1].cell; return &self.storage[x + 1].cell;
} }
/// Copy the row src into this row. /// Copy the row src into this row.
pub fn copyRow(self: Row, src: Row) void { pub fn copyRow(self: Row, src: Row) void {
assert(self.header().init);
std.mem.copy(StorageCell, self.storage[1..], src.storage[1..]); std.mem.copy(StorageCell, self.storage[1..], src.storage[1..]);
} }
/// Read-only iterator for the cells in the row. /// Read-only iterator for the cells in the row.
pub fn cellIterator(self: Row) CellIterator { pub fn cellIterator(self: Row) CellIterator {
assert(self.header().init);
return .{ .row = self }; return .{ .row = self };
} }
/// If this row isn't initialized, this sets all our cells to the
/// proper union tag so that it is properly zeroed.
fn initIfNeeded(self: Row) void {
if (!self.storage[0].header.init) {
self.fill(.{});
self.storage[0].header.init = true;
}
}
}; };
/// Used to iterate through the rows of a specific region. /// Used to iterate through the rows of a specific region.
@ -311,7 +336,9 @@ pub const RowIndexTag = enum {
} }
}; };
const StorageBuf = CircBuf(StorageCell, .{ .cell = .{} }); // Initialize to header and not a cell so that we can check header.init
// to know if the remainder of the row has been initialized or not.
const StorageBuf = CircBuf(StorageCell, .{ .header = .{} });
/// The allocator used for all the storage operations /// The allocator used for all the storage operations
alloc: Allocator, alloc: Allocator,
@ -386,7 +413,9 @@ pub fn getRow(self: *Screen, index: RowIndex) Row {
const slices = self.storage.getPtrSlice(offset, self.cols + 1); const slices = self.storage.getPtrSlice(offset, self.cols + 1);
assert(slices[0].len == self.cols + 1 and slices[1].len == 0); assert(slices[0].len == self.cols + 1 and slices[1].len == 0);
return .{ .storage = slices[0] }; const row: Row = .{ .storage = slices[0] };
row.initIfNeeded();
return row;
} }
/// Copy the row at src to dst. /// Copy the row at src to dst.