Skip to content

Commit

Permalink
fix: big cache performance improvement (#51)
Browse files Browse the repository at this point in the history
* feature: command bar shows mode

* fmt: statusbar

* move: cache

* comment: cache

* refactor: cache

* remove: free image

* todo: clear img from terminal

* remove: redundant cache flag

* rename: max size to lru size

* deps: update libvaxis

* delete: print

* delete: prefetch config

* docs: add missing parentheses
  • Loading branch information
freref authored Feb 23, 2025
1 parent 04a9cb4 commit e2ad107
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 97 deletions.
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
.minimum_zig_version = "0.13.0",
.dependencies = .{
.vaxis = .{
.url = "https://github.com/freref/libvaxis/archive/208e7f7062d53debf52f8d1c8b6b956b4795d398.tar.gz",
.hash = "1220f957b4c5ec81c249fe4b0fec9b2259b89db830c0c0e20da11e659489f26ec4df",
.url = "https://github.com/freref/libvaxis/archive/b2e2588e69bb227a16ec50bdb3bedabe48b7f58a.tar.gz",
.hash = "122098b207673eb55bf51b9981b7b7f5c852149dac82d49d70f07c7227323f8c0617",
},
.fzwatch = .{
.url = "https://github.com/freref/fzwatch/archive/refs/tags/v0.2.1.tar.gz",
Expand Down
22 changes: 11 additions & 11 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ Each binding requires:

```jsonc
{
"next": { "key": "n" }, // Next page
"prev": { "key": "p" }, // Previous page
"scroll_up": { "key": "k" }, // Move viewport up
"scroll_down": { "key": "j" }, // Move viewport down
"scroll_left": { "key": "h" }, // Move viewport left
"scroll_right": { "key": "l" }, // Move viewport right
"zoom_in": { "key": "i" }, // Increase zoom level
"zoom_out": { "key": "o" }, // Decrease zoom level
"colorize": { "key": "z" }, // Toggle color inversion
"quit": { "key": "c", "modifiers": ["ctrl"] } // Exit program
"next": { "key": "n" }, // Next page
"prev": { "key": "p" }, // Previous page
"scroll_up": { "key": "k" }, // Move viewport up
"scroll_down": { "key": "j" }, // Move viewport down
"scroll_left": { "key": "h" }, // Move viewport left
"scroll_right": { "key": "l" }, // Move viewport right
"zoom_in": { "key": "i" }, // Increase zoom level
"zoom_out": { "key": "o" }, // Decrease zoom level
"colorize": { "key": "z" }, // Toggle color inversion
"quit": { "key": "c", "modifiers": ["ctrl"] } // Exit program
}
```

Expand Down Expand Up @@ -66,4 +66,4 @@ Configure the information bar at screen bottom:
Configure the LRU cache

- `enabled`: `bool` - Allow/disallow caching
- `max_size`: ìnteger` - Maximum amount of pages cached
- `lru_size`: `ìnteger` - Amount of recently visited pages that get cached
26 changes: 15 additions & 11 deletions src/Cache.zig
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
const Self = @This();
const std = @import("std");
const Config = @import("config/Config.zig");
const vaxis = @import("vaxis");

pub const Key = struct { colorize: bool, page: u16 };
pub const EncodedImage = struct { base64: []const u8, width: u16, height: u16, cached: bool };
pub const CachedImage = struct { image: vaxis.Image };

const Node = struct {
key: Key,
value: EncodedImage,
value: CachedImage,
prev: ?*Node,
next: ?*Node,
};
Expand All @@ -17,17 +18,20 @@ map: std.AutoHashMap(Key, *Node),
head: ?*Node,
tail: ?*Node,
config: Config,
max_pages: usize,
lru_size: usize,
mutex: std.Thread.Mutex,

pub fn init(allocator: std.mem.Allocator, config: Config) Self {
pub fn init(
allocator: std.mem.Allocator,
config: Config,
) Self {
return .{
.allocator = allocator,
.map = std.AutoHashMap(Key, *Node).init(allocator),
.head = null,
.tail = null,
.config = config,
.max_pages = config.cache.max_pages,
.lru_size = config.cache.lru_size,
.mutex = .{},
};
}
Expand All @@ -38,7 +42,6 @@ pub fn deinit(self: *Self) void {
var current = self.head;
while (current) |node| {
const next = node.next;
self.allocator.free(node.value.base64);
self.allocator.destroy(node);
current = next;
}
Expand All @@ -51,8 +54,10 @@ pub fn clear(self: *Self) void {

var current = self.head;
while (current) |node| {
// TODO clear the image from the terminal everywhere
// Currently assuming the terminal takes care of it somewhat
//self.vx.freeImage(self.tty.anyWriter(), node.value.image.id);
const next = node.next;
self.allocator.free(node.value.base64);
self.allocator.destroy(node);
current = next;
}
Expand All @@ -62,15 +67,15 @@ pub fn clear(self: *Self) void {
self.tail = null;
}

pub fn get(self: *Self, key: Key) ?EncodedImage {
pub fn get(self: *Self, key: Key) ?CachedImage {
self.mutex.lock();
defer self.mutex.unlock();
const node = self.map.get(key) orelse return null;
self.moveToFront(node);
return node.value;
}

pub fn put(self: *Self, key: Key, image: EncodedImage) !bool {
pub fn put(self: *Self, key: Key, image: CachedImage) !bool {
self.mutex.lock();
defer self.mutex.unlock();
if (self.map.get(key)) |node| {
Expand All @@ -89,11 +94,10 @@ pub fn put(self: *Self, key: Key, image: EncodedImage) !bool {
try self.map.put(key, new_node);
self.addToFront(new_node);

if (self.map.count() > self.max_pages) {
if (self.map.count() > self.lru_size) {
const tail_node = self.tail orelse unreachable;
_ = self.map.remove(tail_node.key);
self.removeNode(tail_node);
self.allocator.free(tail_node.value.base64);
self.allocator.destroy(tail_node);
}

Expand Down
101 changes: 76 additions & 25 deletions src/Context.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const CommandState = @import("states/CommandState.zig");
const fzwatch = @import("fzwatch");
const Config = @import("config/Config.zig");
const PdfHandler = @import("./PdfHandler.zig");
const Cache = @import("./Cache.zig");

pub const panic = vaxis.panic_handler;

Expand Down Expand Up @@ -34,6 +35,8 @@ pub const Context = struct {
config: Config,
current_state: State,
reload_page: bool,
cache: Cache,
check_cache: bool,

pub fn init(allocator: std.mem.Allocator, args: [][]const u8) !Self {
const path = args[1];
Expand All @@ -53,11 +56,14 @@ pub const Context = struct {
if (watcher) |*w| try w.addFile(path);
}

const vx = try vaxis.init(allocator, .{});
const tty = try vaxis.Tty.init();

return .{
.allocator = allocator,
.should_quit = false,
.tty = try vaxis.Tty.init(),
.vx = try vaxis.init(allocator, .{}),
.tty = tty,
.vx = vx,
.pdf_handler = pdf_handler,
.page_info_text = &[_]u8{},
.current_page = null,
Expand All @@ -67,6 +73,8 @@ pub const Context = struct {
.config = config,
.current_state = undefined,
.reload_page = false,
.cache = Cache.init(allocator, config),
.check_cache = true,
};
}

Expand All @@ -80,6 +88,7 @@ pub const Context = struct {
if (self.thread) |thread| thread.join();
w.deinit();
}
self.cache.deinit();
if (self.page_info_text.len > 0) self.allocator.free(self.page_info_text);
self.pdf_handler.deinit();
self.vx.deinit(self.allocator, self.tty.anyWriter());
Expand Down Expand Up @@ -149,12 +158,9 @@ pub const Context = struct {
}

pub fn resetCurrentPage(self: *Self) void {
if (self.current_page) |img| {
self.vx.freeImage(self.tty.anyWriter(), img.id);
self.current_page = null;
self.pdf_handler.resetZoomAndScroll();
self.pdf_handler.check_cache = true;
}
self.current_page = null;
self.pdf_handler.resetZoomAndScroll();
self.check_cache = true;
}

pub fn handleKeyStroke(self: *Self, key: vaxis.Key) !void {
Expand All @@ -179,7 +185,7 @@ pub const Context = struct {
.winsize => |ws| {
try self.vx.resize(self.allocator, self.tty.anyWriter(), ws);
self.pdf_handler.resetZoomAndScroll();
self.pdf_handler.cache.clear();
self.cache.clear();
self.reload_page = true;
},
.file_changed => {
Expand All @@ -189,6 +195,57 @@ pub const Context = struct {
}
}

pub fn getCurrentPage(
self: *Self,
window_width: u32,
window_height: u32,
) !void {
// TODO make this interchangeable with other file formats (no pdf specific logic in context)
const shouldCheckCache = self.config.cache.enabled and
self.pdf_handler.zoom == 0 and
self.pdf_handler.x_offset == 0 and
self.pdf_handler.y_offset == 0 and
self.check_cache;

if (shouldCheckCache) {
if (self.cache.get(.{
.colorize = self.config.general.colorize,
.page = self.pdf_handler.current_page_number,
})) |cached| {
// Once we get the cached image we don't need to check the cache anymore because
// The only actions a user can take is zoom or scrolling, but we don't cache those
// Or go to the next page, at which point we set check_cache to true again
self.check_cache = false;
self.current_page = cached.image;
return;
}
}

const image = try self.pdf_handler.renderPage(
self.pdf_handler.current_page_number,
window_width,
window_height,
);
defer self.allocator.free(image.base64);

self.current_page = try self.vx.transmitPreEncodedImage(
self.tty.anyWriter(),
image.base64,
image.width,
image.height,
.rgb,
);

if (!self.config.cache.enabled) return;

if (self.current_page) |img| {
_ = try self.cache.put(.{
.colorize = self.config.general.colorize,
.page = self.pdf_handler.current_page_number,
}, .{ .image = img });
}
}

pub fn drawCurrentPage(self: *Self, win: vaxis.Window) !void {
self.pdf_handler.commitReload();
if (self.current_page == null or self.reload_page) {
Expand All @@ -201,16 +258,7 @@ pub const Context = struct {
y_pix -|= 2 * pix_per_row;
}

const encoded_image = try self.pdf_handler.getCurrentPage(x_pix, y_pix);
defer if (!encoded_image.cached) self.allocator.free(encoded_image.base64);

self.current_page = try self.vx.transmitPreEncodedImage(
self.tty.anyWriter(),
encoded_image.base64,
encoded_image.width,
encoded_image.height,
.rgb,
);
try self.getCurrentPage(x_pix, y_pix);

self.reload_page = false;
}
Expand Down Expand Up @@ -239,24 +287,27 @@ pub const Context = struct {
.width = win.width,
.height = 1,
});

status_bar.fill(vaxis.Cell{ .style = self.config.status_bar.style });

const mode_text = switch (self.current_state) {
.view => "VIS",
.command => "CMD",
};
_ = status_bar.print(
&.{.{ .text = self.pdf_handler.path, .style = self.config.status_bar.style }},
&.{
.{ .text = mode_text, .style = self.config.status_bar.style },
.{ .text = " ", .style = self.config.status_bar.style },
.{ .text = self.pdf_handler.path, .style = self.config.status_bar.style },
},
.{ .col_offset = 1 },
);

if (self.page_info_text.len > 0) {
self.allocator.free(self.page_info_text);
}

self.page_info_text = try std.fmt.allocPrint(
self.allocator,
"{d}:{d}",
.{ self.pdf_handler.current_page_number + 1, self.pdf_handler.total_pages },
);

_ = status_bar.print(
&.{.{ .text = self.page_info_text, .style = self.config.status_bar.style }},
.{ .col_offset = @intCast(win.width -| self.page_info_text.len -| 1) },
Expand Down
Loading

0 comments on commit e2ad107

Please sign in to comment.