Skip to content

Commit

Permalink
[rank] added rank state
Browse files Browse the repository at this point in the history
  • Loading branch information
aslpavel committed Nov 7, 2024
1 parent a198013 commit 9e28981
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 102 deletions.
237 changes: 141 additions & 96 deletions sweep-lib/src/rank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,140 +130,185 @@ enum RankerCmd {
Sync(Arc<AtomicBool>),
}

fn ranker_worker<N>(receiver: Receiver<RankerCmd>, result: Arc<Mutex<Arc<RankedItems>>>, notify: N)
where
N: Fn(Arc<RankedItems>) -> bool,
{
let mut haystack_gen = 0usize;
let mut haystack: StringViewArray = byte_view_concat([]);
let mut haystack_appends: Vec<StringViewArray> = Vec::new();

let mut needle = String::new();

let mut keep_order = false;

let mut scorer_builder = fuzzy_scorer();
let mut scorer = scorer_builder("");
let mut score = scorer.score(&haystack, Ok(0), !keep_order);
#[derive(Clone, Copy)]
enum RankAction {
DoNothing, // ignore
Notify, // only notify
Offset(usize), // rank items starting from offset
CurrentMatch, // rank only current match
All, // rank everything
}

let mut rank_gen = 0usize;
let mut synced: Vec<Arc<AtomicBool>> = Vec::new();
struct RankerState {
haystack_gen: usize,
haystack: StringViewArray,
haystack_appends: Vec<StringViewArray>,
needle: String,
keep_order: bool,
scorer_builder: ScorerBuilder,
scorer: Arc<dyn Scorer>,
score: ScoreArray,
rank_gen: usize,
synced: Vec<Arc<AtomicBool>>,
action: RankAction,
}

loop {
#[derive(Clone, Copy)]
enum RankAction {
DoNothing, // ignore
Notify, // only notify
Offset(usize), // rank items starting from offset
CurrentMatch, // rank only current match
All, // rank everything
}
impl RankerState {
// process ranker cmd
fn process(&mut self, cmd: RankerCmd) {
use RankAction::*;
let mut action = DoNothing;

// block on first event and process all pending requests in one go
let cmd = match receiver.recv() {
Ok(cmd) => cmd,
Err(_) => return,
};
for cmd in iter::once(cmd).chain(receiver.try_iter()) {
use RankerCmd::*;
match cmd {
Needle(needle_new) => {
action = match action {
DoNothing if needle_new == needle => continue,
DoNothing | CurrentMatch if needle_new.starts_with(&needle) => CurrentMatch,
_ => All,
};
needle = needle_new;
scorer = scorer_builder(&needle);
}
Scorer(scorer_builder_new) => {
action = All;
scorer_builder = scorer_builder_new;
scorer = scorer_builder(&needle);
}
HaystackAppend(haystack_append) => {
action = match action {
DoNothing => Offset(haystack.len()),
Offset(offset) => Offset(offset),
_ => All,
};
haystack_appends.push(haystack_append);
}
HaystackClear => {
action = All;
haystack_gen = haystack_gen.wrapping_add(1);
haystack_appends.clear();
haystack = byte_view_concat([]);
}
KeepOrder(toggle) => {
action = All;
match toggle {
None => keep_order = !keep_order,
Some(value) => keep_order = value,
use RankerCmd::*;

match cmd {
Needle(needle_new) => {
self.action = match self.action {
DoNothing if needle_new == self.needle => return,
DoNothing | CurrentMatch if needle_new.starts_with(&self.needle) => {
CurrentMatch
}
}
Sync(sync) => {
action = match action {
DoNothing => Notify,
_ => action,
};
synced.push(sync);
_ => All,
};
self.needle = needle_new;
self.scorer = (self.scorer_builder)(&self.needle);
}
Scorer(scorer_builder_new) => {
self.action = All;
self.scorer_builder = scorer_builder_new;
self.scorer = (self.scorer_builder)(&self.needle);
}
HaystackAppend(haystack_append) => {
self.action = match self.action {
DoNothing => Offset(self.haystack.len()),
Offset(offset) => Offset(offset),
_ => All,
};
self.haystack_appends.push(haystack_append);
}
HaystackClear => {
self.action = All;
self.haystack_gen = self.haystack_gen.wrapping_add(1);
self.haystack_appends.clear();
self.haystack = byte_view_concat([]);
}
KeepOrder(toggle) => {
self.action = All;
match toggle {
None => self.keep_order = !self.keep_order,
Some(value) => self.keep_order = value,
}
}
Sync(sync) => {
self.action = match self.action {
DoNothing => Notify,
_ => self.action,
};
self.synced.push(sync);
}
}
if !haystack_appends.is_empty() {
haystack = byte_view_concat(iter::once(&haystack).chain(&haystack_appends));
haystack_appends.clear();
}

// do actual ranking
fn rank(&mut self, result: Arc<Mutex<Arc<RankedItems>>>) {
use RankAction::*;

// collect haystack
if !self.haystack_appends.is_empty() {
self.haystack =
byte_view_concat(iter::once(&self.haystack).chain(&self.haystack_appends));
self.haystack_appends.clear();
}

// rank
let rank_instant = Instant::now();
score = match action {
self.score = match self.action {
DoNothing => {
continue;
return;
}
Notify => score,
Notify => self.score.clone(),
Offset(offset) => {
// score new data
score.merge(
scorer.score_par(
&haystack.slice(offset, haystack.len() - offset),
self.score.merge(
self.scorer.score_par(
&self.haystack.slice(offset, self.haystack.len() - offset),
Ok(offset as u32),
false,
SCORE_CHUNK_SIZE,
),
!keep_order,
!self.keep_order,
)
}
CurrentMatch => {
// score current matches
score.score_par(&scorer, !keep_order, SCORE_CHUNK_SIZE)
self.score
.score_par(&self.scorer, !self.keep_order, SCORE_CHUNK_SIZE)
}
All => {
// score all haystack elements
scorer.score_par(&haystack, Ok(0), !keep_order, SCORE_CHUNK_SIZE)
self.scorer
.score_par(&self.haystack, Ok(0), !self.keep_order, SCORE_CHUNK_SIZE)
}
};
let rank_elapsed = rank_instant.elapsed();

// update result
// TODO: ArcSwap?
rank_gen = rank_gen.wrapping_add(1);
self.rank_gen = self.rank_gen.wrapping_add(1);
result.with_mut(|result| {
*result = Arc::new(RankedItems {
haystack_gen,
score: score.clone(),
scorer: scorer.clone(),
score: self.score.clone(),
scorer: self.scorer.clone(),
duration: rank_elapsed,
rank_gen,
haystack_gen: self.haystack_gen,
rank_gen: self.rank_gen,
});
});

for sync in synced.drain(..) {
for sync in self.synced.drain(..) {
sync.store(true, Ordering::Release);
}
}
}

impl Default for RankerState {
fn default() -> Self {
let haystack = byte_view_concat([]);
let keep_order = false;
let scorer_builder = fuzzy_scorer();
let scorer = scorer_builder("");
let score = scorer.score(&haystack, Ok(0), !keep_order);
Self {
haystack_gen: 0,
haystack: byte_view_concat([]),
haystack_appends: Default::default(),
needle: String::new(),
keep_order,
scorer_builder,
scorer,
score,
rank_gen: 0,
synced: Default::default(),
action: RankAction::DoNothing,
}
}
}

fn ranker_worker<N>(receiver: Receiver<RankerCmd>, result: Arc<Mutex<Arc<RankedItems>>>, notify: N)
where
N: Fn(Arc<RankedItems>) -> bool,
{
let mut state = RankerState::default();
loop {
// process all pending commands
let cmd = match receiver.recv() {
Ok(cmd) => cmd,
Err(_) => return,
};
for cmd in iter::once(cmd).chain(receiver.try_iter()) {
state.process(cmd);
}

// rank
state.rank(result.clone());

// notify
if !notify(result.with(|r| r.clone())) {
return;
}
Expand Down
18 changes: 14 additions & 4 deletions sweep-lib/src/sweep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1283,7 +1283,9 @@ where
}
}
SweepAction::ScorerNext => {
scorer_by_name(&mut self.scorers, None);
if let Some(scorer) = scorer_by_name(&mut self.scorers, None) {
self.ranker.scorer_set(scorer);
}
}
SweepAction::PreviewToggle => self.theme_set(
self.theme
Expand Down Expand Up @@ -1424,8 +1426,13 @@ impl<H: Haystack> Window for SweepWindow<H> {
self.list.cursor_set(position);
}
ScorerByName(name, resolve) => {
let _ =
resolve.send(scorer_by_name(&mut self.scorers, name.as_deref()).is_some());
let _ = match scorer_by_name(&mut self.scorers, name.as_deref()) {
None => resolve.send(false),
Some(scorer) => {
self.ranker.scorer_set(scorer);
resolve.send(true)
}
};
}
PreviewSet(value) => {
let show_preview = match value {
Expand Down Expand Up @@ -2414,10 +2421,13 @@ fn scorer_by_name(
scorers: &mut VecDeque<ScorerBuilder>,
name: Option<&str>,
) -> Option<ScorerBuilder> {
if scorers.is_empty() {
return None;
}
match name {
None => {
scorers.rotate_left(1);
Some(scorers[0].clone())
scorers.iter().next().cloned()
}
Some(name) => scorers
.iter()
Expand Down
9 changes: 7 additions & 2 deletions sweep-py/sweep/apps/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
SweepBind,
SweepEvent,
SweepSelect,
SweepWindow,
SweepSize,
Text,
)
from . import sweep_default_cmd
Expand Down Expand Up @@ -101,6 +103,7 @@
size=(1, 3),
path="M81.51 20.43L81.51 20.43Q84.19 20.43 86.45 21.88Q88.72 23.32 89.75 25.79Q90.78 28.26 90.26 30.84Q89.75 33.41 87.79 35.37Q85.83 37.32 83.26 37.84Q80.68 38.35 78.21 37.32Q75.74 36.29 74.30 34.03Q72.86 31.76 72.86 29.09L72.86 29.09Q72.86 25.58 75.43 23.01Q78.01 20.43 81.51 20.43ZM64.21 24.76L64.21 24.76Q66.88 24.76 68.84 26.72Q70.80 28.67 70.80 31.35Q70.80 34.03 68.84 35.99Q66.88 37.94 64.21 37.94Q61.53 37.94 59.57 35.99Q57.61 34.03 57.61 31.35Q57.61 28.67 59.57 26.72Q61.53 24.76 64.21 24.76ZM51.23 31.35L51.23 31.35Q53.08 31.35 54.32 32.59Q55.55 33.82 55.55 35.68Q55.55 37.53 54.32 38.87Q53.08 40.21 51.23 40.21Q49.37 40.21 48.14 38.87Q46.90 37.53 46.90 35.68Q46.90 33.82 48.14 32.59Q49.37 31.35 51.23 31.35ZM42.17 37.94L42.17 37.94Q44.02 37.94 45.36 39.18Q46.70 40.41 46.70 42.27Q46.70 44.12 45.36 45.46Q44.02 46.80 42.17 46.80Q40.31 46.80 39.08 45.46Q37.84 44.12 37.84 42.27Q37.84 40.41 39.08 39.18Q40.31 37.94 42.17 37.94ZM75.12 64.31L75.12 64.31Q80.07 64.31 83.26 60.70Q86.45 57.10 86.04 52.16L86.04 52.16Q85.42 47.83 82.13 45.05Q78.83 42.27 74.51 42.27L74.51 42.27L63.59 42.27Q54.73 42.27 47.62 47.73Q40.52 53.19 38.25 61.63L38.25 61.63Q37.22 64.93 38.66 67.81L38.66 67.81Q41.55 73.99 41.44 80.68Q41.34 87.38 38.66 93.15L38.66 93.15Q36.81 97.06 38.87 100.77L38.87 100.77Q41.75 105.09 46.39 107.05Q51.02 109.01 55.97 107.77L55.97 107.77Q59.67 106.95 62.56 104.58Q65.44 102.21 66.88 98.71Q68.33 95.21 67.91 91.50Q67.50 87.79 65.44 84.60Q63.38 81.41 63.59 77.49L63.59 77.49L63.59 77.49Q63.38 74.20 64.62 70.90L64.62 70.90Q67.30 64.31 75.12 64.31Z",
)
MOUSE_EVENT_TAG = "my-custom-mouse-event"


async def yes_or_no(sweep: Sweep[Any]) -> bool | None:
Expand Down Expand Up @@ -161,7 +164,7 @@ async def field_resolver(ref: int) -> Field | None:
.border_radius(10)
.padding(10)
.border_width(3)
).tag("my-custom-mouse-event")
).tag(MOUSE_EVENT_TAG)
view = (
Container(
Flex.row().push(glyph, align=Align.CENTER).justify(Justify.CENTER)
Expand Down Expand Up @@ -267,6 +270,8 @@ def candidate_clicked(clicked: int) -> Candidate:

async for event in sweep:
match event:
case SweepSize() | SweepWindow():
continue
case SweepSelect(items) if len(items) == 1:
item = items[0]
if isinstance(item, Candidate) and item.extra:
Expand All @@ -285,7 +290,7 @@ def candidate_clicked(clicked: int) -> Candidate:
await sweep.item_update(0, candidate_clicked(0))
continue
case _:
continue
pass
result = event
break

Expand Down

0 comments on commit 9e28981

Please sign in to comment.