Anki/rslib/src/scheduler/answering/preview.rs
Jarrett Ye 0e31efac08
Feat/grade now (#3840)
* Feat/grade now

* pass ci

* fix from_queue

* Refactor card answering to support from_queue flag

- Add `from_queue` field to `CardAnswer` struct and proto message
- Modify `answer_card_inner` to handle queue updates based on `from_queue`
- Remove `grade_card` method and consolidate card answering logic
- Update related test cases to set `from_queue` flag

* fix current_changes() called when no op set

* Optimize queue updates for batch card processing

- Refactor `grade_now` to collect processed card IDs first
- Add new `update_queues_for_processed_cards` method for efficient batch queue updates
- Improve queue management by removing entries and updating counts in a single pass
- Remove individual queue update method in favor of batch processing

* pass ci

* keep the same style

* remove ineffective code

* remove unused imports
2025-03-15 17:30:40 +07:00

139 lines
4.3 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use super::CardStateUpdater;
use super::RevlogEntryPartial;
use crate::card::CardQueue;
use crate::scheduler::states::CardState;
use crate::scheduler::states::IntervalKind;
use crate::scheduler::states::PreviewState;
impl CardStateUpdater {
pub(super) fn apply_preview_state(
&mut self,
current: CardState,
next: PreviewState,
) -> RevlogEntryPartial {
let revlog = RevlogEntryPartial::new(current, next.into(), 0.0, self.secs_until_rollover());
if next.finished {
self.card.remove_from_filtered_deck_restoring_queue();
return revlog;
}
self.card.queue = CardQueue::PreviewRepeat;
let interval = next.interval_kind();
match interval {
IntervalKind::InSecs(secs) => {
self.card.due = self.fuzzed_next_learning_timestamp(secs);
}
IntervalKind::InDays(_days) => {
// unsupported
}
}
revlog
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::card::CardType;
use crate::prelude::*;
use crate::scheduler::answering::CardAnswer;
use crate::scheduler::answering::Rating;
use crate::scheduler::states::CardState;
use crate::scheduler::states::FilteredState;
use crate::timestamp::TimestampMillis;
#[test]
fn preview() -> Result<()> {
let mut col = Collection::new();
let mut c = Card {
deck_id: DeckId(1),
ctype: CardType::Learn,
queue: CardQueue::DayLearn,
remaining_steps: 2,
due: 123,
..Default::default()
};
col.add_card(&mut c)?;
// pull the card into a preview deck
let mut filtered_deck = Deck::new_filtered();
filtered_deck.filtered_mut()?.reschedule = false;
col.add_or_update_deck(&mut filtered_deck)?;
assert_eq!(col.rebuild_filtered_deck(filtered_deck.id)?.output, 1);
let next = col.get_scheduling_states(c.id)?;
assert!(matches!(
next.current,
CardState::Filtered(FilteredState::Preview(_))
));
// the exit state should have a 0 second interval, which will show up as (end)
assert!(matches!(
next.easy,
CardState::Filtered(FilteredState::Preview(PreviewState {
scheduled_secs: 0,
finished: true
}))
));
assert!(matches!(
next.good,
CardState::Filtered(FilteredState::Preview(PreviewState {
scheduled_secs: 0,
finished: true
}))
));
// use Again on the preview
col.answer_card(&mut CardAnswer {
card_id: c.id,
current_state: next.current,
new_state: next.again,
rating: Rating::Again,
answered_at: TimestampMillis::now(),
milliseconds_taken: 0,
custom_data: None,
from_queue: true,
})?;
c = col.storage.get_card(c.id)?.unwrap();
assert_eq!(c.queue, CardQueue::PreviewRepeat);
// hard
let next = col.get_scheduling_states(c.id)?;
col.answer_card(&mut CardAnswer {
card_id: c.id,
current_state: next.current,
new_state: next.hard,
rating: Rating::Hard,
answered_at: TimestampMillis::now(),
milliseconds_taken: 0,
custom_data: None,
from_queue: true,
})?;
c = col.storage.get_card(c.id)?.unwrap();
assert_eq!(c.queue, CardQueue::PreviewRepeat);
// and then it should return to its old state once good or easy selected,
// with the default filtered config
let next = col.get_scheduling_states(c.id)?;
col.answer_card(&mut CardAnswer {
card_id: c.id,
current_state: next.current,
new_state: next.good,
rating: Rating::Good,
answered_at: TimestampMillis::now(),
milliseconds_taken: 0,
custom_data: None,
from_queue: true,
})?;
c = col.storage.get_card(c.id)?.unwrap();
assert_eq!(c.queue, CardQueue::DayLearn);
assert_eq!(c.due, 123);
Ok(())
}
}