From d7caafe91c9da148062a2211d83d4bf97c0e2479 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Thu, 15 Jul 2021 15:21:58 +1000 Subject: [PATCH] scheduler: use deck config's initial ease in set_due_date Previously we would just use 250% ease for any new card that had no pre-configured ease, but this will result in decks that have non-standard ease values to have "set due date" cards in them that don't match. In order to make this somewhat more efficient, we cache deckid->ease lookups during this operation. Ref: Signed-off-by: Aleksa Sarai --- rslib/src/scheduler/reviews.rs | 59 +++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/rslib/src/scheduler/reviews.rs b/rslib/src/scheduler/reviews.rs index 3a09c5ee7..b7cef5e91 100644 --- a/rslib/src/scheduler/reviews.rs +++ b/rslib/src/scheduler/reviews.rs @@ -1,6 +1,8 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +use std::collections::HashMap; + use lazy_static::lazy_static; use rand::distributions::{Distribution, Uniform}; use regex::Regex; @@ -9,7 +11,6 @@ use crate::{ card::{Card, CardId, CardQueue, CardType}, collection::Collection, config::StringKey, - deckconfig::INITIAL_EASE_FACTOR_THOUSANDS, error::Result, prelude::*, }; @@ -19,7 +20,14 @@ impl Card { /// If card is not a review card, convert it into one. /// Review/relearning cards have their interval preserved unless /// `force_reset` is true. - fn set_due_date(&mut self, today: u32, days_from_today: u32, force_reset: bool) { + /// If the card has no ease factor (it's new), `ease_factor` is used. + fn set_due_date( + &mut self, + today: u32, + days_from_today: u32, + ease_factor: f32, + force_reset: bool, + ) { let new_due = (today + days_from_today) as i32; let new_interval = if force_reset || !matches!(self.ctype, CardType::Review | CardType::Relearn) { @@ -27,11 +35,12 @@ impl Card { } else { self.interval }; + let ease_factor = (ease_factor * 1000.0).round() as u16; - self.schedule_as_review(new_interval, new_due); + self.schedule_as_review(new_interval, new_due, ease_factor); } - fn schedule_as_review(&mut self, interval: u32, due: i32) { + fn schedule_as_review(&mut self, interval: u32, due: i32, ease_factor: u16) { self.remove_from_filtered_deck_before_reschedule(); self.interval = interval.max(1); self.due = due; @@ -40,7 +49,7 @@ impl Card { if self.ease_factor == 0 { // unlike the old Python code, we leave the ease factor alone // if it's already set - self.ease_factor = INITIAL_EASE_FACTOR_THOUSANDS; + self.ease_factor = ease_factor; } } } @@ -100,12 +109,31 @@ impl Collection { let today = self.timing_today()?.days_elapsed; let mut rng = rand::thread_rng(); let distribution = Uniform::from(spec.min..=spec.max); + let mut decks_initial_ease: HashMap = HashMap::new(); self.transact(Op::SetDueDate, |col| { col.storage.set_search_table_to_card_ids(cids, false)?; for mut card in col.storage.all_searched_cards()? { + let deck_id = card.original_deck_id.or(card.deck_id); + let ease_factor = match decks_initial_ease.get(&deck_id) { + Some(ease) => *ease, + None => { + let config_id = col + .get_deck(deck_id)? + .ok_or(AnkiError::NotFound)? + .config_id() + .ok_or(AnkiError::NotFound)?; + let ease = col + .get_deck_config(config_id, true)? + .unwrap_or_else(Default::default) // cannot happen + .inner + .initial_ease; + decks_initial_ease.insert(deck_id, ease); + ease + } + }; let original = card.clone(); let days_from_today = distribution.sample(&mut rng); - card.set_due_date(today, days_from_today, spec.force_reset); + card.set_due_date(today, days_from_today, ease_factor, spec.force_reset); col.log_manually_scheduled_review(&card, &original, usn)?; col.update_card_inner(&mut card, original, usn)?; } @@ -169,35 +197,41 @@ mod test { let mut c = Card::new(NoteId(0), 0, DeckId(0), 0); // setting the due date of a new card will convert it - c.set_due_date(5, 2, false); + c.set_due_date(5, 2, 1.8, false); assert_eq!(c.ctype, CardType::Review); assert_eq!(c.due, 7); assert_eq!(c.interval, 2); + assert_eq!(c.ease_factor, 1800); // reschedule it again the next day, shifting it from day 7 to day 9 - c.set_due_date(6, 3, false); + c.set_due_date(6, 3, 2.5, false); assert_eq!(c.due, 9); assert_eq!(c.interval, 2); + assert_eq!(c.ease_factor, 1800); // interval doesn't change // we can bring cards forward too - return it to its original due date - c.set_due_date(6, 1, false); + c.set_due_date(6, 1, 2.4, false); assert_eq!(c.due, 7); assert_eq!(c.interval, 2); + assert_eq!(c.ease_factor, 1800); // interval doesn't change // we can force the interval to be reset instead of shifted - c.set_due_date(6, 3, true); + c.set_due_date(6, 3, 2.3, true); assert_eq!(c.due, 9); assert_eq!(c.interval, 3); + assert_eq!(c.ease_factor, 1800); // interval doesn't change // should work in a filtered deck c.interval = 2; + c.ease_factor = 0; c.original_due = 7; c.original_deck_id = DeckId(1); c.due = -10000; c.queue = CardQueue::New; - c.set_due_date(6, 1, false); + c.set_due_date(6, 1, 2.2, false); assert_eq!(c.due, 7); assert_eq!(c.interval, 2); + assert_eq!(c.ease_factor, 2200); assert_eq!(c.queue, CardQueue::Review); assert_eq!(c.original_due, 0); assert_eq!(c.original_deck_id, DeckId(0)); @@ -206,8 +240,9 @@ mod test { c.ctype = CardType::Relearn; c.original_due = c.due; c.due = 12345678; - c.set_due_date(6, 10, false); + c.set_due_date(6, 10, 2.1, false); assert_eq!(c.due, 16); assert_eq!(c.interval, 2); + assert_eq!(c.ease_factor, 2200); // interval doesn't change } }