Misc error handling improvements (#10392)

See individual commits. An overview:

* Added explicit error sets in more places
* Removed `!` from functions that can't ever fail
* `renderer.cell.Contents.resize` `errdefer` now does proper cleanup
* `renderer.rebuildCells` can now only fail due to allocator OOM
* If shaping fails during rendering, that row is skipped (previously
it'd halt rendering there)
* Failed image rendering setup in the renderer now skips that image
(previously would halt the full frame)
* GPU texture cleanup for failed image setup now works properly
This commit is contained in:
Mitchell Hashimoto
2026-01-20 12:38:20 -08:00
committed by GitHub
7 changed files with 590 additions and 499 deletions

View File

@@ -196,8 +196,18 @@ pub fn getFace(self: *Collection, index: Index) !*Face {
return try self.getFaceFromEntry(try self.getEntry(index));
}
pub const EntryError = error{
/// Index represents a special font (built-in) and these don't
/// have an associated face. This should be caught upstream and use
/// alternate logic.
SpecialHasNoFace,
/// Invalid index.
IndexOutOfBounds,
};
/// Get the unaliased entry from an index
pub fn getEntry(self: *Collection, index: Index) !*Entry {
pub fn getEntry(self: *Collection, index: Index) EntryError!*Entry {
if (index.special() != null) return error.SpecialHasNoFace;
const list = self.faces.getPtr(index.style);
if (index.idx >= list.len) return error.IndexOutOfBounds;

View File

@@ -98,7 +98,7 @@ pub const Shaper = struct {
self.unichars.deinit(alloc);
}
fn reset(self: *RunState) !void {
fn reset(self: *RunState) void {
self.codepoints.clearRetainingCapacity();
self.unichars.clearRetainingCapacity();
}
@@ -644,8 +644,8 @@ pub const Shaper = struct {
pub const RunIteratorHook = struct {
shaper: *Shaper,
pub fn prepare(self: *RunIteratorHook) !void {
try self.shaper.run_state.reset();
pub fn prepare(self: *RunIteratorHook) void {
self.shaper.run_state.reset();
// log.warn("----------- run reset -------------", .{});
}
@@ -681,7 +681,7 @@ pub const Shaper = struct {
});
}
pub fn finalize(self: RunIteratorHook) !void {
pub fn finalize(self: RunIteratorHook) void {
_ = self;
}
};

View File

@@ -175,7 +175,7 @@ pub const Shaper = struct {
pub const RunIteratorHook = struct {
shaper: *Shaper,
pub fn prepare(self: RunIteratorHook) !void {
pub fn prepare(self: RunIteratorHook) void {
// Reset the buffer for our current run
self.shaper.hb_buf.reset();
self.shaper.hb_buf.setContentType(.unicode);
@@ -191,7 +191,7 @@ pub const Shaper = struct {
self.shaper.hb_buf.add(cp, cluster);
}
pub fn finalize(self: RunIteratorHook) !void {
pub fn finalize(self: RunIteratorHook) void {
self.shaper.hb_buf.guessSegmentProperties();
}
};

View File

@@ -73,7 +73,7 @@ pub const RunIterator = struct {
var current_font: font.Collection.Index = .{};
// Allow the hook to prepare
try self.hooks.prepare();
self.hooks.prepare();
// Initialize our hash for this run.
var hasher = Hasher.init(0);
@@ -283,7 +283,7 @@ pub const RunIterator = struct {
}
// Finalize our buffer
try self.hooks.finalize();
self.hooks.finalize();
// Add our length to the hash as an additional mechanism to avoid collisions
autoHash(&hasher, j - self.i);

View File

@@ -90,7 +90,6 @@ pub const Contents = struct {
const bg_cells = try alloc.alloc(shaderpkg.CellBg, cell_count);
errdefer alloc.free(bg_cells);
@memset(bg_cells, .{ 0, 0, 0, 0 });
// The foreground lists can hold 3 types of items:
@@ -106,32 +105,28 @@ pub const Contents = struct {
// We have size.rows + 2 lists because indexes 0 and size.rows - 1 are
// used for special lists containing the cursor cell which need to
// be first and last in the buffer, respectively.
var fg_rows = try ArrayListCollection(shaderpkg.CellText).init(
var fg_rows: ArrayListCollection(shaderpkg.CellText) = try .init(
alloc,
size.rows + 2,
size.columns * 3,
);
errdefer fg_rows.deinit(alloc);
alloc.free(self.bg_cells);
self.fg_rows.deinit(alloc);
self.bg_cells = bg_cells;
self.fg_rows = fg_rows;
// We don't need 3*cols worth of cells for the cursor lists, so we can
// replace them with smaller lists. This is technically a tiny bit of
// extra work but resize is not a hot function so it's worth it to not
// waste the memory.
self.fg_rows.lists[0].deinit(alloc);
self.fg_rows.lists[0] = try std.ArrayListUnmanaged(
shaderpkg.CellText,
).initCapacity(alloc, 1);
fg_rows.lists[0].deinit(alloc);
fg_rows.lists[0] = try .initCapacity(alloc, 1);
fg_rows.lists[size.rows + 1].deinit(alloc);
fg_rows.lists[size.rows + 1] = try .initCapacity(alloc, 1);
self.fg_rows.lists[size.rows + 1].deinit(alloc);
self.fg_rows.lists[size.rows + 1] = try std.ArrayListUnmanaged(
shaderpkg.CellText,
).initCapacity(alloc, 1);
// Perform the swap, no going back from here.
errdefer comptime unreachable;
alloc.free(self.bg_cells);
self.fg_rows.deinit(alloc);
self.bg_cells = bg_cells;
self.fg_rows = fg_rows;
}
/// Reset the cell contents to an empty state without resizing.

File diff suppressed because it is too large Load Diff

View File

@@ -146,7 +146,7 @@ pub const Image = union(enum) {
/// Mark the current image to be replaced with a pending one. This will
/// attempt to update the existing texture if we have one, otherwise it
/// will act like a new upload.
pub fn markForReplace(self: *Image, alloc: Allocator, img: Image) !void {
pub fn markForReplace(self: *Image, alloc: Allocator, img: Image) void {
assert(img.isPending());
// If we have pending data right now, free it.
@@ -216,9 +216,8 @@ pub const Image = union(enum) {
/// Prepare the pending image data for upload to the GPU.
/// This doesn't need GPU access so is safe to call any time.
pub fn prepForUpload(self: *Image, alloc: Allocator) !void {
pub fn prepForUpload(self: *Image, alloc: Allocator) wuffs.Error!void {
assert(self.isPending());
try self.convert(alloc);
}