1use crate::settings::Settings;
2use bevy::ecs::message::MessageId;
3use bevy::prelude::*;
4use ib_matcher::matcher::{IbMatcher, PinyinMatchConfig, RomajiMatchConfig};
5use ib_matcher::pinyin::PinyinNotation;
6use rayon::prelude::*;
7use std::sync::Arc;
8
9pub struct SearchPlugin;
10impl Plugin for SearchPlugin {
11 fn build(&self, app: &mut App) {
12 app.add_message::<SearchCommand>()
13 .add_message::<SearchResponse>()
14 .add_systems(Update, search.run_if(on_message::<SearchCommand>));
15 }
16}
17
18#[derive(Message)]
19pub enum SearchCommand {
20 Table {
21 data: Arc<Vec<String>>,
22 query: String,
23 },
24}
25
26#[derive(Message)]
27pub enum SearchResponse {
28 Table(MessageId<SearchCommand>, Vec<usize>),
29}
30
31pub fn search(
32 mut msg_reader: MessageReader<SearchCommand>,
33 mut msg_writer: MessageWriter<SearchResponse>,
34 settings: Res<Settings>,
35) {
36 let now = instant::Instant::now();
37 for (msg, id) in msg_reader.read_with_id() {
38 match msg {
39 SearchCommand::Table { data, query } => {
40 let result = search_table(data, &build_matcher(query, &settings));
41 msg_writer.write(SearchResponse::Table(id, result));
42 }
43 }
44 }
45 debug!("Search completed in {:?}", now.elapsed());
46}
47
48fn build_matcher(query: &str, settings: &Settings) -> IbMatcher<'static> {
49 let now = instant::Instant::now();
50 let matcher = IbMatcher::builder(query)
51 .pinyin(PinyinMatchConfig::notations(
52 PinyinNotation::Ascii | PinyinNotation::DiletterMicrosoft,
53 ))
54 .maybe_romaji(
55 settings
56 .enable_romaji_search
57 .then(RomajiMatchConfig::default),
58 )
59 .build();
60 debug!("Matcher built in {:?}", now.elapsed());
61 matcher
62}
63
64fn search_table(data: &Arc<Vec<String>>, searcher: &IbMatcher<'static>) -> Vec<usize> {
65 data.par_iter()
66 .enumerate()
67 .filter_map(|(index, row)| {
68 if searcher.is_match(row.as_str()) {
69 None
70 } else {
71 Some(index)
72 }
73 })
74 .collect()
75}