pass sort options into test scheduler

- split new card fetch order and subsequent sort order; use latter
when building queues
- default to spacing siblings when burying is off, with options to
show each sibling in turn, and shuffle the fetched cards
This commit is contained in:
Damien Elmes 2021-05-12 16:33:18 +10:00
parent b64f7a9456
commit a1bd6b481d
12 changed files with 176 additions and 95 deletions

View file

@ -309,15 +309,21 @@ service CardsService {
message DeckConfig { message DeckConfig {
message Config { message Config {
enum NewCardOrder { enum NewCardFetchOrder {
NEW_CARD_ORDER_DUE = 0; NEW_CARD_FETCH_ORDER_DUE = 0;
NEW_CARD_ORDER_RANDOM = 1; NEW_CARD_FETCH_ORDER_RANDOM = 1;
}
enum NewCardSortOrder {
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_DUE = 0;
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_RANDOM = 1;
NEW_CARD_SORT_ORDER_DUE = 2;
NEW_CARD_SORT_ORDER_RANDOM = 3;
} }
enum ReviewCardOrder { enum ReviewCardOrder {
REVIEW_CARD_ORDER_SHUFFLED_BY_DAY = 0; REVIEW_CARD_ORDER_DAY_THEN_RANDOM = 0;
REVIEW_CARD_ORDER_SHUFFLED = 1; REVIEW_CARD_ORDER_INTERVALS_ASCENDING = 1;
REVIEW_CARD_ORDER_INTERVALS_ASCENDING = 2; REVIEW_CARD_ORDER_INTERVALS_DESCENDING = 2;
REVIEW_CARD_ORDER_INTERVALS_DESCENDING = 3; // REVIEW_CARD_ORDER_RELATIVE_OVERDUE = 3;
} }
enum ReviewMix { enum ReviewMix {
REVIEW_MIX_MIX_WITH_REVIEWS = 0; REVIEW_MIX_MIX_WITH_REVIEWS = 0;
@ -350,8 +356,9 @@ message DeckConfig {
uint32 graduating_interval_good = 18; uint32 graduating_interval_good = 18;
uint32 graduating_interval_easy = 19; uint32 graduating_interval_easy = 19;
NewCardOrder new_card_order = 20; NewCardFetchOrder new_card_fetch_order = 20;
ReviewCardOrder review_order = 32; NewCardSortOrder new_card_sort_order = 32;
ReviewCardOrder review_order = 33;
ReviewMix new_mix = 30; ReviewMix new_mix = 30;
ReviewMix interday_learning_mix = 31; ReviewMix interday_learning_mix = 31;

View file

@ -10,7 +10,7 @@ use crate::{
backend_proto::{self as pb}, backend_proto::{self as pb},
prelude::*, prelude::*,
scheduler::{ scheduler::{
new::NewCardSortOrder, new::NewCardDueOrder,
states::{CardState, NextCardStates}, states::{CardState, NextCardStates},
}, },
stats::studied_today, stats::studied_today,
@ -132,9 +132,9 @@ impl SchedulingService for Backend {
input.shift_existing, input.shift_existing,
); );
let order = if random { let order = if random {
NewCardSortOrder::Random NewCardDueOrder::Random
} else { } else {
NewCardSortOrder::Preserve NewCardDueOrder::Preserve
}; };
self.with_col(|col| { self.with_col(|col| {
col.sort_cards(&cids, start, step, order, shift) col.sort_cards(&cids, start, step, order, shift)

View file

@ -9,7 +9,7 @@ pub use schema11::{DeckConfSchema11, NewCardOrderSchema11};
pub use update::UpdateDeckConfigsIn; pub use update::UpdateDeckConfigsIn;
pub use crate::backend_proto::deck_config::{ pub use crate::backend_proto::deck_config::{
config::{LeechAction, NewCardOrder, ReviewCardOrder, ReviewMix}, config::{LeechAction, NewCardFetchOrder, NewCardSortOrder, ReviewCardOrder, ReviewMix},
Config as DeckConfigInner, Config as DeckConfigInner,
}; };
@ -58,8 +58,9 @@ impl Default for DeckConfig {
minimum_lapse_interval: 1, minimum_lapse_interval: 1,
graduating_interval_good: 1, graduating_interval_good: 1,
graduating_interval_easy: 4, graduating_interval_easy: 4,
new_card_order: NewCardOrder::Due as i32, new_card_fetch_order: NewCardFetchOrder::Due as i32,
review_order: ReviewCardOrder::ShuffledByDay as i32, new_card_sort_order: NewCardSortOrder::TemplateThenDue as i32,
review_order: ReviewCardOrder::DayThenRandom as i32,
new_mix: ReviewMix::MixWithReviews as i32, new_mix: ReviewMix::MixWithReviews as i32,
interday_learning_mix: ReviewMix::MixWithReviews as i32, interday_learning_mix: ReviewMix::MixWithReviews as i32,
leech_action: LeechAction::TagOnly as i32, leech_action: LeechAction::TagOnly as i32,

View file

@ -10,7 +10,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
use serde_tuple::Serialize_tuple; use serde_tuple::Serialize_tuple;
use super::{ use super::{
DeckConfig, DeckConfigId, DeckConfigInner, NewCardOrder, INITIAL_EASE_FACTOR_THOUSANDS, DeckConfig, DeckConfigId, DeckConfigInner, NewCardFetchOrder, INITIAL_EASE_FACTOR_THOUSANDS,
}; };
use crate::{serde::default_on_invalid, timestamp::TimestampSecs, types::Usn}; use crate::{serde::default_on_invalid, timestamp::TimestampSecs, types::Usn};
@ -46,6 +46,8 @@ pub struct DeckConfSchema11 {
interday_learning_mix: i32, interday_learning_mix: i32,
#[serde(default)] #[serde(default)]
review_order: i32, review_order: i32,
#[serde(default)]
new_sort_order: i32,
#[serde(flatten)] #[serde(flatten)]
other: HashMap<String, Value>, other: HashMap<String, Value>,
@ -210,6 +212,7 @@ impl Default for DeckConfSchema11 {
new_per_day_minimum: 0, new_per_day_minimum: 0,
interday_learning_mix: 0, interday_learning_mix: 0,
review_order: 0, review_order: 0,
new_sort_order: 0,
} }
} }
} }
@ -260,10 +263,11 @@ impl From<DeckConfSchema11> for DeckConfig {
minimum_lapse_interval: c.lapse.min_int, minimum_lapse_interval: c.lapse.min_int,
graduating_interval_good: c.new.ints.good as u32, graduating_interval_good: c.new.ints.good as u32,
graduating_interval_easy: c.new.ints.easy as u32, graduating_interval_easy: c.new.ints.easy as u32,
new_card_order: match c.new.order { new_card_fetch_order: match c.new.order {
NewCardOrderSchema11::Random => NewCardOrder::Random, NewCardOrderSchema11::Random => NewCardFetchOrder::Random,
NewCardOrderSchema11::Due => NewCardOrder::Due, NewCardOrderSchema11::Due => NewCardFetchOrder::Due,
} as i32, } as i32,
new_card_sort_order: c.new_sort_order,
review_order: c.review_order, review_order: c.review_order,
new_mix: c.new_mix, new_mix: c.new_mix,
interday_learning_mix: c.interday_learning_mix, interday_learning_mix: c.interday_learning_mix,
@ -308,7 +312,7 @@ impl From<DeckConfig> for DeckConfSchema11 {
} }
} }
let i = c.inner; let i = c.inner;
let new_order = i.new_card_order(); let new_order = i.new_card_fetch_order();
DeckConfSchema11 { DeckConfSchema11 {
id: c.id, id: c.id,
mtime: c.mtime_secs, mtime: c.mtime_secs,
@ -329,8 +333,8 @@ impl From<DeckConfig> for DeckConfSchema11 {
_unused: 0, _unused: 0,
}, },
order: match new_order { order: match new_order {
NewCardOrder::Random => NewCardOrderSchema11::Random, NewCardFetchOrder::Random => NewCardOrderSchema11::Random,
NewCardOrder::Due => NewCardOrderSchema11::Due, NewCardFetchOrder::Due => NewCardOrderSchema11::Due,
}, },
per_day: i.new_per_day, per_day: i.new_per_day,
other: new_other, other: new_other,
@ -360,6 +364,7 @@ impl From<DeckConfig> for DeckConfSchema11 {
new_per_day_minimum: i.new_per_day_minimum, new_per_day_minimum: i.new_per_day_minimum,
interday_learning_mix: i.interday_learning_mix, interday_learning_mix: i.interday_learning_mix,
review_order: i.review_order, review_order: i.review_order,
new_sort_order: i.new_card_sort_order,
} }
} }
} }
@ -377,6 +382,7 @@ fn clear_other_duplicates(top_other: &mut HashMap<String, Value>) {
"newPerDayMinimum", "newPerDayMinimum",
"interdayLearningMix", "interdayLearningMix",
"reviewOrder", "reviewOrder",
"newSortOrder",
] { ] {
top_other.remove(*key); top_other.remove(*key);
} }

View file

@ -150,7 +150,7 @@ impl Collection {
let previous_config_id = DeckConfigId(normal.config_id); let previous_config_id = DeckConfigId(normal.config_id);
let previous_order = configs_before_update let previous_order = configs_before_update
.get(&previous_config_id) .get(&previous_config_id)
.map(|c| c.inner.new_card_order()) .map(|c| c.inner.new_card_fetch_order())
.unwrap_or_default(); .unwrap_or_default();
// if a selected (sub)deck, or its old config was removed, update deck to point to new config // if a selected (sub)deck, or its old config was removed, update deck to point to new config
@ -168,7 +168,7 @@ impl Collection {
// if new order differs, deck needs re-sorting // if new order differs, deck needs re-sorting
let current_order = configs_after_update let current_order = configs_after_update
.get(&current_config_id) .get(&current_config_id)
.map(|c| c.inner.new_card_order()) .map(|c| c.inner.new_card_fetch_order())
.unwrap_or_default(); .unwrap_or_default();
if previous_order != current_order { if previous_order != current_order {
self.sort_deck(deck_id, current_order, usn)?; self.sort_deck(deck_id, current_order, usn)?;
@ -183,7 +183,7 @@ impl Collection {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::{collection::open_test_collection, deckconfig::NewCardOrder}; use crate::{collection::open_test_collection, deckconfig::NewCardFetchOrder};
#[test] #[test]
fn updating() -> Result<()> { fn updating() -> Result<()> {
@ -257,7 +257,7 @@ mod test {
assert_eq!(card1_pos(&mut col), 1); assert_eq!(card1_pos(&mut col), 1);
reset_card1_pos(&mut col); reset_card1_pos(&mut col);
assert_eq!(card1_pos(&mut col), 0); assert_eq!(card1_pos(&mut col), 0);
input.configs[1].inner.new_card_order = NewCardOrder::Random as i32; input.configs[1].inner.new_card_fetch_order = NewCardFetchOrder::Random as i32;
assert_eq!( assert_eq!(
col.update_deck_configs(input.clone())?.changes.changes.card, col.update_deck_configs(input.clone())?.changes.changes.card,
true true

View file

@ -306,9 +306,15 @@ impl Collection {
} }
let next_pos = cache.next_position.unwrap(); let next_pos = cache.next_position.unwrap();
match cache.deck_configs.get(&did).unwrap().inner.new_card_order() { match cache
crate::deckconfig::NewCardOrder::Random => Ok(random_position(next_pos)), .deck_configs
crate::deckconfig::NewCardOrder::Due => Ok(next_pos), .get(&did)
.unwrap()
.inner
.new_card_fetch_order()
{
crate::deckconfig::NewCardFetchOrder::Random => Ok(random_position(next_pos)),
crate::deckconfig::NewCardFetchOrder::Due => Ok(next_pos),
} }
} }

View file

@ -7,7 +7,7 @@ use rand::seq::SliceRandom;
use crate::{ use crate::{
card::{CardQueue, CardType}, card::{CardQueue, CardType},
deckconfig::NewCardOrder, deckconfig::NewCardFetchOrder,
prelude::*, prelude::*,
search::{SortMode, StateKind}, search::{SortMode, StateKind},
}; };
@ -37,7 +37,7 @@ pub(crate) struct NewCardSorter {
} }
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum NewCardSortOrder { pub enum NewCardDueOrder {
NoteId, NoteId,
Random, Random,
Preserve, Preserve,
@ -48,7 +48,7 @@ impl NewCardSorter {
cards: &[Card], cards: &[Card],
starting_from: u32, starting_from: u32,
step: u32, step: u32,
order: NewCardSortOrder, order: NewCardDueOrder,
) -> Self { ) -> Self {
let nids = nids_in_desired_order(cards, order); let nids = nids_in_desired_order(cards, order);
@ -69,20 +69,20 @@ impl NewCardSorter {
} }
} }
fn nids_in_desired_order(cards: &[Card], order: NewCardSortOrder) -> Vec<NoteId> { fn nids_in_desired_order(cards: &[Card], order: NewCardDueOrder) -> Vec<NoteId> {
if order == NewCardSortOrder::Preserve { if order == NewCardDueOrder::Preserve {
nids_in_preserved_order(cards) nids_in_preserved_order(cards)
} else { } else {
let nids: HashSet<_> = cards.iter().map(|c| c.note_id).collect(); let nids: HashSet<_> = cards.iter().map(|c| c.note_id).collect();
let mut nids: Vec<_> = nids.into_iter().collect(); let mut nids: Vec<_> = nids.into_iter().collect();
match order { match order {
NewCardSortOrder::NoteId => { NewCardDueOrder::NoteId => {
nids.sort_unstable(); nids.sort_unstable();
} }
NewCardSortOrder::Random => { NewCardDueOrder::Random => {
nids.shuffle(&mut rand::thread_rng()); nids.shuffle(&mut rand::thread_rng());
} }
NewCardSortOrder::Preserve => unreachable!(), NewCardDueOrder::Preserve => unreachable!(),
} }
nids nids
} }
@ -128,7 +128,7 @@ impl Collection {
cids: &[CardId], cids: &[CardId],
starting_from: u32, starting_from: u32,
step: u32, step: u32,
order: NewCardSortOrder, order: NewCardDueOrder,
shift: bool, shift: bool,
) -> Result<OpOutput<usize>> { ) -> Result<OpOutput<usize>> {
let usn = self.usn()?; let usn = self.usn()?;
@ -142,7 +142,7 @@ impl Collection {
cids: &[CardId], cids: &[CardId],
starting_from: u32, starting_from: u32,
step: u32, step: u32,
order: NewCardSortOrder, order: NewCardDueOrder,
shift: bool, shift: bool,
usn: Usn, usn: Usn,
) -> Result<usize> { ) -> Result<usize> {
@ -171,9 +171,9 @@ impl Collection {
col.sort_deck( col.sort_deck(
deck, deck,
if random { if random {
NewCardOrder::Random NewCardFetchOrder::Random
} else { } else {
NewCardOrder::Due NewCardFetchOrder::Due
}, },
col.usn()?, col.usn()?,
) )
@ -183,7 +183,7 @@ impl Collection {
pub(crate) fn sort_deck( pub(crate) fn sort_deck(
&mut self, &mut self,
deck: DeckId, deck: DeckId,
order: NewCardOrder, order: NewCardFetchOrder,
usn: Usn, usn: Usn,
) -> Result<usize> { ) -> Result<usize> {
let cids = self.search_cards(match_all![deck, StateKind::New], SortMode::NoOrder)?; let cids = self.search_cards(match_all![deck, StateKind::New], SortMode::NoOrder)?;
@ -217,13 +217,13 @@ mod test {
let cards = vec![c1.clone(), c2.clone(), c3.clone()]; let cards = vec![c1.clone(), c2.clone(), c3.clone()];
// Preserve // Preserve
let sorter = NewCardSorter::new(&cards, 0, 1, NewCardSortOrder::Preserve); let sorter = NewCardSorter::new(&cards, 0, 1, NewCardDueOrder::Preserve);
assert_eq!(sorter.position(&c1), 0); assert_eq!(sorter.position(&c1), 0);
assert_eq!(sorter.position(&c2), 1); assert_eq!(sorter.position(&c2), 1);
assert_eq!(sorter.position(&c3), 2); assert_eq!(sorter.position(&c3), 2);
// NoteId/step/starting // NoteId/step/starting
let sorter = NewCardSorter::new(&cards, 3, 2, NewCardSortOrder::NoteId); let sorter = NewCardSorter::new(&cards, 3, 2, NewCardDueOrder::NoteId);
assert_eq!(sorter.position(&c3), 3); assert_eq!(sorter.position(&c3), 3);
assert_eq!(sorter.position(&c2), 5); assert_eq!(sorter.position(&c2), 5);
assert_eq!(sorter.position(&c1), 7); assert_eq!(sorter.position(&c1), 7);
@ -231,7 +231,7 @@ mod test {
// Random // Random
let mut c1_positions = HashSet::new(); let mut c1_positions = HashSet::new();
for _ in 1..100 { for _ in 1..100 {
let sorter = NewCardSorter::new(&cards, 0, 1, NewCardSortOrder::Random); let sorter = NewCardSorter::new(&cards, 0, 1, NewCardDueOrder::Random);
c1_positions.insert(sorter.position(&c1)); c1_positions.insert(sorter.position(&c1));
if c1_positions.len() == cards.len() { if c1_positions.len() == cards.len() {
return; return;
@ -241,11 +241,11 @@ mod test {
} }
} }
impl From<NewCardOrder> for NewCardSortOrder { impl From<NewCardFetchOrder> for NewCardDueOrder {
fn from(o: NewCardOrder) -> Self { fn from(o: NewCardFetchOrder) -> Self {
match o { match o {
NewCardOrder::Due => NewCardSortOrder::NoteId, NewCardFetchOrder::Due => NewCardDueOrder::NoteId,
NewCardOrder::Random => NewCardSortOrder::Random, NewCardFetchOrder::Random => NewCardDueOrder::Random,
} }
} }
} }

View file

@ -70,7 +70,9 @@ impl QueueBuilder {
let previous_card_was_sibling_with_higher_ordinal = self let previous_card_was_sibling_with_higher_ordinal = self
.new .new
.last() .last()
.map(|previous| previous.note_id == card.note_id && previous.extra > card.extra) .map(|previous| {
previous.note_id == card.note_id && previous.template_index > card.template_index
})
.unwrap_or(false); .unwrap_or(false);
if previous_card_was_sibling_with_higher_ordinal { if previous_card_was_sibling_with_higher_ordinal {
@ -87,7 +89,9 @@ impl QueueBuilder {
.enumerate() .enumerate()
.rev() .rev()
.filter_map(|(idx, queued_card)| { .filter_map(|(idx, queued_card)| {
if queued_card.note_id != card.note_id || queued_card.extra < card.extra { if queued_card.note_id != card.note_id
|| queued_card.template_index < card.template_index
{
Some(idx + 1) Some(idx + 1)
} else { } else {
None None
@ -147,26 +151,26 @@ mod test {
NewCard { NewCard {
id: CardId(1), id: CardId(1),
note_id: NoteId(1), note_id: NoteId(1),
extra: 0, template_index: 0,
..Default::default() ..Default::default()
}, },
NewCard { NewCard {
id: CardId(2), id: CardId(2),
note_id: NoteId(2), note_id: NoteId(2),
extra: 1, template_index: 1,
..Default::default() ..Default::default()
}, },
NewCard { NewCard {
id: CardId(3), id: CardId(3),
note_id: NoteId(2), note_id: NoteId(2),
extra: 2, template_index: 2,
..Default::default() ..Default::default()
}, },
NewCard { NewCard {
id: CardId(4), id: CardId(4),
note_id: NoteId(2), note_id: NoteId(2),
// lowest ordinal, should be used instead of card 2/3 // lowest ordinal, should be used instead of card 2/3
extra: 0, template_index: 0,
..Default::default() ..Default::default()
}, },
]; ];

View file

@ -19,7 +19,7 @@ use super::{
CardQueues, Counts, LearningQueueEntry, MainQueueEntry, MainQueueEntryKind, CardQueues, Counts, LearningQueueEntry, MainQueueEntry, MainQueueEntryKind,
}; };
use crate::{ use crate::{
deckconfig::{NewCardOrder, ReviewCardOrder, ReviewMix}, deckconfig::{NewCardSortOrder, ReviewCardOrder, ReviewMix},
prelude::*, prelude::*,
}; };
@ -31,8 +31,8 @@ pub(crate) struct DueCard {
pub mtime: TimestampSecs, pub mtime: TimestampSecs,
pub due: i32, pub due: i32,
pub interval: u32, pub interval: u32,
pub hash: u64,
pub original_deck_id: DeckId, pub original_deck_id: DeckId,
pub hash: u64,
} }
/// Temporary holder for new cards that will be built into a queue. /// Temporary holder for new cards that will be built into a queue.
@ -43,8 +43,8 @@ pub(crate) struct NewCard {
pub mtime: TimestampSecs, pub mtime: TimestampSecs,
pub due: i32, pub due: i32,
pub original_deck_id: DeckId, pub original_deck_id: DeckId,
/// Used to store template_idx, and for shuffling pub template_index: u32,
pub extra: u64, pub hash: u64,
} }
impl From<DueCard> for MainQueueEntry { impl From<DueCard> for MainQueueEntry {
@ -85,6 +85,14 @@ pub(super) struct BuryMode {
bury_reviews: bool, bury_reviews: bool,
} }
#[derive(Default, Clone, Debug)]
pub(super) struct QueueSortOptions {
pub(super) new_order: NewCardSortOrder,
pub(super) review_order: ReviewCardOrder,
pub(super) day_learn_mix: ReviewMix,
pub(super) new_review_mix: ReviewMix,
}
#[derive(Default)] #[derive(Default)]
pub(super) struct QueueBuilder { pub(super) struct QueueBuilder {
pub(super) new: Vec<NewCard>, pub(super) new: Vec<NewCard>,
@ -92,13 +100,17 @@ pub(super) struct QueueBuilder {
pub(super) learning: Vec<DueCard>, pub(super) learning: Vec<DueCard>,
pub(super) day_learning: Vec<DueCard>, pub(super) day_learning: Vec<DueCard>,
pub(super) seen_note_ids: HashMap<NoteId, BuryMode>, pub(super) seen_note_ids: HashMap<NoteId, BuryMode>,
pub(super) new_order: NewCardOrder, pub(super) sort_options: QueueSortOptions,
pub(super) review_order: ReviewCardOrder,
pub(super) day_learn_mix: ReviewMix,
pub(super) new_review_mix: ReviewMix,
} }
impl QueueBuilder { impl QueueBuilder {
pub(super) fn new(sort_options: QueueSortOptions) -> Self {
QueueBuilder {
sort_options,
..Default::default()
}
}
pub(super) fn build( pub(super) fn build(
mut self, mut self,
top_deck_limits: RemainingLimits, top_deck_limits: RemainingLimits,
@ -107,7 +119,7 @@ impl QueueBuilder {
current_day: u32, current_day: u32,
) -> CardQueues { ) -> CardQueues {
self.sort_new(); self.sort_new();
self.sort_reviews(); self.sort_reviews(current_day);
// split and sort learning // split and sort learning
let learn_ahead_secs = learn_ahead_secs as i64; let learn_ahead_secs = learn_ahead_secs as i64;
@ -115,14 +127,18 @@ impl QueueBuilder {
let learn_count = due_learning.len(); let learn_count = due_learning.len();
// merge day learning in, and cap to parent review count // merge day learning in, and cap to parent review count
let main_iter = merge_day_learning(self.review, self.day_learning, self.day_learn_mix); let main_iter = merge_day_learning(
self.review,
self.day_learning,
self.sort_options.day_learn_mix,
);
let main_iter = main_iter.take(top_deck_limits.review as usize); let main_iter = main_iter.take(top_deck_limits.review as usize);
let review_count = main_iter.len(); let review_count = main_iter.len();
// cap to parent new count, note down the new count, then merge new in // cap to parent new count, note down the new count, then merge new in
self.new.truncate(top_deck_limits.new as usize); self.new.truncate(top_deck_limits.new as usize);
let new_count = self.new.len(); let new_count = self.new.len();
let main_iter = merge_new(main_iter, self.new, self.new_review_mix); let main_iter = merge_new(main_iter, self.new, self.sort_options.new_review_mix);
CardQueues { CardQueues {
counts: Counts { counts: Counts {
@ -207,9 +223,27 @@ impl Collection {
let (decks, parent_count) = self.storage.deck_with_parents_and_children(deck_id)?; let (decks, parent_count) = self.storage.deck_with_parents_and_children(deck_id)?;
let deck_map = self.storage.get_decks_map()?; let deck_map = self.storage.get_decks_map()?;
let config = self.storage.get_deck_config_map()?; let config = self.storage.get_deck_config_map()?;
let sort_options = decks
.get(parent_count)
.unwrap()
.config_id()
.and_then(|config_id| config.get(&config_id))
.map(|config| QueueSortOptions {
new_order: config.inner.new_card_sort_order(),
review_order: config.inner.review_order(),
day_learn_mix: config.inner.interday_learning_mix(),
new_review_mix: config.inner.new_mix(),
})
.unwrap_or_else(|| {
// filtered decks do not space siblings
QueueSortOptions {
new_order: NewCardSortOrder::Due,
..Default::default()
}
});
let limits = remaining_limits_capped_to_parents(&decks, &config, timing.days_elapsed); let limits = remaining_limits_capped_to_parents(&decks, &config, timing.days_elapsed);
let selected_deck_limits = limits[parent_count]; let selected_deck_limits = limits[parent_count];
let mut queues = QueueBuilder::default(); let mut queues = QueueBuilder::new(sort_options);
for (deck, mut limit) in decks.iter().zip(limits).skip(parent_count) { for (deck, mut limit) in decks.iter().zip(limits).skip(parent_count) {
if limit.review > 0 { if limit.review > 0 {

View file

@ -5,58 +5,75 @@ use std::{cmp::Ordering, hash::Hasher};
use fnv::FnvHasher; use fnv::FnvHasher;
use super::{DueCard, NewCard, NewCardOrder, QueueBuilder, ReviewCardOrder}; use super::{DueCard, NewCard, NewCardSortOrder, QueueBuilder, ReviewCardOrder};
impl QueueBuilder { impl QueueBuilder {
pub(super) fn sort_new(&mut self) { pub(super) fn sort_new(&mut self) {
match self.new_order { match self.sort_options.new_order {
NewCardOrder::Random => { NewCardSortOrder::TemplateThenDue => {
self.new.iter_mut().for_each(NewCard::hash_id_and_mtime); self.new.sort_unstable_by(template_then_due);
self.new.sort_unstable_by(shuffle_new_card);
} }
NewCardOrder::Due => { NewCardSortOrder::TemplateThenRandom => {
self.new.sort_unstable_by(|a, b| a.due.cmp(&b.due)); self.new.iter_mut().for_each(NewCard::hash_id_and_mtime);
self.new.sort_unstable_by(template_then_random);
}
NewCardSortOrder::Due => self.new.sort_unstable_by(new_position),
NewCardSortOrder::Random => {
self.new.iter_mut().for_each(NewCard::hash_id_and_mtime);
self.new.sort_unstable_by(new_hash)
} }
} }
} }
pub(super) fn sort_reviews(&mut self) { pub(super) fn sort_reviews(&mut self, _current_day: u32) {
self.review.iter_mut().for_each(DueCard::hash_id_and_mtime);
self.day_learning self.day_learning
.iter_mut() .iter_mut()
.for_each(DueCard::hash_id_and_mtime); .for_each(DueCard::hash_id_and_mtime);
self.day_learning.sort_unstable_by(day_then_hash);
match self.review_order { match self.sort_options.review_order {
ReviewCardOrder::ShuffledByDay => { ReviewCardOrder::DayThenRandom => {
self.review.sort_unstable_by(shuffle_by_day); self.review.iter_mut().for_each(DueCard::hash_id_and_mtime);
self.day_learning.sort_unstable_by(shuffle_by_day); self.review.sort_unstable_by(day_then_hash);
}
ReviewCardOrder::Shuffled => {
self.review.sort_unstable_by(shuffle_due_card);
self.day_learning.sort_unstable_by(shuffle_due_card);
} }
ReviewCardOrder::IntervalsAscending => { ReviewCardOrder::IntervalsAscending => {
self.review.sort_unstable_by(intervals_ascending); self.review.sort_unstable_by(intervals_ascending);
self.day_learning.sort_unstable_by(shuffle_due_card);
} }
ReviewCardOrder::IntervalsDescending => { ReviewCardOrder::IntervalsDescending => {
self.review self.review
.sort_unstable_by(|a, b| intervals_ascending(b, a)); .sort_unstable_by(|a, b| intervals_ascending(b, a));
self.day_learning.sort_unstable_by(shuffle_due_card); } // ReviewCardOrder::RelativeOverdue => {
} // self.review
// .iter_mut()
// .for_each(|card| card.set_hash_to_relative_overdue(current_day));
// self.review.sort_unstable_by(due_card_hash)
// }
} }
} }
} }
fn shuffle_new_card(a: &NewCard, b: &NewCard) -> Ordering { fn template_then_due(a: &NewCard, b: &NewCard) -> Ordering {
a.extra.cmp(&b.extra) (a.template_index, a.due).cmp(&(b.template_index, b.due))
} }
fn shuffle_by_day(a: &DueCard, b: &DueCard) -> Ordering { fn template_then_random(a: &NewCard, b: &NewCard) -> Ordering {
(a.template_index, a.hash).cmp(&(b.template_index, b.hash))
}
fn new_position(a: &NewCard, b: &NewCard) -> Ordering {
a.due.cmp(&b.due)
}
fn new_hash(a: &NewCard, b: &NewCard) -> Ordering {
a.hash.cmp(&b.hash)
}
fn day_then_hash(a: &DueCard, b: &DueCard) -> Ordering {
(a.due, a.hash).cmp(&(b.due, b.hash)) (a.due, a.hash).cmp(&(b.due, b.hash))
} }
fn shuffle_due_card(a: &DueCard, b: &DueCard) -> Ordering { #[allow(dead_code)]
fn due_card_hash(a: &DueCard, b: &DueCard) -> Ordering {
a.hash.cmp(&b.hash) a.hash.cmp(&b.hash)
} }
@ -75,6 +92,11 @@ impl DueCard {
hasher.write_i64(self.mtime.0); hasher.write_i64(self.mtime.0);
self.hash = hasher.finish(); self.hash = hasher.finish();
} }
#[allow(dead_code)]
fn set_hash_to_relative_overdue(&mut self, _current_day: u32) {
todo!()
}
} }
impl NewCard { impl NewCard {
@ -82,6 +104,6 @@ impl NewCard {
let mut hasher = FnvHasher::default(); let mut hasher = FnvHasher::default();
hasher.write_i64(self.id.0); hasher.write_i64(self.id.0);
hasher.write_i64(self.mtime.0); hasher.write_i64(self.mtime.0);
self.extra = hasher.finish(); self.hash = hasher.finish();
} }
} }

View file

@ -217,9 +217,10 @@ impl super::SqliteStorage {
id: row.get(0)?, id: row.get(0)?,
note_id: row.get(1)?, note_id: row.get(1)?,
due: row.get(2)?, due: row.get(2)?,
extra: row.get::<_, u32>(3)? as u64, template_index: row.get(3)?,
mtime: row.get(4)?, mtime: row.get(4)?,
original_deck_id: row.get(5)?, original_deck_id: row.get(5)?,
hash: 0,
}) { }) {
break; break;
} }

View file

@ -61,5 +61,5 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<EnumSelector <EnumSelector
label={tr.schedulingOrder()} label={tr.schedulingOrder()}
choices={newOrderChoices} choices={newOrderChoices}
defaultValue={defaults.newCardOrder} defaultValue={defaults.newCardFetchOrder}
bind:value={$config.newCardOrder} /> bind:value={$config.newCardFetchOrder} />