diff --git a/rslib/src/scheduler/queue/builder/mod.rs b/rslib/src/scheduler/queue/builder/mod.rs index 064220bce..1cd29744d 100644 --- a/rslib/src/scheduler/queue/builder/mod.rs +++ b/rslib/src/scheduler/queue/builder/mod.rs @@ -38,6 +38,7 @@ pub(crate) struct DueCard { pub current_deck_id: DeckId, pub original_deck_id: DeckId, pub kind: DueCardKind, + pub reps: u32, } #[derive(Debug, Clone, Copy)] @@ -87,6 +88,7 @@ impl From for LearningQueueEntry { due: TimestampSecs(c.due as i64), id: c.id, mtime: c.mtime, + reps: c.reps, } } } @@ -274,9 +276,21 @@ fn merge_new( } } -fn sort_learning(mut learning: Vec) -> VecDeque { - learning.sort_unstable_by(|a, b| a.due.cmp(&b.due)); - learning.into_iter().map(LearningQueueEntry::from).collect() +fn sort_learning(learning: Vec) -> VecDeque { + // Prioritize intraday learning cards that were previously attempted + // (reps > 0) before never-attempted cards (reps == 0). Preserve due-time + // ordering within each group. + let (mut previously_attempted, mut never_attempted): (Vec, Vec) = + learning.into_iter().partition(|c| c.reps > 0); + + previously_attempted.sort_unstable_by(|a, b| a.due.cmp(&b.due)); + never_attempted.sort_unstable_by(|a, b| a.due.cmp(&b.due)); + + previously_attempted + .into_iter() + .chain(never_attempted) + .map(LearningQueueEntry::from) + .collect() } impl Collection { @@ -543,4 +557,49 @@ mod test { col.set_current_deck(child.id).unwrap(); assert_eq!(col.card_queue_len(), 0); } + + #[test] + fn intraday_learning_prioritizes_previously_attempted() -> Result<()> { + let mut col = Collection::new(); + + // create two notes with one card each + let nt = col.get_notetype_by_name("Basic")?.unwrap(); + let mut n1 = nt.new_note(); + n1.set_field(0, "one")?; + col.add_note(&mut n1, DeckId(1))?; + let mut n2 = nt.new_note(); + n2.set_field(0, "two")?; + col.add_note(&mut n2, DeckId(1))?; + + // fetch their cards and make them intraday learning cards + let mut c1 = col.storage.get_card_by_ordinal(n1.id, 0)?.unwrap(); + let mut c2 = col.storage.get_card_by_ordinal(n2.id, 0)?.unwrap(); + + c1.queue = CardQueue::Learn; + c2.queue = CardQueue::Learn; + + // c1: never attempted, due earlier (0) + c1.due = 0; + c1.reps = 0; + // c2: previously attempted, due later (600) + c2.due = 600; + c2.reps = 1; + + let id1 = c1.id; + let id2 = c2.id; + + col.update_cards_maybe_undoable(vec![c1, c2], false)?; + + // build queues and inspect intraday learning order + let queues = col.build_queues(DeckId(1))?; + let ids: Vec<_> = queues.intraday_learning.iter().map(|e| e.id).collect(); + + // ensure the previously attempted card appears before the never-attempted one + assert!( + ids.iter().position(|&id| id == id2).unwrap() + < ids.iter().position(|&id| id == id1).unwrap() + ); + + Ok(()) + } } diff --git a/rslib/src/scheduler/queue/entry.rs b/rslib/src/scheduler/queue/entry.rs index c9e587747..1ca9de478 100644 --- a/rslib/src/scheduler/queue/entry.rs +++ b/rslib/src/scheduler/queue/entry.rs @@ -55,6 +55,7 @@ impl From<&Card> for QueueEntry { due: TimestampSecs(card.due as i64), id: card.id, mtime: card.mtime, + reps: card.reps, }); } CardQueue::New => MainQueueEntryKind::New, diff --git a/rslib/src/scheduler/queue/learning.rs b/rslib/src/scheduler/queue/learning.rs index 79d0304c9..8e2d210b4 100644 --- a/rslib/src/scheduler/queue/learning.rs +++ b/rslib/src/scheduler/queue/learning.rs @@ -12,6 +12,7 @@ pub(crate) struct LearningQueueEntry { pub due: TimestampSecs, pub id: CardId, pub mtime: TimestampSecs, + pub reps: u32, } impl CardQueues { @@ -71,6 +72,7 @@ impl CardQueues { due: TimestampSecs(card.due as i64), id: card.id, mtime: card.mtime, + reps: card.reps, }; Some(self.requeue_learning_entry(entry)) diff --git a/rslib/src/storage/card/due_cards.sql b/rslib/src/storage/card/due_cards.sql index 0c29b5a31..149cc0f27 100644 --- a/rslib/src/storage/card/due_cards.sql +++ b/rslib/src/storage/card/due_cards.sql @@ -4,7 +4,8 @@ SELECT id, cast(ivl AS integer), cast(mod AS integer), did, - odid + odid, + reps FROM cards WHERE did IN ( SELECT id diff --git a/rslib/src/storage/card/intraday_due.sql b/rslib/src/storage/card/intraday_due.sql index 634cacd6b..a24874659 100644 --- a/rslib/src/storage/card/intraday_due.sql +++ b/rslib/src/storage/card/intraday_due.sql @@ -3,7 +3,8 @@ SELECT id, due, cast(mod AS integer), did, - odid + odid, + reps FROM cards WHERE did IN ( SELECT id diff --git a/rslib/src/storage/card/mod.rs b/rslib/src/storage/card/mod.rs index 3a5066ff4..787b4b5d8 100644 --- a/rslib/src/storage/card/mod.rs +++ b/rslib/src/storage/card/mod.rs @@ -267,6 +267,7 @@ impl super::SqliteStorage { mtime: row.get(3)?, current_deck_id: row.get(4)?, original_deck_id: row.get(5)?, + reps: row.get(6)?, kind: DueCardKind::Learning, }) } @@ -306,6 +307,7 @@ impl super::SqliteStorage { mtime: row.get(4)?, current_deck_id: row.get(5)?, original_deck_id: row.get(6)?, + reps: row.get(7)?, kind, })? { break;