mirror of
https://github.com/ankitects/anki.git
synced 2025-11-06 12:47:11 -05:00
228 lines
7.9 KiB
Rust
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)
|
|
}
|
|
}
|