terminal: search thread refresh timer to reconcile state
parent
acab8c90a2
commit
f0af63db15
|
|
@ -28,6 +28,12 @@ const ViewportSearch = @import("viewport.zig").ViewportSearch;
|
||||||
|
|
||||||
const log = std.log.scoped(.search_thread);
|
const log = std.log.scoped(.search_thread);
|
||||||
|
|
||||||
|
/// The interval at which we refresh the terminal state to check if
|
||||||
|
/// there are any changes that require us to re-search. This should be
|
||||||
|
/// balanced to be fast enough to be responsive but not so fast that
|
||||||
|
/// we hold the terminal lock too often.
|
||||||
|
const REFRESH_INTERVAL = 24; // 40 FPS
|
||||||
|
|
||||||
/// Allocator used for some state
|
/// Allocator used for some state
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
|
@ -47,6 +53,13 @@ wakeup_c: xev.Completion = .{},
|
||||||
stop: xev.Async,
|
stop: xev.Async,
|
||||||
stop_c: xev.Completion = .{},
|
stop_c: xev.Completion = .{},
|
||||||
|
|
||||||
|
/// The timer used for refreshing the terminal state to determine if
|
||||||
|
/// we have a stale active area, viewport, screen change, etc. This is
|
||||||
|
/// CPU intensive so we stop doing this under certain conditions.
|
||||||
|
refresh: xev.Timer,
|
||||||
|
refresh_c: xev.Completion = .{},
|
||||||
|
refresh_active: bool = false,
|
||||||
|
|
||||||
/// Search state. Starts as null and is populated when a search is
|
/// Search state. Starts as null and is populated when a search is
|
||||||
/// started (a needle is given).
|
/// started (a needle is given).
|
||||||
search: ?Search = null,
|
search: ?Search = null,
|
||||||
|
|
@ -74,12 +87,17 @@ pub fn init(alloc: Allocator, opts: Options) !Thread {
|
||||||
var stop_h = try xev.Async.init();
|
var stop_h = try xev.Async.init();
|
||||||
errdefer stop_h.deinit();
|
errdefer stop_h.deinit();
|
||||||
|
|
||||||
|
// Refresh timer, see comments.
|
||||||
|
var refresh_h = try xev.Timer.init();
|
||||||
|
errdefer refresh_h.deinit();
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.mailbox = mailbox,
|
.mailbox = mailbox,
|
||||||
.loop = loop,
|
.loop = loop,
|
||||||
.wakeup = wakeup_h,
|
.wakeup = wakeup_h,
|
||||||
.stop = stop_h,
|
.stop = stop_h,
|
||||||
|
.refresh = refresh_h,
|
||||||
.opts = opts,
|
.opts = opts,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -87,6 +105,7 @@ pub fn init(alloc: Allocator, opts: Options) !Thread {
|
||||||
/// Clean up the thread. This is only safe to call once the thread
|
/// Clean up the thread. This is only safe to call once the thread
|
||||||
/// completes executing; the caller must join prior to this.
|
/// completes executing; the caller must join prior to this.
|
||||||
pub fn deinit(self: *Thread) void {
|
pub fn deinit(self: *Thread) void {
|
||||||
|
self.refresh.deinit();
|
||||||
self.wakeup.deinit();
|
self.wakeup.deinit();
|
||||||
self.stop.deinit();
|
self.stop.deinit();
|
||||||
self.loop.deinit();
|
self.loop.deinit();
|
||||||
|
|
@ -130,6 +149,9 @@ fn threadMain_(self: *Thread) !void {
|
||||||
// Send an initial wakeup so we drain our mailbox immediately.
|
// Send an initial wakeup so we drain our mailbox immediately.
|
||||||
try self.wakeup.notify();
|
try self.wakeup.notify();
|
||||||
|
|
||||||
|
// Start the refresh timer
|
||||||
|
self.startRefreshTimer();
|
||||||
|
|
||||||
// Run
|
// Run
|
||||||
log.debug("starting search thread", .{});
|
log.debug("starting search thread", .{});
|
||||||
defer log.debug("starting search thread shutdown", .{});
|
defer log.debug("starting search thread shutdown", .{});
|
||||||
|
|
@ -192,6 +214,13 @@ fn threadMain_(self: *Thread) !void {
|
||||||
// Ticking can complete our search.
|
// Ticking can complete our search.
|
||||||
if (s.isComplete()) {
|
if (s.isComplete()) {
|
||||||
if (self.opts.event_cb) |cb| {
|
if (self.opts.event_cb) |cb| {
|
||||||
|
// Send all pending notifications before we send complete.
|
||||||
|
s.notify(
|
||||||
|
self.alloc,
|
||||||
|
cb,
|
||||||
|
self.opts.event_userdata,
|
||||||
|
);
|
||||||
|
|
||||||
cb(
|
cb(
|
||||||
.complete,
|
.complete,
|
||||||
self.opts.event_userdata,
|
self.opts.event_userdata,
|
||||||
|
|
@ -240,6 +269,30 @@ fn changeNeedle(self: *Thread, needle: []const u8) !void {
|
||||||
self.search.?.feed(self.alloc, self.opts.terminal);
|
self.search.?.feed(self.alloc, self.opts.terminal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn startRefreshTimer(self: *Thread) void {
|
||||||
|
// Set our active state so it knows we're running. We set this before
|
||||||
|
// even checking the active state in case we have a pending shutdown.
|
||||||
|
self.refresh_active = true;
|
||||||
|
|
||||||
|
// If our timer is already active, then we don't have to do anything.
|
||||||
|
if (self.refresh_c.state() == .active) return;
|
||||||
|
|
||||||
|
// Start the timer which loops
|
||||||
|
self.refresh.run(
|
||||||
|
&self.loop,
|
||||||
|
&self.refresh_c,
|
||||||
|
REFRESH_INTERVAL,
|
||||||
|
Thread,
|
||||||
|
self,
|
||||||
|
refreshCallback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stopRefreshTimer(self: *Thread) void {
|
||||||
|
// This will stop the refresh on the next iteration.
|
||||||
|
self.refresh_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
fn wakeupCallback(
|
fn wakeupCallback(
|
||||||
self_: ?*Thread,
|
self_: ?*Thread,
|
||||||
_: *xev.Loop,
|
_: *xev.Loop,
|
||||||
|
|
@ -272,6 +325,39 @@ fn stopCallback(
|
||||||
return .disarm;
|
return .disarm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn refreshCallback(
|
||||||
|
self_: ?*Thread,
|
||||||
|
_: *xev.Loop,
|
||||||
|
_: *xev.Completion,
|
||||||
|
r: xev.Timer.RunError!void,
|
||||||
|
) xev.CallbackAction {
|
||||||
|
_ = r catch unreachable;
|
||||||
|
const self: *Thread = self_ orelse {
|
||||||
|
// This shouldn't happen so we log it.
|
||||||
|
log.warn("refresh callback fired without data set", .{});
|
||||||
|
return .disarm;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run our feed if we have a search active.
|
||||||
|
if (self.search) |*s| {
|
||||||
|
self.opts.mutex.lock();
|
||||||
|
defer self.opts.mutex.unlock();
|
||||||
|
s.feed(self.alloc, self.opts.terminal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only continue if we're still active
|
||||||
|
if (self.refresh_active) self.refresh.run(
|
||||||
|
&self.loop,
|
||||||
|
&self.refresh_c,
|
||||||
|
REFRESH_INTERVAL,
|
||||||
|
Thread,
|
||||||
|
self,
|
||||||
|
refreshCallback,
|
||||||
|
);
|
||||||
|
|
||||||
|
return .disarm;
|
||||||
|
}
|
||||||
|
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
/// Mutex that must be held while reading/writing the terminal.
|
/// Mutex that must be held while reading/writing the terminal.
|
||||||
mutex: *Mutex,
|
mutex: *Mutex,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue