1use abi_stable::external_types::crossbeam_channel::{RReceiver, RSender};
7use abi_stable::sabi_trait;
8use abi_stable::std_types::{ROption, RString};
9use abi_stable::traits::{IntoReprC, IntoReprRust};
10use engine::QueryEngine;
11use gravel_ffi::{ActionKind, ArcDynHit, clone_hit_arc};
12use gravel_ffi::{BoxDynFrontendContext, FrontendContext, FrontendMessage, FrontendMessageNe};
13use std::sync::atomic::{AtomicU32, Ordering};
14use std::{thread, time::Duration};
15
16use crate::clipboard::Clipboard;
17
18pub mod clipboard;
19pub mod config;
20pub mod engine;
21pub mod hotkeys;
22pub mod paths;
23pub mod performance;
24pub mod plugin;
25pub mod scoring;
26
27pub struct Core {
28 engine: QueryEngine,
29 frontend_sender: RSender<FrontendMessageNe>,
30 receiver: RReceiver<CoreMessage>,
31 clipboard: Clipboard,
32}
33
34pub enum CoreMessage {
35 Frontend(FrontendMessage),
36 Query(u32, String),
37 RunAction(ArcDynHit, ActionKind),
38 ClearCaches,
39 SetClipboardText(String),
40}
41
42impl Core {
43 pub fn new(
44 engine: QueryEngine,
45 frontend_sender: RSender<FrontendMessageNe>,
46 receiver: RReceiver<CoreMessage>,
47 ) -> Self {
48 Self {
49 engine,
50 frontend_sender,
51 receiver,
52 clipboard: Clipboard::new(),
53 }
54 }
55
56 pub fn spawn(self) {
57 thread::spawn(move || self.run());
58 }
59
60 pub fn run(&self) {
61 loop {
62 self.receive_message();
63 }
64 }
65
66 fn receive_message(&self) -> Option<()> {
67 const ONE_MILLI: Duration = Duration::from_millis(1);
68
69 match self.receiver.recv_timeout(ONE_MILLI).ok()? {
70 CoreMessage::Frontend(msg) => self.send_frontend(msg),
71 CoreMessage::Query(token, query) => self.query(token, &query),
72 CoreMessage::RunAction(hit, kind) => self.run_action(&hit, kind),
73 CoreMessage::ClearCaches => self.clear_caches(),
74 CoreMessage::SetClipboardText(content) => self.clipboard.set_text(&content),
75 }
76
77 None
78 }
79
80 fn run_action(&self, hit: &ArcDynHit, kind: ActionKind) {
81 timed!("hit action took", {
82 self.engine.run_hit_action(hit, kind);
83 });
84 }
85
86 fn query(&self, token: u32, query: &str) {
87 let result = timed!(("full query {token} took"), { self.engine.query(query.into_c()) });
88
89 self.send_frontend(FrontendMessage::QueryResult(token, result));
90 }
91
92 fn clear_caches(&self) {
93 log::debug!("clearing caches");
94
95 self.send_frontend(FrontendMessage::ClearCaches);
96 self.engine.clear_caches();
97 }
98
99 fn send_frontend(&self, message: FrontendMessage) {
100 self.frontend_sender
101 .send(FrontendMessageNe::new(message))
102 .inspect_err(|e| log::error!("unable to send frontend message: {e}"))
103 .ok();
104 }
105}
106
107pub struct FrontendCtx {
108 receiver: RReceiver<FrontendMessageNe>,
109 sender: RSender<CoreMessage>,
110 token_counter: AtomicU32,
111}
112
113impl FrontendCtx {
114 pub fn new(receiver: RReceiver<FrontendMessageNe>, sender: RSender<CoreMessage>) -> Self {
115 Self {
116 receiver,
117 sender,
118 token_counter: Default::default(),
119 }
120 }
121
122 fn send(&self, message: CoreMessage) {
123 self.sender
124 .send(message)
125 .inspect_err(|e| log::error!("unable to send core message: {e}"))
126 .ok();
127 }
128
129 fn new_token(&self) -> u32 {
130 self.token_counter.fetch_add(1, Ordering::Relaxed)
131 }
132}
133
134impl FrontendContext for FrontendCtx {
135 fn recv_raw(&self) -> ROption<FrontendMessageNe> {
136 self.receiver.try_recv().ok().into_c()
137 }
138
139 fn query(&self, query: RString) -> u32 {
140 let token = self.new_token();
141 self.send(CoreMessage::Query(token, query.into_rust()));
142 token
143 }
144
145 fn run_hit_action(&self, hit: &ArcDynHit, kind: ActionKind) {
146 self.send(CoreMessage::RunAction(clone_hit_arc(hit), kind));
147 }
148}
149
150impl From<FrontendCtx> for BoxDynFrontendContext {
151 fn from(value: FrontendCtx) -> Self {
152 Self::from_value(value, sabi_trait::TD_Opaque)
153 }
154}