gravel_core/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! gravel's core library.
//!
//! Contains functionality used internally by gravel, and is generally not
//! required for writing plugins.

use abi_stable::external_types::crossbeam_channel::{RReceiver, RSender};
use abi_stable::sabi_trait;
use abi_stable::std_types::{ROption, RString};
use abi_stable::traits::{IntoReprC, IntoReprRust};
use engine::QueryEngine;
use gravel_ffi::{clone_hit_arc, ActionKind, ArcDynHit};
use gravel_ffi::{BoxDynFrontendContext, FrontendContext, FrontendMessage, FrontendMessageNe};
use std::sync::atomic::{AtomicU32, Ordering};
use std::{thread, time::Duration};

pub mod config;
pub mod engine;
pub mod hotkeys;
pub mod paths;
pub mod performance;
pub mod plugin;
pub mod scoring;

pub struct Core {
	engine: QueryEngine,
	frontend_sender: RSender<FrontendMessageNe>,
	receiver: RReceiver<CoreMessage>,
}

pub enum CoreMessage {
	Frontend(FrontendMessage),
	Query(u32, String),
	RunAction(ArcDynHit, ActionKind),
	ClearCaches,
}

impl Core {
	pub fn new(
		engine: QueryEngine,
		frontend_sender: RSender<FrontendMessageNe>,
		receiver: RReceiver<CoreMessage>,
	) -> Self {
		Self {
			engine,
			frontend_sender,
			receiver,
		}
	}

	pub fn spawn(self) {
		thread::spawn(move || self.run());
	}

	pub fn run(&self) {
		loop {
			self.receive_message();
		}
	}

	fn receive_message(&self) -> Option<()> {
		const ONE_MILLI: Duration = Duration::from_millis(1);

		match self.receiver.recv_timeout(ONE_MILLI).ok()? {
			CoreMessage::Frontend(msg) => self.send_frontend(msg),
			CoreMessage::Query(token, query) => self.query(token, &query),
			CoreMessage::RunAction(hit, kind) => self.run_action(&hit, kind),
			CoreMessage::ClearCaches => self.clear_caches(),
		}

		None
	}

	fn run_action(&self, hit: &ArcDynHit, kind: ActionKind) {
		timed!("hit action took", {
			self.engine.run_hit_action(hit, kind);
		});
	}

	fn query(&self, token: u32, query: &str) {
		let result = timed!(("full query {token} took"), { self.engine.query(query.into_c()) });

		self.send_frontend(FrontendMessage::QueryResult(token, result));
	}

	fn clear_caches(&self) {
		log::debug!("clearing caches");

		self.send_frontend(FrontendMessage::ClearCaches);
		self.engine.clear_caches();
	}

	fn send_frontend(&self, message: FrontendMessage) {
		self.frontend_sender
			.send(FrontendMessageNe::new(message))
			.inspect_err(|e| log::error!("unable to send frontend message: {e}"))
			.ok();
	}
}

pub struct FrontendCtx {
	receiver: RReceiver<FrontendMessageNe>,
	sender: RSender<CoreMessage>,
	token_counter: AtomicU32,
}

impl FrontendCtx {
	pub fn new(receiver: RReceiver<FrontendMessageNe>, sender: RSender<CoreMessage>) -> Self {
		Self {
			receiver,
			sender,
			token_counter: Default::default(),
		}
	}

	fn send(&self, message: CoreMessage) {
		self.sender
			.send(message)
			.inspect_err(|e| log::error!("unable to send core message: {e}"))
			.ok();
	}

	fn new_token(&self) -> u32 {
		self.token_counter.fetch_add(1, Ordering::Relaxed)
	}
}

impl FrontendContext for FrontendCtx {
	fn recv_raw(&self) -> ROption<FrontendMessageNe> {
		self.receiver.try_recv().ok().into_c()
	}

	fn query(&self, query: RString) -> u32 {
		let token = self.new_token();
		self.send(CoreMessage::Query(token, query.into_rust()));
		token
	}

	fn run_hit_action(&self, hit: &ArcDynHit, kind: ActionKind) {
		self.send(CoreMessage::RunAction(clone_hit_arc(hit), kind));
	}
}

impl From<FrontendCtx> for BoxDynFrontendContext {
	fn from(value: FrontendCtx) -> Self {
		Self::from_value(value, sabi_trait::TD_Opaque)
	}
}