Anki/rslib/src/scheduler/queue/builder/gathering.rs
jariji 45223075ea
Update rslib/src/scheduler/queue/builder/gathering.rs
Co-authored-by: Abdo <abdo@abdnh.net>
2025-09-29 12:27:45 -07:00

228 lines
7.9 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::collections::HashSet;
use rand::rng;
use rand::Rng;
use super::DueCard;
use super::NewCard;
use super::QueueBuilder;
use crate::deckconfig::NewCardGatherPriority;
use crate::decks::limits::LimitKind;
use crate::prelude::*;
use crate::scheduler::queue::DueCardKind;
use crate::storage::card::NewCardSorting;
impl QueueBuilder {
pub(super) fn gather_cards(&mut self, col: &mut Collection) -> Result<()> {
self.gather_intraday_learning_cards(col)?;
self.gather_due_cards(col, DueCardKind::Learning)?;
self.gather_due_cards(col, DueCardKind::Review)?;
self.gather_new_cards(col)?;
Ok(())
}
fn gather_intraday_learning_cards(&mut self, col: &mut Collection) -> Result<()> {
col.storage.for_each_intraday_card_in_active_decks(
self.context.timing.next_day_at,
|card| {
self.get_and_update_bury_mode_for_note(card.into());
self.learning.push(card);
},
)?;
Ok(())
}
fn gather_due_cards(&mut self, col: &mut Collection, kind: DueCardKind) -> Result<()> {
if self.limits.root_limit_reached(LimitKind::Review) {
return Ok(());
}
col.storage.for_each_due_card_in_active_decks(
self.context.timing,
self.context.sort_options.review_order,
kind,
self.context.fsrs,
|card| {
if self.limits.root_limit_reached(LimitKind::Review) {
return Ok(false);
}
if !self
.limits
.limit_reached(card.current_deck_id, LimitKind::Review)?
&& self.add_due_card(card)
{
self.limits.decrement_deck_and_parent_limits(
card.current_deck_id,
LimitKind::Review,
)?;
}
Ok(true)
},
)
}
fn gather_new_cards(&mut self, col: &mut Collection) -> Result<()> {
let salt = Self::knuth_salt(self.context.timing.days_elapsed);
match self.context.sort_options.new_gather_priority {
NewCardGatherPriority::Deck => {
self.gather_new_cards_by_deck(col, NewCardSorting::LowestPosition)
}
NewCardGatherPriority::EachFromRandomDeck => {
self.gather_new_cards_by_each_from_random_deck(col, NewCardSorting::LowestPosition)
}
NewCardGatherPriority::DeckThenRandomNotes => {
self.gather_new_cards_by_deck(col, NewCardSorting::RandomNotes(salt))
}
NewCardGatherPriority::LowestPosition => {
self.gather_new_cards_sorted(col, NewCardSorting::LowestPosition)
}
NewCardGatherPriority::HighestPosition => {
self.gather_new_cards_sorted(col, NewCardSorting::HighestPosition)
}
NewCardGatherPriority::RandomNotes => {
self.gather_new_cards_sorted(col, NewCardSorting::RandomNotes(salt))
}
NewCardGatherPriority::RandomCards => {
self.gather_new_cards_sorted(col, NewCardSorting::RandomCards(salt))
}
}
}
fn gather_new_cards_by_deck(
&mut self,
col: &mut Collection,
sort: NewCardSorting,
) -> Result<()> {
for deck_id in col.storage.get_active_deck_ids_sorted()? {
if self.limits.root_limit_reached(LimitKind::New) {
break;
}
if self.limits.limit_reached(deck_id, LimitKind::New)? {
continue;
}
col.storage
.for_each_new_card_in_deck(deck_id, sort, |card| {
let limit_reached = self.limits.limit_reached(deck_id, LimitKind::New)?;
if !limit_reached && self.add_new_card(card) {
self.limits
.decrement_deck_and_parent_limits(deck_id, LimitKind::New)?;
}
Ok(!limit_reached)
})?;
}
Ok(())
}
fn gather_new_cards_by_each_from_random_deck(
&mut self,
col: &mut Collection,
sort: NewCardSorting,
) -> Result<()> {
let mut deck_ids = col.storage.get_active_deck_ids_sorted()?;
let mut rng = rng();
let mut cards_added = HashSet::<CardId>::new();
// Continue until global limit is reached or no more decks with cards.
while !self.limits.root_limit_reached(LimitKind::New) && !deck_ids.is_empty() {
let selected_index = rng.random_range(0..deck_ids.len());
let selected_deck = deck_ids[selected_index];
if self.limits.limit_reached(selected_deck, LimitKind::New)? {
// Remove the deck from the list since it's at its limit.
deck_ids.swap_remove(selected_index);
continue;
}
let mut found_card = false;
col.storage
.for_each_new_card_in_deck(selected_deck, sort, |card| {
if !cards_added.contains(&card.id) && self.add_new_card(card) {
cards_added.insert(card.id);
self.limits
.decrement_deck_and_parent_limits(selected_deck, LimitKind::New)?;
found_card = true;
// Stop iterating this deck after getting one card.
Ok(false)
} else {
Ok(true)
}
})?;
// If we couldn't find any card from this deck, remove it from consideration
if !found_card {
deck_ids.swap_remove(selected_index);
}
}
Ok(())
}
fn gather_new_cards_sorted(
&mut self,
col: &mut Collection,
order: NewCardSorting,
) -> Result<()> {
col.storage
.for_each_new_card_in_active_decks(order, |card| {
if self.limits.root_limit_reached(LimitKind::New) {
return Ok(false);
}
if !self
.limits
.limit_reached(card.current_deck_id, LimitKind::New)?
&& self.add_new_card(card)
{
self.limits
.decrement_deck_and_parent_limits(card.current_deck_id, LimitKind::New)?;
}
Ok(true)
})
}
/// True if limit should be decremented.
fn add_due_card(&mut self, card: DueCard) -> bool {
let bury_this_card = self
.get_and_update_bury_mode_for_note(card.into())
.map(|mode| match card.kind {
DueCardKind::Review => mode.bury_reviews,
DueCardKind::Learning => mode.bury_interday_learning,
})
.unwrap_or_default();
if bury_this_card {
false
} else {
match card.kind {
DueCardKind::Review => self.review.push(card),
DueCardKind::Learning => self.day_learning.push(card),
}
true
}
}
// True if limit should be decremented.
fn add_new_card(&mut self, card: NewCard) -> bool {
let bury_this_card = self
.get_and_update_bury_mode_for_note(card.into())
.map(|mode| mode.bury_new)
.unwrap_or_default();
// no previous siblings seen?
if bury_this_card {
false
} else {
self.new.push(card);
true
}
}
// Generates a salt for use with fnvhash. Useful to increase randomness
// when the base salt is a small integer.
fn knuth_salt(base_salt: u32) -> u32 {
base_salt.wrapping_mul(2654435761)
}
}