mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-09 21:01:58 -04:00
124 lines
4.1 KiB
Zig
124 lines
4.1 KiB
Zig
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
|
//
|
|
// Francis Bouvier <francis@lightpanda.io>
|
|
// Pierre Tachoire <pierre@lightpanda.io>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
pub fn LruCache(comptime K: type) type {
|
|
return struct {
|
|
const Self = @This();
|
|
|
|
const Map = if (K == []const u8) std.StringHashMapUnmanaged(*Entry) else std.AutoArrayHashMapUnmanaged(K, *Entry);
|
|
|
|
const Entry = struct {
|
|
key: K,
|
|
node: std.DoublyLinkedList.Node = .{},
|
|
};
|
|
|
|
allocator: Allocator,
|
|
capacity: usize,
|
|
list: std.DoublyLinkedList = .{},
|
|
map: Map = .empty,
|
|
|
|
pub fn init(allocator: Allocator, capacity: usize) Self {
|
|
return .{ .allocator = allocator, .capacity = capacity };
|
|
}
|
|
|
|
pub fn deinit(self: *Self) void {
|
|
var it = self.map.valueIterator();
|
|
while (it.next()) |node| self.allocator.destroy(node.*);
|
|
self.map.deinit(self.allocator);
|
|
}
|
|
|
|
pub fn insert(self: *Self, key: K) !?K {
|
|
const entry = try self.allocator.create(Entry);
|
|
entry.* = .{ .key = key };
|
|
try self.map.put(self.allocator, key, entry);
|
|
self.list.append(&entry.node);
|
|
|
|
if (self.map.count() <= self.capacity) return null;
|
|
return self.evictOldest();
|
|
}
|
|
|
|
pub fn touch(self: *Self, key: K) void {
|
|
const entry = self.map.get(key).?;
|
|
self.list.remove(&entry.node);
|
|
self.list.append(&entry.node);
|
|
}
|
|
|
|
fn evictOldest(self: *Self) ?K {
|
|
const node = self.list.popFirst() orelse return null;
|
|
const entry: *Entry = @fieldParentPtr("node", node);
|
|
const key = entry.key;
|
|
_ = self.map.remove(key);
|
|
self.allocator.destroy(entry);
|
|
return key;
|
|
}
|
|
};
|
|
}
|
|
|
|
const testing = @import("../testing.zig");
|
|
|
|
test "LruCache: no eviction under capacity" {
|
|
var q = LruCache([]const u8).init(testing.allocator, 3);
|
|
defer q.deinit();
|
|
|
|
try testing.expectEqual(null, try q.insert("a"));
|
|
try testing.expectEqual(null, try q.insert("b"));
|
|
try testing.expectEqual(null, try q.insert("c"));
|
|
try testing.expectEqual(3, q.list.len());
|
|
}
|
|
|
|
test "LruCache: evicts oldest key once over capacity" {
|
|
var q = LruCache([]const u8).init(testing.allocator, 2);
|
|
defer q.deinit();
|
|
|
|
try testing.expectEqual(null, try q.insert("a"));
|
|
try testing.expectEqual(null, try q.insert("b"));
|
|
|
|
const evicted = try q.insert("c");
|
|
try testing.expect(evicted != null);
|
|
try testing.expectString("a", evicted.?);
|
|
try testing.expectEqual(2, q.list.len());
|
|
|
|
const evicted2 = try q.insert("d");
|
|
try testing.expect(evicted2 != null);
|
|
try testing.expectString("b", evicted2.?);
|
|
try testing.expectEqual(2, q.list.len());
|
|
}
|
|
|
|
test "LruCache: touch properly prevents eviction on oldest" {
|
|
var q = LruCache([]const u8).init(testing.allocator, 2);
|
|
defer q.deinit();
|
|
|
|
try testing.expectEqual(null, try q.insert("a"));
|
|
try testing.expectEqual(null, try q.insert("b"));
|
|
q.touch("a");
|
|
|
|
const evicted = try q.insert("c");
|
|
try testing.expect(evicted != null);
|
|
try testing.expectString("b", evicted.?);
|
|
try testing.expectEqual(2, q.list.len());
|
|
q.touch("a");
|
|
|
|
const evicted2 = try q.insert("d");
|
|
try testing.expect(evicted2 != null);
|
|
try testing.expectString("c", evicted2.?);
|
|
try testing.expectEqual(2, q.list.len());
|
|
}
|