diff --git a/src/renderer/image.zig b/src/renderer/image.zig index 927c7371e..dc08b8432 100644 --- a/src/renderer/image.zig +++ b/src/renderer/image.zig @@ -990,7 +990,7 @@ test "kitty renderer ignores pending payloads and retains native placements" { defer state.deinit(alloc); const storage = &t.screens.active.kitty_images; - const pending = try storage.addPendingImage(io, alloc, .{ + const pending = try storage.addPendingImage(io, alloc, t.screens.active, .{ .id = 1, .width = 1, .height = 1, @@ -1022,7 +1022,7 @@ test "kitty renderer ignores pending payloads and retains native placements" { // A newer pending replacement clears the renderer placement and marks // the copied texture data for unload without deleting the native pin. - _ = try storage.addPendingImage(io, alloc, .{ + _ = try storage.addPendingImage(io, alloc, t.screens.active, .{ .id = 1, .width = 1, .height = 1, diff --git a/src/terminal/c/kitty_graphics.zig b/src/terminal/c/kitty_graphics.zig index 4e36b88a8..396b463fb 100644 --- a/src/terminal/c/kitty_graphics.zig +++ b/src/terminal/c/kitty_graphics.zig @@ -1010,7 +1010,7 @@ test "image_get exposes pending metadata without a data pointer" { )); const alloc = lib.alloc.default(&lib.alloc.test_allocator); - const pending = try graphics.addPendingImage(testing.io, alloc, .{ + const pending = try graphics.addPendingImage(testing.io, alloc, terminal_c.zigTerminal(t).?.screens.active, .{ .id = 42, .number = 7, .width = 1, diff --git a/src/terminal/kitty/graphics_exec.zig b/src/terminal/kitty/graphics_exec.zig index da9087a8b..58fa4d5c4 100644 --- a/src/terminal/kitty/graphics_exec.zig +++ b/src/terminal/kitty/graphics_exec.zig @@ -377,7 +377,7 @@ fn loadAndAddImage( // Validate and store our image var img = try loading.complete(alloc); errdefer img.deinit(alloc); - try storage.addImage(io, alloc, img); + try storage.addImage(io, alloc, terminal.screens.active, img); // Get our display settings const display_ = loading.display; diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig index 5c33b1845..29a018e24 100644 --- a/src/terminal/kitty/graphics_storage.zig +++ b/src/terminal/kitty/graphics_storage.zig @@ -220,7 +220,7 @@ pub const ImageStorage = struct { if (limit < self.total_bytes) { const req_bytes = self.total_bytes - limit; log.info("evicting images to lower limit, evicting={}", .{req_bytes}); - if (!self.evictImage(io, alloc, req_bytes)) { + if (!self.evictImage(io, alloc, s, req_bytes)) { log.warn("failed to evict enough images for required bytes", .{}); } } @@ -231,7 +231,13 @@ pub const ImageStorage = struct { /// Add an image to the storage. This will automatically free any existing /// image with the same ID. Prefer addPendingImage for pending data so the /// caller receives a completion token. - pub fn addImage(self: *ImageStorage, io: std.Io, alloc: Allocator, img: Image) Allocator.Error!void { + pub fn addImage( + self: *ImageStorage, + io: std.Io, + alloc: Allocator, + s: *terminal.Screen, + img: Image, + ) Allocator.Error!void { const new_len = img.data.len(); // If the image itself is over the limit, then error immediately @@ -258,7 +264,7 @@ pub const ImageStorage = struct { if (total_bytes > self.total_limit) { const req_bytes = total_bytes - self.total_limit; log.info("evicting images to make space for {} bytes", .{req_bytes}); - if (!self.evictImageExcept(io, alloc, req_bytes, img.id)) { + if (!self.evictImageExcept(io, alloc, s, req_bytes, img.id)) { log.warn("failed to evict enough images for required bytes", .{}); return error.OutOfMemory; } @@ -294,10 +300,11 @@ pub const ImageStorage = struct { self: *ImageStorage, io: std.Io, alloc: Allocator, + s: *terminal.Screen, img: Image, ) Allocator.Error!PendingImage { assert(img.data == .pending); - try self.addImage(io, alloc, img); + try self.addImage(io, alloc, s, img); const stored = self.images.get(img.id).?; return .{ .id = stored.id, .generation = stored.generation }; } @@ -713,8 +720,14 @@ pub const ImageStorage = struct { /// /// This will evict as many images as necessary to make space for /// req bytes. - fn evictImage(self: *ImageStorage, io: std.Io, alloc: Allocator, req: usize) bool { - return self.evictImageExcept(io, alloc, req, null); + fn evictImage( + self: *ImageStorage, + io: std.Io, + alloc: Allocator, + s: *terminal.Screen, + req: usize, + ) bool { + return self.evictImageExcept(io, alloc, s, req, null); } /// Evict images while preserving `exclude_id`, used when replacing an @@ -723,6 +736,7 @@ pub const ImageStorage = struct { self: *ImageStorage, io: std.Io, alloc: Allocator, + s: *terminal.Screen, req: usize, exclude_id: ?u32, ) bool { @@ -780,6 +794,7 @@ pub const ImageStorage = struct { var p_it = self.placements.iterator(); while (p_it.next()) |entry| { if (entry.key_ptr.image_id == c.id) { + entry.value_ptr.deinit(s); self.removePlacementByPtr(entry.key_ptr); } } @@ -1032,8 +1047,8 @@ test "storage: add placement with zero placement id" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1, .width = 50, .height = 50 }); - try s.addImage(io, alloc, .{ .id = 2, .width = 25, .height = 25 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 50, .height = 50 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(io, alloc, t.screens.active, 1, 0, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 0, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); @@ -1060,7 +1075,7 @@ test "storage: replacing placement releases tracked pin" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); const tracked = t.screens.active.pages.countTrackedPins(); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ @@ -1098,7 +1113,7 @@ test "storage: adding placement reclaims garbage placements" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); const tracked = t.screens.active.pages.countTrackedPins(); const old_pin = try trackPin(&t, .{ .x = 0, .y = 0 }); @@ -1135,7 +1150,7 @@ test "storage: placement count limit permits replacement" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .virtual = {} } }); const img = s.images.getPtr(1).?; @@ -1158,9 +1173,9 @@ test "storage: delete all placements and images" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); - try s.addImage(io, alloc, .{ .id = 2 }); - try s.addImage(io, alloc, .{ .id = 3 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 3 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); @@ -1183,9 +1198,9 @@ test "storage: delete all placements and images preserves limit" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); s.total_limit = 5000; - try s.addImage(io, alloc, .{ .id = 1 }); - try s.addImage(io, alloc, .{ .id = 2 }); - try s.addImage(io, alloc, .{ .id = 3 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 3 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); @@ -1208,9 +1223,9 @@ test "storage: delete all placements" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); - try s.addImage(io, alloc, .{ .id = 2 }); - try s.addImage(io, alloc, .{ .id = 3 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 3 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); @@ -1232,9 +1247,9 @@ test "storage: delete all placements by image id" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); - try s.addImage(io, alloc, .{ .id = 2 }); - try s.addImage(io, alloc, .{ .id = 3 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 3 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); @@ -1256,9 +1271,9 @@ test "storage: delete all placements by image id and unused images" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); - try s.addImage(io, alloc, .{ .id = 2 }); - try s.addImage(io, alloc, .{ .id = 3 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 3 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); @@ -1280,9 +1295,9 @@ test "storage: delete placement by specific id" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); - try s.addImage(io, alloc, .{ .id = 2 }); - try s.addImage(io, alloc, .{ .id = 3 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 3 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); @@ -1311,8 +1326,8 @@ test "storage: delete intersecting cursor" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1, .width = 50, .height = 50 }); - try s.addImage(io, alloc, .{ .id = 2, .width = 25, .height = 25 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 50, .height = 50 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); @@ -1343,7 +1358,7 @@ test "storage: delete intersecting cursor checks interior row column" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1, .width = 10, .height = 10 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 10, .height = 10 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 20, .y = 0 }) } }); @@ -1370,7 +1385,7 @@ test "storage: delete intersecting cell checks interior row column" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1, .width = 10, .height = 10 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 10, .height = 10 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 20, .y = 0 }) } }); @@ -1399,8 +1414,8 @@ test "storage: delete intersecting cursor plus unused" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1, .width = 50, .height = 50 }); - try s.addImage(io, alloc, .{ .id = 2, .width = 25, .height = 25 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 50, .height = 50 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); @@ -1432,8 +1447,8 @@ test "storage: delete intersecting cursor hits multiple" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1, .width = 50, .height = 50 }); - try s.addImage(io, alloc, .{ .id = 2, .width = 25, .height = 25 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 50, .height = 50 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); @@ -1459,8 +1474,8 @@ test "storage: delete by column" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1, .width = 50, .height = 50 }); - try s.addImage(io, alloc, .{ .id = 2, .width = 25, .height = 25 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 50, .height = 50 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); @@ -1492,7 +1507,7 @@ test "storage: delete by column 1x1" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1, .width = 1, .height = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 1, .height = 1 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 0 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 3, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 2, .y = 0 }) } }); @@ -1527,8 +1542,8 @@ test "storage: delete by row" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1, .width = 50, .height = 50 }); - try s.addImage(io, alloc, .{ .id = 2, .width = 25, .height = 25 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 50, .height = 50 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); @@ -1560,7 +1575,7 @@ test "storage: delete by row 1x1" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1, .width = 1, .height = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 1, .height = 1 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 0 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 1, 3, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 2 }) } }); @@ -1593,9 +1608,9 @@ test "storage: delete images by range 1" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); - try s.addImage(io, alloc, .{ .id = 2 }); - try s.addImage(io, alloc, .{ .id = 3 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 3 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try testing.expectEqual(@as(usize, 3), s.images.count()); @@ -1619,9 +1634,9 @@ test "storage: delete images by range 2" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); - try s.addImage(io, alloc, .{ .id = 2 }); - try s.addImage(io, alloc, .{ .id = 3 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 3 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try testing.expectEqual(@as(usize, 3), s.images.count()); @@ -1645,9 +1660,9 @@ test "storage: delete images by range 3" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); - try s.addImage(io, alloc, .{ .id = 2 }); - try s.addImage(io, alloc, .{ .id = 3 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 3 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 3, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); @@ -1680,9 +1695,9 @@ test "storage: delete images by range 4" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); - try s.addImage(io, alloc, .{ .id = 2 }); - try s.addImage(io, alloc, .{ .id = 3 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 3 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); try s.addPlacement(io, alloc, t.screens.active, 3, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); @@ -1872,7 +1887,7 @@ test "storage: generation stamps on image add and replace" { // Fresh storage has generation zero (never mutated). try testing.expectEqual(@as(u64, 0), s.generation); - try s.addImage(io, alloc, .{ .id = 1, .width = 1, .height = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 1, .height = 1 }); const gen1 = s.generation; try testing.expect(gen1 > 0); @@ -1880,7 +1895,7 @@ test "storage: generation stamps on image add and replace" { try testing.expectEqual(gen1, img1.generation); // A second image gets a strictly greater stamp. - try s.addImage(io, alloc, .{ .id = 2, .width = 1, .height = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2, .width = 1, .height = 1 }); const gen2 = s.generation; try testing.expect(gen2 > gen1); try testing.expectEqual(gen2, s.imageById(2).?.generation); @@ -1888,7 +1903,7 @@ test "storage: generation stamps on image add and replace" { // Retransmitting the same image ID (identical dimensions) gets a // fresh stamp: this is what makes same-sized retransmissions // detectable by renderers. - try s.addImage(io, alloc, .{ .id = 1, .width = 1, .height = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 1, .height = 1 }); const gen3 = s.generation; try testing.expect(gen3 > gen2); try testing.expectEqual(gen3, s.imageById(1).?.generation); @@ -1906,7 +1921,7 @@ test "storage: generation bumps on placement and delete" { var s: ImageStorage = .{}; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); const gen_add = s.generation; try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); @@ -1933,7 +1948,7 @@ test "storage: generation bumps when setLimit evicts or disables" { defer s.deinit(alloc, t.screens.active); const data = try alloc.dupe(u8, "1234"); - try s.addImage(io, alloc, .{ + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 1, .height = 1, @@ -1968,12 +1983,12 @@ test "storage: imageByNumber returns most recently transmitted" { // Two images sharing a number: the newest transmission wins, // regardless of insertion order or clock resolution. - try s.addImage(io, alloc, .{ .id = 1, .number = 7 }); - try s.addImage(io, alloc, .{ .id = 2, .number = 7 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .number = 7 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 2, .number = 7 }); try testing.expectEqual(@as(u32, 2), s.imageByNumber(7).?.id); // Retransmit the first: it becomes the newest. - try s.addImage(io, alloc, .{ .id = 1, .number = 7 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .number = 7 }); try testing.expectEqual(@as(u32, 1), s.imageByNumber(7).?.id); } @@ -2002,7 +2017,7 @@ test "storage: no-op delete does not mark a mutation" { try testing.expectEqual(@as(u64, 0), s.generation); // Same for a delete that matches nothing. - try s.addImage(io, alloc, .{ .id = 1 }); + try s.addImage(io, alloc, t.screens.active, .{ .id = 1 }); try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } }); const gen = s.generation; s.dirty = false; @@ -2026,17 +2041,17 @@ test "storage: evicts images in priority order" { var s: ImageStorage = .{ .total_limit = 192 }; defer s.deinit(alloc, t.screens.active); - try s.addImage(io, alloc, .{ + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .data = .{ .complete = try alloc.dupe(u8, "*" ** 64) }, .metadata = .{ .transient = false }, }); - try s.addImage(io, alloc, .{ + try s.addImage(io, alloc, t.screens.active, .{ .id = 2, .data = .{ .complete = try alloc.dupe(u8, "*" ** 64) }, .metadata = .{ .transient = true }, }); - try s.addImage(io, alloc, .{ + try s.addImage(io, alloc, t.screens.active, .{ .id = 3, .data = .{ .complete = try alloc.dupe(u8, "*" ** 64) }, .metadata = .{ .transient = true }, @@ -2051,7 +2066,7 @@ test "storage: evicts images in priority order" { ); const gen = s.generation; - const result = s.evictImage(io, alloc, 96); + const result = s.evictImage(io, alloc, t.screens.active, 96); try testing.expect(s.dirty); try testing.expect(s.generation > gen); try testing.expectEqual(true, result); @@ -2061,6 +2076,50 @@ test "storage: evicts images in priority order" { try testing.expect(!s.images.contains(3)); } +test "storage: eviction releases placement pins" { + const testing = std.testing; + const io = testing.io; + const alloc = testing.allocator; + var t = try terminal.Terminal.init(io, alloc, .{ .rows = 3, .cols = 3 }); + defer t.deinit(alloc); + + var s: ImageStorage = .{ .total_limit = 128 }; + defer s.deinit(alloc, t.screens.active); + + const tracked = t.screens.active.pages.countTrackedPins(); + try s.addImage(io, alloc, t.screens.active, .{ + .id = 1, + .data = .{ .complete = try alloc.dupe(u8, "*" ** 64) }, + }); + try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ + .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) }, + }); + try s.addImage(io, alloc, t.screens.active, .{ + .id = 2, + .data = .{ .complete = try alloc.dupe(u8, "*" ** 64) }, + }); + try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{ + .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }, + }); + try testing.expectEqual(tracked + 2, t.screens.active.pages.countTrackedPins()); + + // Adding a third image evicts the oldest equally-ranked image and its + // placement. The newer image's placement and tracked pin remain intact. + try s.addImage(io, alloc, t.screens.active, .{ + .id = 3, + .data = .{ .complete = try alloc.dupe(u8, "*" ** 64) }, + }); + try testing.expect(!s.images.contains(1)); + try testing.expect(s.images.contains(2)); + try testing.expect(s.images.contains(3)); + try testing.expectEqual(@as(usize, 1), s.placements.count()); + try testing.expect(s.placements.contains(.{ + .image_id = 2, + .placement_id = .{ .tag = .external, .id = 1 }, + })); + try testing.expectEqual(tracked + 1, t.screens.active.pages.countTrackedPins()); +} + test "storage: pending image completes once and preserves age" { const testing = std.testing; const io = testing.io; @@ -2071,7 +2130,7 @@ test "storage: pending image completes once and preserves age" { var s: ImageStorage = .{ .total_limit = 16 }; defer s.deinit(alloc, t.screens.active); - const pending = try s.addPendingImage(io, alloc, .{ + const pending = try s.addPendingImage(io, alloc, t.screens.active, .{ .id = 1, .number = 7, .width = 2, @@ -2119,7 +2178,7 @@ test "storage: stale pending completion loses to delete replacement and eviction var s: ImageStorage = .{ .total_limit = 8 }; defer s.deinit(alloc, t.screens.active); - const deleted = try s.addPendingImage(io, alloc, .{ + const deleted = try s.addPendingImage(io, alloc, t.screens.active, .{ .id = 1, .data = .{ .pending = 4 }, }); @@ -2129,11 +2188,11 @@ test "storage: stale pending completion loses to delete replacement and eviction try testing.expect(!deleted_completed); defer alloc.free(deleted_data); - const replaced = try s.addPendingImage(io, alloc, .{ + const replaced = try s.addPendingImage(io, alloc, t.screens.active, .{ .id = 2, .data = .{ .pending = 4 }, }); - try s.addImage(io, alloc, .{ + try s.addImage(io, alloc, t.screens.active, .{ .id = 2, .data = .{ .complete = try alloc.dupe(u8, "live") }, }); @@ -2143,11 +2202,11 @@ test "storage: stale pending completion loses to delete replacement and eviction defer alloc.free(replaced_data); try testing.expectEqualStrings("live", s.imageById(2).?.data.bytes().?); - const evicted = try s.addPendingImage(io, alloc, .{ + const evicted = try s.addPendingImage(io, alloc, t.screens.active, .{ .id = 3, .data = .{ .pending = 4 }, }); - try s.addImage(io, alloc, .{ + try s.addImage(io, alloc, t.screens.active, .{ .id = 4, .data = .{ .complete = try alloc.dupe(u8, "12345678") }, }); @@ -2168,7 +2227,7 @@ test "storage: replacement reuses pending reservation and preserves placements" var s: ImageStorage = .{ .total_limit = 12 }; defer s.deinit(alloc, t.screens.active); - const pending = try s.addPendingImage(io, alloc, .{ + const pending = try s.addPendingImage(io, alloc, t.screens.active, .{ .id = 1, .width = 2, .height = 1, @@ -2178,12 +2237,12 @@ test "storage: replacement reuses pending reservation and preserves placements" try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) }, }); - try s.addImage(io, alloc, .{ + try s.addImage(io, alloc, t.screens.active, .{ .id = 2, .data = .{ .complete = try alloc.dupe(u8, "keep") }, }); - try s.addImage(io, alloc, .{ + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .width = 2, .height = 1, @@ -2202,7 +2261,7 @@ test "storage: replacement reuses pending reservation and preserves placements" // Growing the replacement requires eviction, but the replacement ID and // its placement are excluded. The other image supplies the needed bytes. - try s.addImage(io, alloc, .{ + try s.addImage(io, alloc, t.screens.active, .{ .id = 1, .data = .{ .complete = try alloc.dupe(u8, "1234567890") }, }); @@ -2227,17 +2286,17 @@ test "storage: pending images share exact eviction ordering" { var s: ImageStorage = .{ .total_limit = 192 }; defer s.deinit(alloc, t.screens.active); - _ = try s.addPendingImage(io, alloc, .{ + _ = try s.addPendingImage(io, alloc, t.screens.active, .{ .id = 1, .data = .{ .pending = 64 }, .metadata = .{ .transient = false }, }); - _ = try s.addPendingImage(io, alloc, .{ + _ = try s.addPendingImage(io, alloc, t.screens.active, .{ .id = 2, .data = .{ .pending = 64 }, .metadata = .{ .transient = true }, }); - try s.addImage(io, alloc, .{ + try s.addImage(io, alloc, t.screens.active, .{ .id = 3, .data = .{ .complete = try alloc.dupe(u8, "*" ** 64) }, .metadata = .{ .transient = true }, @@ -2247,7 +2306,7 @@ test "storage: pending images share exact eviction ordering" { }); // ID 3 is transient and unused, so one exact-size eviction is enough. - try testing.expect(s.evictImage(io, alloc, 64)); + try testing.expect(s.evictImage(io, alloc, t.screens.active, 64)); try testing.expect(s.images.contains(1)); try testing.expect(s.images.contains(2)); try testing.expect(!s.images.contains(3)); diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index 21fa0b3a5..485502593 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -1195,7 +1195,7 @@ test "unicode render placement: dog 4x2" { defer s.deinit(alloc, t.screens.active); const image: Image = .{ .id = 1, .width = 500, .height = 306 }; - try s.addImage(io, alloc, image); + try s.addImage(io, alloc, t.screens.active, image); try s.addPlacement(io, alloc, t.screens.active, 1, 0, .{ .location = .{ .virtual = {} }, .columns = 4, @@ -1263,7 +1263,7 @@ test "unicode render placement: dog 2x2 with blank cells" { defer s.deinit(alloc, t.screens.active); const image: Image = .{ .id = 1, .width = 500, .height = 306 }; - try s.addImage(io, alloc, image); + try s.addImage(io, alloc, t.screens.active, image); try s.addPlacement(io, alloc, t.screens.active, 1, 0, .{ .location = .{ .virtual = {} }, .columns = 2, @@ -1330,7 +1330,7 @@ test "unicode render placement: dog 1x1" { defer s.deinit(alloc, t.screens.active); const image: Image = .{ .id = 1, .width = 500, .height = 306 }; - try s.addImage(io, alloc, image); + try s.addImage(io, alloc, t.screens.active, image); try s.addPlacement(io, alloc, t.screens.active, 1, 0, .{ .location = .{ .virtual = {} }, .columns = 1, diff --git a/src/terminal/snapshot/snapshot.zig b/src/terminal/snapshot/snapshot.zig index dc8d7524d..0859755ce 100644 --- a/src/terminal/snapshot/snapshot.zig +++ b/src/terminal/snapshot/snapshot.zig @@ -1226,6 +1226,7 @@ test "complete snapshot preserves Kitty virtual placeholders" { try t.screens.active.kitty_images.addImage( testing.io, testing.allocator, + t.screens.active, .{ .id = 1 }, ); try t.screens.active.kitty_images.addPlacement(