|
|
|
|
@@ -171,12 +171,12 @@ pub const ImageStorage = struct {
|
|
|
|
|
) void {
|
|
|
|
|
if (self.loading) |loading| loading.destroy(alloc);
|
|
|
|
|
|
|
|
|
|
self.clearPlacements(s);
|
|
|
|
|
self.placements.deinit(alloc);
|
|
|
|
|
|
|
|
|
|
var it = self.images.iterator();
|
|
|
|
|
while (it.next()) |kv| kv.value_ptr.deinit(alloc);
|
|
|
|
|
self.images.deinit(alloc);
|
|
|
|
|
|
|
|
|
|
self.clearPlacements(s);
|
|
|
|
|
self.placements.deinit(alloc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Kitty image protocol is enabled if we have a non-zero limit.
|
|
|
|
|
@@ -207,7 +207,7 @@ pub const ImageStorage = struct {
|
|
|
|
|
alloc: Allocator,
|
|
|
|
|
s: *terminal.Screen,
|
|
|
|
|
limit: usize,
|
|
|
|
|
) !void {
|
|
|
|
|
) void {
|
|
|
|
|
// Special case disabling by quickly deleting all
|
|
|
|
|
if (limit == 0) {
|
|
|
|
|
const image_limits = self.image_limits;
|
|
|
|
|
@@ -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 (!try self.evictImage(io, alloc, req_bytes)) {
|
|
|
|
|
if (!self.evictImage(io, alloc, req_bytes)) {
|
|
|
|
|
log.warn("failed to evict enough images for required bytes", .{});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -258,7 +258,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 (!try self.evictImageExcept(io, alloc, req_bytes, img.id)) {
|
|
|
|
|
if (!self.evictImageExcept(io, alloc, req_bytes, img.id)) {
|
|
|
|
|
log.warn("failed to evict enough images for required bytes", .{});
|
|
|
|
|
return error.OutOfMemory;
|
|
|
|
|
}
|
|
|
|
|
@@ -268,13 +268,16 @@ pub const ImageStorage = struct {
|
|
|
|
|
|
|
|
|
|
log.debug("addImage image={}", .{img.withoutData()});
|
|
|
|
|
|
|
|
|
|
// Write our new image
|
|
|
|
|
if (gop.found_existing) {
|
|
|
|
|
// Write our new image, preserving placements across replacement.
|
|
|
|
|
const placement_count = if (gop.found_existing) count: {
|
|
|
|
|
self.total_bytes -= gop.value_ptr.data.len();
|
|
|
|
|
const count = gop.value_ptr.metadata.placement_count;
|
|
|
|
|
gop.value_ptr.deinit(alloc);
|
|
|
|
|
}
|
|
|
|
|
break :count count;
|
|
|
|
|
} else 0;
|
|
|
|
|
|
|
|
|
|
gop.value_ptr.* = img;
|
|
|
|
|
gop.value_ptr.metadata.placement_count = placement_count;
|
|
|
|
|
self.total_bytes += new_len;
|
|
|
|
|
|
|
|
|
|
// Stamp the stored image with a fresh generation. This gives
|
|
|
|
|
@@ -347,7 +350,19 @@ pub const ImageStorage = struct {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const gop = try self.placements.getOrPut(alloc, key);
|
|
|
|
|
if (gop.found_existing) gop.value_ptr.deinit(s);
|
|
|
|
|
if (gop.found_existing) {
|
|
|
|
|
gop.value_ptr.deinit(s);
|
|
|
|
|
} else {
|
|
|
|
|
const img = self.images.getPtr(image_id).?;
|
|
|
|
|
img.metadata.placement_count = std.math.add(
|
|
|
|
|
@TypeOf(img.metadata.placement_count),
|
|
|
|
|
img.metadata.placement_count,
|
|
|
|
|
1,
|
|
|
|
|
) catch {
|
|
|
|
|
self.placements.removeByPtr(gop.key_ptr);
|
|
|
|
|
return error.OutOfMemory;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
gop.value_ptr.* = p;
|
|
|
|
|
|
|
|
|
|
self.markMutated(io);
|
|
|
|
|
@@ -369,7 +384,7 @@ pub const ImageStorage = struct {
|
|
|
|
|
if (!pin.garbage) continue;
|
|
|
|
|
|
|
|
|
|
entry.value_ptr.deinit(s);
|
|
|
|
|
self.placements.removeByPtr(entry.key_ptr);
|
|
|
|
|
self.removePlacementByPtr(entry.key_ptr);
|
|
|
|
|
removed = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -382,6 +397,13 @@ pub const ImageStorage = struct {
|
|
|
|
|
self.placements.clearRetainingCapacity();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn removePlacementByPtr(self: *ImageStorage, key: *PlacementKey) void {
|
|
|
|
|
const img = self.images.getPtr(key.image_id).?;
|
|
|
|
|
assert(img.metadata.placement_count > 0);
|
|
|
|
|
img.metadata.placement_count -= 1;
|
|
|
|
|
self.placements.removeByPtr(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get an image by its ID. If the image doesn't exist, null is returned.
|
|
|
|
|
pub fn imageById(self: *const ImageStorage, image_id: u32) ?Image {
|
|
|
|
|
return self.images.get(image_id);
|
|
|
|
|
@@ -437,7 +459,7 @@ pub const ImageStorage = struct {
|
|
|
|
|
// Deinit the placement and remove it
|
|
|
|
|
const image_id = entry.key_ptr.image_id;
|
|
|
|
|
entry.value_ptr.deinit(t.screens.active);
|
|
|
|
|
self.placements.removeByPtr(entry.key_ptr);
|
|
|
|
|
self.removePlacementByPtr(entry.key_ptr);
|
|
|
|
|
if (delete_images) self.deleteIfUnused(alloc, image_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -535,7 +557,7 @@ pub const ImageStorage = struct {
|
|
|
|
|
const rect = entry.value_ptr.rect(img, t) orelse continue;
|
|
|
|
|
if (rect.top_left.x <= x and rect.bottom_right.x >= x) {
|
|
|
|
|
entry.value_ptr.deinit(t.screens.active);
|
|
|
|
|
self.placements.removeByPtr(entry.key_ptr);
|
|
|
|
|
self.removePlacementByPtr(entry.key_ptr);
|
|
|
|
|
if (v.delete) self.deleteIfUnused(alloc, img.id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -564,7 +586,7 @@ pub const ImageStorage = struct {
|
|
|
|
|
target_pin_copy.x = rect.top_left.x;
|
|
|
|
|
if (target_pin_copy.isBetween(rect.top_left, rect.bottom_right)) {
|
|
|
|
|
entry.value_ptr.deinit(t.screens.active);
|
|
|
|
|
self.placements.removeByPtr(entry.key_ptr);
|
|
|
|
|
self.removePlacementByPtr(entry.key_ptr);
|
|
|
|
|
if (v.delete) self.deleteIfUnused(alloc, img.id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -584,7 +606,7 @@ pub const ImageStorage = struct {
|
|
|
|
|
if (entry.value_ptr.z == v.z) {
|
|
|
|
|
const image_id = entry.key_ptr.image_id;
|
|
|
|
|
entry.value_ptr.deinit(t.screens.active);
|
|
|
|
|
self.placements.removeByPtr(entry.key_ptr);
|
|
|
|
|
self.removePlacementByPtr(entry.key_ptr);
|
|
|
|
|
if (v.delete) self.deleteIfUnused(alloc, image_id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -605,7 +627,7 @@ pub const ImageStorage = struct {
|
|
|
|
|
if (entry.key_ptr.image_id >= v.first and entry.key_ptr.image_id <= v.last) {
|
|
|
|
|
const image_id = entry.key_ptr.image_id;
|
|
|
|
|
entry.value_ptr.deinit(t.screens.active);
|
|
|
|
|
self.placements.removeByPtr(entry.key_ptr);
|
|
|
|
|
self.removePlacementByPtr(entry.key_ptr);
|
|
|
|
|
if (v.delete) self.deleteIfUnused(alloc, image_id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -631,7 +653,7 @@ pub const ImageStorage = struct {
|
|
|
|
|
while (it.next()) |entry| {
|
|
|
|
|
if (entry.key_ptr.image_id == image_id) {
|
|
|
|
|
entry.value_ptr.deinit(s);
|
|
|
|
|
self.placements.removeByPtr(entry.key_ptr);
|
|
|
|
|
self.removePlacementByPtr(entry.key_ptr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
@@ -640,7 +662,7 @@ pub const ImageStorage = struct {
|
|
|
|
|
.placement_id = .{ .tag = .external, .id = placement_id },
|
|
|
|
|
})) |entry| {
|
|
|
|
|
entry.value_ptr.deinit(s);
|
|
|
|
|
self.placements.removeByPtr(entry.key_ptr);
|
|
|
|
|
self.removePlacementByPtr(entry.key_ptr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -651,19 +673,12 @@ pub const ImageStorage = struct {
|
|
|
|
|
|
|
|
|
|
/// Delete an image if it is unused.
|
|
|
|
|
fn deleteIfUnused(self: *ImageStorage, alloc: Allocator, image_id: u32) void {
|
|
|
|
|
var it = self.placements.iterator();
|
|
|
|
|
while (it.next()) |kv| {
|
|
|
|
|
if (kv.key_ptr.image_id == image_id) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const entry = self.images.getEntry(image_id) orelse return;
|
|
|
|
|
if (entry.value_ptr.metadata.placement_count > 0) return;
|
|
|
|
|
|
|
|
|
|
// If we get here, we can delete the image.
|
|
|
|
|
if (self.images.getEntry(image_id)) |entry| {
|
|
|
|
|
self.total_bytes -= entry.value_ptr.data.len();
|
|
|
|
|
entry.value_ptr.deinit(alloc);
|
|
|
|
|
self.images.removeByPtr(entry.key_ptr);
|
|
|
|
|
}
|
|
|
|
|
self.total_bytes -= entry.value_ptr.data.len();
|
|
|
|
|
entry.value_ptr.deinit(alloc);
|
|
|
|
|
self.images.removeByPtr(entry.key_ptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Deletes all placements intersecting a screen point.
|
|
|
|
|
@@ -686,7 +701,7 @@ pub const ImageStorage = struct {
|
|
|
|
|
if (target_pin.isBetween(rect.top_left, rect.bottom_right)) {
|
|
|
|
|
if (filter) |f| if (!f(filter_ctx, entry.value_ptr.*)) continue;
|
|
|
|
|
entry.value_ptr.deinit(t.screens.active);
|
|
|
|
|
self.placements.removeByPtr(entry.key_ptr);
|
|
|
|
|
self.removePlacementByPtr(entry.key_ptr);
|
|
|
|
|
if (delete_unused) self.deleteIfUnused(alloc, img.id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -698,7 +713,7 @@ 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 {
|
|
|
|
|
fn evictImage(self: *ImageStorage, io: std.Io, alloc: Allocator, req: usize) bool {
|
|
|
|
|
return self.evictImageExcept(io, alloc, req, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -710,128 +725,77 @@ pub const ImageStorage = struct {
|
|
|
|
|
alloc: Allocator,
|
|
|
|
|
req: usize,
|
|
|
|
|
exclude_id: ?u32,
|
|
|
|
|
) !bool {
|
|
|
|
|
) bool {
|
|
|
|
|
assert(req <= self.total_limit);
|
|
|
|
|
|
|
|
|
|
// Ironically we allocate to evict. We should probably redesign the
|
|
|
|
|
// data structures to avoid this but for now allocating a little
|
|
|
|
|
// bit is fine compared to the megabytes we're looking to save.
|
|
|
|
|
const Candidate = struct {
|
|
|
|
|
id: u32,
|
|
|
|
|
generation: u64,
|
|
|
|
|
// Map images into four distinct blocks:
|
|
|
|
|
|
|
|
|
|
// Lower values are evicted first:
|
|
|
|
|
// 0: transient, unused
|
|
|
|
|
// 1: not transient, unused
|
|
|
|
|
// 2: transient, used
|
|
|
|
|
// 3: not transient, used
|
|
|
|
|
block: u2,
|
|
|
|
|
priority: u2,
|
|
|
|
|
|
|
|
|
|
fn init(img: *const Image) @This() {
|
|
|
|
|
return .{
|
|
|
|
|
.id = img.id,
|
|
|
|
|
.generation = img.generation,
|
|
|
|
|
.priority = (if (img.metadata.transient) @as(u2, 0) else @as(u2, 1)) +
|
|
|
|
|
(if (img.metadata.placement_count > 0) @as(u2, 2) else @as(u2, 0)),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn lessThan(lhs: @This(), rhs: @This()) bool {
|
|
|
|
|
if (lhs.priority != rhs.priority)
|
|
|
|
|
return lhs.priority < rhs.priority;
|
|
|
|
|
if (lhs.generation != rhs.generation)
|
|
|
|
|
return lhs.generation < rhs.generation;
|
|
|
|
|
return lhs.id < rhs.id;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const candidates = try alloc.alloc(Candidate, self.images.count());
|
|
|
|
|
defer alloc.free(candidates);
|
|
|
|
|
|
|
|
|
|
var it = self.images.iterator();
|
|
|
|
|
var candidate_count: usize = 0;
|
|
|
|
|
while (it.next()) |kv| {
|
|
|
|
|
const img = kv.value_ptr;
|
|
|
|
|
if (exclude_id != null and img.id == exclude_id.?) continue;
|
|
|
|
|
|
|
|
|
|
// This is a huge waste. See comment above about redesigning
|
|
|
|
|
// our data structures to avoid this. Eviction should be very
|
|
|
|
|
// rare though and we never have that many images/placements
|
|
|
|
|
// so hopefully this will last a long time.
|
|
|
|
|
const used = used: {
|
|
|
|
|
var p_it = self.placements.iterator();
|
|
|
|
|
while (p_it.next()) |p_kv| {
|
|
|
|
|
if (p_kv.key_ptr.image_id == img.id) {
|
|
|
|
|
break :used true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break :used false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transient = img.usage.transient;
|
|
|
|
|
|
|
|
|
|
candidates[candidate_count] = .{
|
|
|
|
|
.id = img.id,
|
|
|
|
|
.generation = img.generation,
|
|
|
|
|
// Map images into four distinct blocks:
|
|
|
|
|
// 0: transient, unused
|
|
|
|
|
// 1: not transient, unused
|
|
|
|
|
// 2: transient, used
|
|
|
|
|
// 3: not transient, used
|
|
|
|
|
.block = (if (transient) @as(u2, 0) else @as(u2, 1)) + (if (used) @as(u2, 2) else @as(u2, 0)),
|
|
|
|
|
};
|
|
|
|
|
candidate_count += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const candidate_slice = candidates[0..candidate_count];
|
|
|
|
|
|
|
|
|
|
// Sort
|
|
|
|
|
std.mem.sortUnstable(
|
|
|
|
|
Candidate,
|
|
|
|
|
candidate_slice,
|
|
|
|
|
{},
|
|
|
|
|
struct {
|
|
|
|
|
fn lessThan(
|
|
|
|
|
ctx: void,
|
|
|
|
|
lhs: Candidate,
|
|
|
|
|
rhs: Candidate,
|
|
|
|
|
) bool {
|
|
|
|
|
_ = ctx;
|
|
|
|
|
|
|
|
|
|
// If images mapped into different blocks, prioritize lower
|
|
|
|
|
// numbered blocks.
|
|
|
|
|
if (lhs.block < rhs.block) return true;
|
|
|
|
|
if (lhs.block > rhs.block) return false;
|
|
|
|
|
|
|
|
|
|
// If images mapped to the same block, compare generations.
|
|
|
|
|
return if (lhs.generation == rhs.generation)
|
|
|
|
|
// If the generation is the same, use the ID to
|
|
|
|
|
// prioritize evicting "earlier" images.
|
|
|
|
|
lhs.id < rhs.id
|
|
|
|
|
else
|
|
|
|
|
// If the generation is different, prioritize evicting
|
|
|
|
|
// images from earlied generations.
|
|
|
|
|
lhs.generation < rhs.generation;
|
|
|
|
|
}
|
|
|
|
|
}.lessThan,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Evicting anything is a content mutation. This matters for the
|
|
|
|
|
// setLimit path in particular, which doesn't otherwise mark it.
|
|
|
|
|
var any_evicted = false;
|
|
|
|
|
defer if (any_evicted) self.markMutated(io);
|
|
|
|
|
const images_before = self.images.count();
|
|
|
|
|
defer if (self.images.count() != images_before) self.markMutated(io);
|
|
|
|
|
|
|
|
|
|
// They're in order of best to evict.
|
|
|
|
|
var evicted: usize = 0;
|
|
|
|
|
for (candidate_slice) |c| {
|
|
|
|
|
// Delete all the placements for this image and the image.
|
|
|
|
|
while (evicted < req) {
|
|
|
|
|
const c = candidate: {
|
|
|
|
|
var best: ?Candidate = null;
|
|
|
|
|
var it = self.images.iterator();
|
|
|
|
|
while (it.next()) |kv| {
|
|
|
|
|
const img = kv.value_ptr;
|
|
|
|
|
if (exclude_id != null and img.id == exclude_id.?) continue;
|
|
|
|
|
|
|
|
|
|
const current = Candidate.init(img);
|
|
|
|
|
if (best == null or current.lessThan(best.?)) best = current;
|
|
|
|
|
}
|
|
|
|
|
break :candidate best orelse return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var p_it = self.placements.iterator();
|
|
|
|
|
while (p_it.next()) |entry| {
|
|
|
|
|
if (entry.key_ptr.image_id == c.id) {
|
|
|
|
|
self.placements.removeByPtr(entry.key_ptr);
|
|
|
|
|
any_evicted = true;
|
|
|
|
|
self.removePlacementByPtr(entry.key_ptr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self.images.getEntry(c.id)) |entry| {
|
|
|
|
|
const image_len = entry.value_ptr.data.len();
|
|
|
|
|
log.info("evicting image id={} bytes={}", .{ c.id, image_len });
|
|
|
|
|
const entry = self.images.getEntry(c.id).?;
|
|
|
|
|
const image_len = entry.value_ptr.data.len();
|
|
|
|
|
log.info("evicting image id={} bytes={}", .{ c.id, image_len });
|
|
|
|
|
|
|
|
|
|
evicted += image_len;
|
|
|
|
|
self.total_bytes -= image_len;
|
|
|
|
|
evicted += image_len;
|
|
|
|
|
self.total_bytes -= image_len;
|
|
|
|
|
|
|
|
|
|
entry.value_ptr.deinit(alloc);
|
|
|
|
|
self.images.removeByPtr(entry.key_ptr);
|
|
|
|
|
any_evicted = true;
|
|
|
|
|
|
|
|
|
|
if (evicted >= req) return true;
|
|
|
|
|
}
|
|
|
|
|
entry.value_ptr.deinit(alloc);
|
|
|
|
|
self.images.removeByPtr(entry.key_ptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Every placement is uniquely identified by the image ID and the
|
|
|
|
|
@@ -1162,6 +1126,28 @@ test "storage: adding placement reclaims garbage placements" {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "storage: placement count limit permits replacement" {
|
|
|
|
|
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 = .{};
|
|
|
|
|
defer s.deinit(alloc, t.screens.active);
|
|
|
|
|
try s.addImage(io, alloc, .{ .id = 1 });
|
|
|
|
|
try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .virtual = {} } });
|
|
|
|
|
|
|
|
|
|
const img = s.images.getPtr(1).?;
|
|
|
|
|
img.metadata.placement_count = std.math.maxInt(@TypeOf(img.metadata.placement_count));
|
|
|
|
|
try s.addPlacement(io, alloc, t.screens.active, 1, 1, .{ .location = .{ .virtual = {} } });
|
|
|
|
|
try testing.expectError(
|
|
|
|
|
error.OutOfMemory,
|
|
|
|
|
s.addPlacement(io, alloc, t.screens.active, 1, 2, .{ .location = .{ .virtual = {} } }),
|
|
|
|
|
);
|
|
|
|
|
try testing.expectEqual(@as(usize, 1), s.placements.count());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "storage: delete all placements and images" {
|
|
|
|
|
const testing = std.testing;
|
|
|
|
|
const alloc = testing.allocator;
|
|
|
|
|
@@ -1902,7 +1888,7 @@ test "storage: generation bumps when setLimit evicts or disables" {
|
|
|
|
|
|
|
|
|
|
// Lowering the limit evicts the image and must mark a mutation.
|
|
|
|
|
s.dirty = false;
|
|
|
|
|
try s.setLimit(io, alloc, t.screens.active, 1);
|
|
|
|
|
s.setLimit(io, alloc, t.screens.active, 1);
|
|
|
|
|
try testing.expect(s.dirty);
|
|
|
|
|
try testing.expect(s.generation > gen_add);
|
|
|
|
|
try testing.expectEqual(@as(usize, 0), s.images.count());
|
|
|
|
|
@@ -1910,7 +1896,7 @@ test "storage: generation bumps when setLimit evicts or disables" {
|
|
|
|
|
|
|
|
|
|
// Disabling (limit=0) resets the storage and must mark a mutation.
|
|
|
|
|
s.dirty = false;
|
|
|
|
|
try s.setLimit(io, alloc, t.screens.active, 0);
|
|
|
|
|
s.setLimit(io, alloc, t.screens.active, 0);
|
|
|
|
|
try testing.expect(s.dirty);
|
|
|
|
|
try testing.expect(s.generation > gen_evict);
|
|
|
|
|
}
|
|
|
|
|
@@ -1975,7 +1961,7 @@ test "storage: no-op delete does not mark a mutation" {
|
|
|
|
|
try testing.expect(s.generation > gen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "storage: evict unused transient image" {
|
|
|
|
|
test "storage: evicts images in priority order" {
|
|
|
|
|
const testing = std.testing;
|
|
|
|
|
const io = testing.io;
|
|
|
|
|
const alloc = testing.allocator;
|
|
|
|
|
@@ -1988,17 +1974,17 @@ test "storage: evict unused transient image" {
|
|
|
|
|
try s.addImage(io, alloc, .{
|
|
|
|
|
.id = 1,
|
|
|
|
|
.data = .{ .complete = try alloc.dupe(u8, "*" ** 64) },
|
|
|
|
|
.usage = .{ .transient = false },
|
|
|
|
|
.metadata = .{ .transient = false },
|
|
|
|
|
});
|
|
|
|
|
try s.addImage(io, alloc, .{
|
|
|
|
|
.id = 2,
|
|
|
|
|
.data = .{ .complete = try alloc.dupe(u8, "*" ** 64) },
|
|
|
|
|
.usage = .{ .transient = true },
|
|
|
|
|
.metadata = .{ .transient = true },
|
|
|
|
|
});
|
|
|
|
|
try s.addImage(io, alloc, .{
|
|
|
|
|
.id = 3,
|
|
|
|
|
.data = .{ .complete = try alloc.dupe(u8, "*" ** 64) },
|
|
|
|
|
.usage = .{ .transient = true },
|
|
|
|
|
.metadata = .{ .transient = true },
|
|
|
|
|
});
|
|
|
|
|
try s.addPlacement(
|
|
|
|
|
io,
|
|
|
|
|
@@ -2010,12 +1996,12 @@ test "storage: evict unused transient image" {
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const gen = s.generation;
|
|
|
|
|
const result = try s.evictImage(io, alloc, 32);
|
|
|
|
|
const result = s.evictImage(io, alloc, 96);
|
|
|
|
|
try testing.expect(s.dirty);
|
|
|
|
|
try testing.expect(s.generation > gen);
|
|
|
|
|
try testing.expectEqual(true, result);
|
|
|
|
|
try testing.expectEqual(2, s.images.count());
|
|
|
|
|
try testing.expect(s.images.contains(1));
|
|
|
|
|
try testing.expectEqual(1, s.images.count());
|
|
|
|
|
try testing.expect(!s.images.contains(1));
|
|
|
|
|
try testing.expect(s.images.contains(2));
|
|
|
|
|
try testing.expect(!s.images.contains(3));
|
|
|
|
|
}
|
|
|
|
|
@@ -2169,6 +2155,11 @@ test "storage: replacement reuses pending reservation and preserves placements"
|
|
|
|
|
try testing.expect(!s.images.contains(2));
|
|
|
|
|
try testing.expectEqual(@as(usize, 1), s.placements.count());
|
|
|
|
|
try testing.expectEqual(@as(usize, 10), s.total_bytes);
|
|
|
|
|
|
|
|
|
|
// The placement remains a live reference across both replacements.
|
|
|
|
|
s.delete(io, alloc, &t, .{ .id = .{ .delete = true, .image_id = 1 } });
|
|
|
|
|
try testing.expectEqual(@as(usize, 0), s.images.count());
|
|
|
|
|
try testing.expectEqual(@as(usize, 0), s.placements.count());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "storage: pending images share exact eviction ordering" {
|
|
|
|
|
@@ -2184,24 +2175,24 @@ test "storage: pending images share exact eviction ordering" {
|
|
|
|
|
_ = try s.addPendingImage(io, alloc, .{
|
|
|
|
|
.id = 1,
|
|
|
|
|
.data = .{ .pending = 64 },
|
|
|
|
|
.usage = .{ .transient = false },
|
|
|
|
|
.metadata = .{ .transient = false },
|
|
|
|
|
});
|
|
|
|
|
_ = try s.addPendingImage(io, alloc, .{
|
|
|
|
|
.id = 2,
|
|
|
|
|
.data = .{ .pending = 64 },
|
|
|
|
|
.usage = .{ .transient = true },
|
|
|
|
|
.metadata = .{ .transient = true },
|
|
|
|
|
});
|
|
|
|
|
try s.addImage(io, alloc, .{
|
|
|
|
|
.id = 3,
|
|
|
|
|
.data = .{ .complete = try alloc.dupe(u8, "*" ** 64) },
|
|
|
|
|
.usage = .{ .transient = true },
|
|
|
|
|
.metadata = .{ .transient = true },
|
|
|
|
|
});
|
|
|
|
|
try s.addPlacement(io, alloc, t.screens.active, 2, 1, .{
|
|
|
|
|
.location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ID 3 is transient and unused, so one exact-size eviction is enough.
|
|
|
|
|
try testing.expect(try s.evictImage(io, alloc, 64));
|
|
|
|
|
try testing.expect(s.evictImage(io, alloc, 64));
|
|
|
|
|
try testing.expect(s.images.contains(1));
|
|
|
|
|
try testing.expect(s.images.contains(2));
|
|
|
|
|
try testing.expect(!s.images.contains(3));
|
|
|
|
|
|