diff --git a/ftl/core/deck-config.ftl b/ftl/core/deck-config.ftl index e97166fe1..f42cf7900 100644 --- a/ftl/core/deck-config.ftl +++ b/ftl/core/deck-config.ftl @@ -170,6 +170,7 @@ deck-config-sort-order-ascending-intervals = Ascending intervals deck-config-sort-order-descending-intervals = Descending intervals deck-config-sort-order-ascending-ease = Ascending ease deck-config-sort-order-descending-ease = Descending ease +deck-config-sort-order-relative-overdueness = Relative overdueness deck-config-display-order-will-use-current-deck = Anki will use the display order from the deck you select to study, and not any subdecks it may have. diff --git a/proto/anki/deckconfig.proto b/proto/anki/deckconfig.proto index 755896f0e..86307d100 100644 --- a/proto/anki/deckconfig.proto +++ b/proto/anki/deckconfig.proto @@ -68,6 +68,7 @@ message DeckConfig { REVIEW_CARD_ORDER_INTERVALS_DESCENDING = 4; REVIEW_CARD_ORDER_EASE_ASCENDING = 5; REVIEW_CARD_ORDER_EASE_DESCENDING = 6; + REVIEW_CARD_ORDER_RELATIVE_OVERDUENESS = 7; } enum ReviewMix { REVIEW_MIX_MIX_WITH_REVIEWS = 0; diff --git a/rslib/src/scheduler/queue/builder/mod.rs b/rslib/src/scheduler/queue/builder/mod.rs index a60c95d57..8da5b11a9 100644 --- a/rslib/src/scheduler/queue/builder/mod.rs +++ b/rslib/src/scheduler/queue/builder/mod.rs @@ -265,6 +265,7 @@ mod test { use super::*; use crate::{ backend_proto::deck_config::config::{NewCardGatherPriority, NewCardSortOrder}, + card::{CardQueue, CardType}, collection::open_test_collection, }; @@ -296,10 +297,29 @@ mod test { }) .collect() } + + fn set_deck_review_order(&mut self, deck: &mut Deck, order: ReviewCardOrder) { + let mut conf = DeckConfig::default(); + conf.inner.review_order = order as i32; + self.add_or_update_deck_config(&mut conf).unwrap(); + deck.normal_mut().unwrap().config_id = conf.id.0; + self.add_or_update_deck(deck).unwrap(); + } + + fn queue_as_due_and_ivl(&mut self, deck_id: DeckId) -> Vec<(i32, u32)> { + self.build_queues(deck_id) + .unwrap() + .iter() + .map(|entry| { + let card = self.storage.get_card(entry.card_id()).unwrap().unwrap(); + (card.due, card.interval) + }) + .collect() + } } #[test] - fn queue_building() -> Result<()> { + fn new_queue_building() -> Result<()> { let mut col = open_test_collection(); col.set_config_bool(BoolKey::Sched2021, true, false)?; @@ -366,4 +386,52 @@ mod test { Ok(()) } + + #[test] + fn review_queue_building() -> Result<()> { + let mut col = open_test_collection(); + col.set_config_bool(BoolKey::Sched2021, true, false)?; + + let mut deck = col.get_or_create_normal_deck("Default").unwrap(); + let nt = col.get_notetype_by_name("Basic")?.unwrap(); + let mut cards = vec![]; + + // relative overdueness + let expected_queue = vec![ + (-150, 1), + (-100, 1), + (-50, 1), + (-150, 5), + (-100, 5), + (-50, 5), + (-150, 20), + (-150, 20), + (-100, 20), + (-50, 20), + (-150, 100), + (-100, 100), + (-50, 100), + (0, 1), + (0, 5), + (0, 20), + (0, 100), + ]; + for t in expected_queue.iter() { + let mut note = nt.new_note(); + note.set_field(0, "foo")?; + note.id.0 = 0; + col.add_note(&mut note, deck.id)?; + let mut card = col.storage.get_card_by_ordinal(note.id, 0)?.unwrap(); + card.interval = t.1; + card.due = t.0; + card.ctype = CardType::Review; + card.queue = CardQueue::Review; + cards.push(card); + } + col.update_cards_maybe_undoable(cards, false)?; + col.set_deck_review_order(&mut deck, ReviewCardOrder::RelativeOverdueness); + assert_eq!(col.queue_as_due_and_ivl(deck.id), expected_queue); + + Ok(()) + } } diff --git a/rslib/src/storage/card/mod.rs b/rslib/src/storage/card/mod.rs index 8d07d61e6..f62053ae8 100644 --- a/rslib/src/storage/card/mod.rs +++ b/rslib/src/storage/card/mod.rs @@ -4,7 +4,7 @@ pub(crate) mod data; pub(crate) mod filtered; -use std::{collections::HashSet, convert::TryFrom, result}; +use std::{collections::HashSet, convert::TryFrom, fmt, result}; use rusqlite::{ named_params, params, @@ -216,7 +216,7 @@ impl super::SqliteStorage { where F: FnMut(DueCard) -> bool, { - let order_clause = review_order_sql(order); + let order_clause = review_order_sql(order, day_cutoff); let mut stmt = self.db.prepare_cached(&format!( "{} order by {}", include_str!("due_cards.sql"), @@ -612,11 +612,13 @@ enum ReviewOrderSubclause { IntervalsDescending, EaseAscending, EaseDescending, + RelativeOverdueness { today: u32 }, } -impl ReviewOrderSubclause { - fn to_str(self) -> &'static str { - match self { +impl fmt::Display for ReviewOrderSubclause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let temp_string; + let clause = match self { ReviewOrderSubclause::Day => "due", ReviewOrderSubclause::Deck => "(select rowid from active_decks ad where ad.id = did)", ReviewOrderSubclause::Random => "fnvhash(id, mod)", @@ -624,11 +626,16 @@ impl ReviewOrderSubclause { ReviewOrderSubclause::IntervalsDescending => "ivl desc", ReviewOrderSubclause::EaseAscending => "factor asc", ReviewOrderSubclause::EaseDescending => "factor desc", - } + ReviewOrderSubclause::RelativeOverdueness { today } => { + temp_string = format!("ivl / cast({today}-due+0.001 as real)", today = today); + &temp_string + } + }; + write!(f, "{}", clause) } } -fn review_order_sql(order: ReviewCardOrder) -> String { +fn review_order_sql(order: ReviewCardOrder, today: u32) -> String { let mut subclauses = match order { ReviewCardOrder::Day => vec![ReviewOrderSubclause::Day], ReviewCardOrder::DayThenDeck => vec![ReviewOrderSubclause::Day, ReviewOrderSubclause::Deck], @@ -637,12 +644,15 @@ fn review_order_sql(order: ReviewCardOrder) -> String { ReviewCardOrder::IntervalsDescending => vec![ReviewOrderSubclause::IntervalsDescending], ReviewCardOrder::EaseAscending => vec![ReviewOrderSubclause::EaseAscending], ReviewCardOrder::EaseDescending => vec![ReviewOrderSubclause::EaseDescending], + ReviewCardOrder::RelativeOverdueness => { + vec![ReviewOrderSubclause::RelativeOverdueness { today }] + } }; subclauses.push(ReviewOrderSubclause::Random); let v: Vec<_> = subclauses - .into_iter() - .map(ReviewOrderSubclause::to_str) + .iter() + .map(ReviewOrderSubclause::to_string) .collect(); v.join(", ") } diff --git a/ts/deck-options/DisplayOrder.svelte b/ts/deck-options/DisplayOrder.svelte index 33d77f5ff..3039df645 100644 --- a/ts/deck-options/DisplayOrder.svelte +++ b/ts/deck-options/DisplayOrder.svelte @@ -42,6 +42,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html tr.deckConfigSortOrderDescendingIntervals(), tr.deckConfigSortOrderAscendingEase(), tr.deckConfigSortOrderDescendingEase(), + tr.deckConfigSortOrderRelativeOverdueness(), ]; const GatherOrder = DeckConfig.DeckConfig.Config.NewCardGatherPriority;