Anki/rslib/src/decks/limits.rs
2022-09-24 11:12:58 +10:00

322 lines
10 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::HashMap, iter::Peekable};
use id_tree::{InsertBehavior, Node, NodeId, Tree};
use super::{Deck, NormalDeck};
use crate::{
deckconfig::{DeckConfig, DeckConfigId},
pb::decks::deck::normal::DayLimit,
prelude::*,
};
impl NormalDeck {
/// The deck's review limit for today, or its regular one, if any is configured.
pub fn current_review_limit(&self, today: u32) -> Option<u32> {
self.review_limit_today(today).or(self.review_limit)
}
/// The deck's new limit for today, or its regular one, if any is configured.
pub fn current_new_limit(&self, today: u32) -> Option<u32> {
self.new_limit_today(today).or(self.new_limit)
}
/// The deck's review limit for today.
pub fn review_limit_today(&self, today: u32) -> Option<u32> {
self.review_limit_today
.and_then(|day_limit| day_limit.limit(today))
}
/// The deck's new limit for today.
pub fn new_limit_today(&self, today: u32) -> Option<u32> {
self.new_limit_today
.and_then(|day_limit| day_limit.limit(today))
}
}
impl DayLimit {
pub fn limit(&self, today: u32) -> Option<u32> {
(self.today == today).then_some(self.limit)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct RemainingLimits {
pub review: u32,
pub new: u32,
}
impl RemainingLimits {
pub(crate) fn new(deck: &Deck, config: Option<&DeckConfig>, today: u32, v3: bool) -> Self {
if let Ok(normal) = deck.normal() {
if let Some(config) = config {
return Self::new_for_normal_deck(deck, today, v3, normal, config);
}
}
Self::default()
}
fn new_for_normal_deck(
deck: &Deck,
today: u32,
v3: bool,
normal: &NormalDeck,
config: &DeckConfig,
) -> RemainingLimits {
let review_limit = normal
.current_review_limit(today)
.unwrap_or(config.inner.reviews_per_day);
let new_limit = normal
.current_new_limit(today)
.unwrap_or(config.inner.new_per_day);
let (new_today, mut rev_today) = deck.new_rev_counts(today);
if v3 {
// any reviewed new cards contribute to the review limit
rev_today += new_today;
}
Self {
review: (review_limit as i32 - rev_today).max(0) as u32,
new: (new_limit as i32 - new_today).max(0) as u32,
}
}
pub(crate) fn cap_to(&mut self, limits: RemainingLimits) {
self.review = self.review.min(limits.review);
self.new = self.new.min(limits.new);
}
}
impl Default for RemainingLimits {
fn default() -> Self {
RemainingLimits {
review: 9999,
new: 9999,
}
}
}
pub(crate) fn remaining_limits_map<'a>(
decks: impl Iterator<Item = &'a Deck>,
config: &'a HashMap<DeckConfigId, DeckConfig>,
today: u32,
v3: bool,
) -> HashMap<DeckId, RemainingLimits> {
decks
.map(|deck| {
(
deck.id,
RemainingLimits::new(
deck,
deck.config_id().and_then(|id| config.get(&id)),
today,
v3,
),
)
})
.collect()
}
/// Wrapper of [RemainingLimits] with some additional meta data.
#[derive(Debug, Clone, Copy)]
struct NodeLimits {
deck_id: DeckId,
/// absolute level in the deck hierarchy
level: usize,
limits: RemainingLimits,
}
impl NodeLimits {
fn new(deck: &Deck, config: &HashMap<DeckConfigId, DeckConfig>, today: u32) -> Self {
Self {
deck_id: deck.id,
level: deck.name.components().count(),
limits: RemainingLimits::new(
deck,
deck.config_id().and_then(|id| config.get(&id)),
today,
true,
),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct LimitTreeMap {
/// A tree representing the remaining limits of the active deck hierarchy.
//
// As long as we never (1) allow a tree without a root, (2) remove nodes,
// and (3) have more than 1 tree, it's safe to unwrap on Tree::get() and
// Tree::root_node_id(), even if we clone Nodes.
tree: Tree<NodeLimits>,
/// A map to access the tree node of a deck. Only decks with a remaining
/// limit above zero are included.
map: HashMap<DeckId, NodeId>,
}
impl LimitTreeMap {
/// Child [Deck]s must be sorted by name.
pub(crate) fn build(
root_deck: &Deck,
child_decks: Vec<Deck>,
config: &HashMap<DeckConfigId, DeckConfig>,
today: u32,
) -> Self {
let root_limits = NodeLimits::new(root_deck, config, today);
let mut tree = Tree::new();
let root_id = tree
.insert(Node::new(root_limits), InsertBehavior::AsRoot)
.unwrap();
let mut map = HashMap::new();
if root_limits.limits.review > 0 {
map.insert(root_deck.id, root_id.clone());
}
let mut limits = Self { tree, map };
let mut remaining_decks = child_decks.into_iter().peekable();
limits.add_child_nodes(root_id, &mut remaining_decks, config, today);
limits
}
/// Recursively appends descendants to the provided parent [Node], and adds
/// them to the [HashMap].
/// Given [Deck]s are assumed to arrive in depth-first order.
/// The tree-from-deck-list logic is taken from [crate::decks::tree::add_child_nodes].
fn add_child_nodes(
&mut self,
parent_node_id: NodeId,
remaining_decks: &mut Peekable<impl Iterator<Item = Deck>>,
config: &HashMap<DeckConfigId, DeckConfig>,
today: u32,
) {
let parent = *self.tree.get(&parent_node_id).unwrap().data();
while let Some(deck) = remaining_decks.peek() {
match deck.name.components().count() {
l if l <= parent.level => {
// next item is at a higher level
break;
}
l if l == parent.level + 1 => {
// next item is an immediate descendent of parent
self.insert_child_node(deck, parent_node_id.clone(), config, today);
remaining_decks.next();
}
_ => {
// next item is at a lower level
if let Some(last_child_node_id) = self
.tree
.get(&parent_node_id)
.unwrap()
.children()
.last()
.cloned()
{
self.add_child_nodes(last_child_node_id, remaining_decks, config, today)
} else {
// immediate parent is missing, skip the deck until a DB check is run
remaining_decks.next();
}
}
}
}
}
fn insert_child_node(
&mut self,
child_deck: &Deck,
parent_node_id: NodeId,
config: &HashMap<DeckConfigId, DeckConfig>,
today: u32,
) {
let mut child_limits = NodeLimits::new(child_deck, config, today);
child_limits
.limits
.cap_to(self.tree.get(&parent_node_id).unwrap().data().limits);
let child_node_id = self
.tree
.insert(
Node::new(child_limits),
InsertBehavior::UnderNode(&parent_node_id),
)
.unwrap();
if child_limits.limits.review > 0 {
self.map.insert(child_deck.id, child_node_id);
}
}
pub(crate) fn root_limit_reached(&self) -> bool {
self.map.is_empty()
}
pub(crate) fn limit_reached(&self, deck_id: DeckId) -> bool {
self.map.get(&deck_id).is_none()
}
pub(crate) fn active_decks(&self) -> Vec<DeckId> {
self.tree
.traverse_pre_order(self.tree.root_node_id().unwrap())
.unwrap()
.map(|node| node.data().deck_id)
.collect()
}
pub(crate) fn remaining_node_id(&self, deck_id: DeckId) -> Option<NodeId> {
self.map.get(&deck_id).map(Clone::clone)
}
pub(crate) fn decrement_node_and_parent_limits(&mut self, node_id: &NodeId, new: bool) {
let node = self.tree.get_mut(node_id).unwrap();
let parent = node.parent().cloned();
let limit = &mut node.data_mut().limits;
if if new {
limit.new = limit.new.saturating_sub(1);
limit.new
} else {
limit.review = limit.review.saturating_sub(1);
limit.review
} == 0
{
self.remove_node_and_descendants_from_map(node_id);
};
if let Some(parent_id) = parent {
self.decrement_node_and_parent_limits(&parent_id, new)
}
}
pub(crate) fn remove_node_and_descendants_from_map(&mut self, node_id: &NodeId) {
let node = self.tree.get(node_id).unwrap();
self.map.remove(&node.data().deck_id);
for child_id in node.children().clone() {
self.remove_node_and_descendants_from_map(&child_id);
}
}
pub(crate) fn cap_new_to_review(&mut self) {
self.cap_new_to_review_rec(&self.tree.root_node_id().unwrap().clone(), 9999);
}
fn cap_new_to_review_rec(&mut self, node_id: &NodeId, parent_limit: u32) {
let node = self.tree.get_mut(node_id).unwrap();
let mut limits = &mut node.data_mut().limits;
limits.new = limits.new.min(limits.review).min(parent_limit);
// clone because of borrowing rules
let node_limit = limits.new;
let children = node.children().clone();
if node_limit == 0 {
self.remove_node_and_descendants_from_map(node_id);
}
for child_id in children {
self.cap_new_to_review_rec(&child_id, node_limit);
}
}
}