mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
Fix panic when enabling FSRS with add-on-rescheduled cards
https://forums.ankiweb.net/t/error-upon-fsrs-activation-on-anki-23-10/36488
This commit is contained in:
parent
613a75773d
commit
987c1825a6
2 changed files with 48 additions and 28 deletions
|
@ -12,7 +12,6 @@ use itertools::Itertools;
|
||||||
use crate::card::CardType;
|
use crate::card::CardType;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::revlog::RevlogEntry;
|
use crate::revlog::RevlogEntry;
|
||||||
use crate::revlog::RevlogReviewKind;
|
|
||||||
use crate::scheduler::fsrs::weights::single_card_revlog_to_items;
|
use crate::scheduler::fsrs::weights::single_card_revlog_to_items;
|
||||||
use crate::scheduler::fsrs::weights::Weights;
|
use crate::scheduler::fsrs::weights::Weights;
|
||||||
use crate::scheduler::states::fuzz::with_review_fuzz;
|
use crate::scheduler::states::fuzz::with_review_fuzz;
|
||||||
|
@ -235,27 +234,39 @@ pub(crate) fn single_card_revlog_to_item(
|
||||||
next_day_at: TimestampSecs,
|
next_day_at: TimestampSecs,
|
||||||
sm2_retention: f32,
|
sm2_retention: f32,
|
||||||
) -> Option<FsrsItemWithStartingState> {
|
) -> Option<FsrsItemWithStartingState> {
|
||||||
let have_learning = entries
|
struct FirstReview {
|
||||||
|
interval: f32,
|
||||||
|
ease_factor: f32,
|
||||||
|
}
|
||||||
|
let first_review = entries
|
||||||
.iter()
|
.iter()
|
||||||
.any(|e| e.review_kind == RevlogReviewKind::Learning);
|
.find(|e| e.button_chosen > 0)
|
||||||
if have_learning {
|
.map(|e| FirstReview {
|
||||||
let items = single_card_revlog_to_items(entries, next_day_at, false);
|
interval: e.interval.max(1) as f32,
|
||||||
Some(FsrsItemWithStartingState {
|
ease_factor: if e.ease_factor == 0 {
|
||||||
item: items.unwrap().pop().unwrap(),
|
|
||||||
starting_state: None,
|
|
||||||
})
|
|
||||||
} else if let Some(first_review) = entries.iter().find(|e| e.button_chosen > 0) {
|
|
||||||
let ease_factor = if first_review.ease_factor == 0 {
|
|
||||||
2500
|
2500
|
||||||
} else {
|
} else {
|
||||||
first_review.ease_factor
|
e.ease_factor
|
||||||
};
|
} as f32
|
||||||
let interval = first_review.interval.max(1);
|
/ 1000.0,
|
||||||
let starting_state =
|
});
|
||||||
fsrs.memory_state_from_sm2(ease_factor as f32 / 1000.0, interval as f32, sm2_retention);
|
if let Some((mut items, found_learning)) =
|
||||||
let items = single_card_revlog_to_items(entries, next_day_at, false);
|
single_card_revlog_to_items(entries, next_day_at, false)
|
||||||
items.and_then(|mut items| {
|
{
|
||||||
let mut item = items.pop().unwrap();
|
let mut item = items.pop().unwrap();
|
||||||
|
if found_learning {
|
||||||
|
// we assume the revlog is complete
|
||||||
|
Some(FsrsItemWithStartingState {
|
||||||
|
item,
|
||||||
|
starting_state: None,
|
||||||
|
})
|
||||||
|
} else if let Some(first_review) = first_review {
|
||||||
|
// the revlog has been truncated, but not fully
|
||||||
|
let starting_state = fsrs.memory_state_from_sm2(
|
||||||
|
first_review.ease_factor,
|
||||||
|
first_review.interval,
|
||||||
|
sm2_retention,
|
||||||
|
);
|
||||||
item.reviews.remove(0);
|
item.reviews.remove(0);
|
||||||
if item.reviews.is_empty() {
|
if item.reviews.is_empty() {
|
||||||
None
|
None
|
||||||
|
@ -265,7 +276,10 @@ pub(crate) fn single_card_revlog_to_item(
|
||||||
starting_state: Some(starting_state),
|
starting_state: Some(starting_state),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
|
// only manual rescheduling; treat like empty
|
||||||
|
None
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ fn fsrs_items_for_training(revlogs: Vec<RevlogEntry>, next_day_at: TimestampSecs
|
||||||
.filter_map(|(_cid, entries)| {
|
.filter_map(|(_cid, entries)| {
|
||||||
single_card_revlog_to_items(entries.collect(), next_day_at, true)
|
single_card_revlog_to_items(entries.collect(), next_day_at, true)
|
||||||
})
|
})
|
||||||
.flatten()
|
.flat_map(|i| i.0)
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
revlogs.sort_by_cached_key(|r| r.reviews.len());
|
revlogs.sort_by_cached_key(|r| r.reviews.len());
|
||||||
revlogs
|
revlogs
|
||||||
|
@ -111,16 +111,22 @@ fn fsrs_items_for_training(revlogs: Vec<RevlogEntry>, next_day_at: TimestampSecs
|
||||||
/// expects multiple items for a given card when training - for revlog
|
/// expects multiple items for a given card when training - for revlog
|
||||||
/// `[1,2,3]`, we create FSRSItems corresponding to `[1,2]` and `[1,2,3]`
|
/// `[1,2,3]`, we create FSRSItems corresponding to `[1,2]` and `[1,2,3]`
|
||||||
/// in training, and `[1]`, [1,2]` and `[1,2,3]` when calculating memory
|
/// in training, and `[1]`, [1,2]` and `[1,2,3]` when calculating memory
|
||||||
/// state.
|
/// state. Returns (items, found_learn_entry), the latter of which is used
|
||||||
|
/// to determine whether the revlogs have been truncated when not training.
|
||||||
pub(crate) fn single_card_revlog_to_items(
|
pub(crate) fn single_card_revlog_to_items(
|
||||||
mut entries: Vec<RevlogEntry>,
|
mut entries: Vec<RevlogEntry>,
|
||||||
next_day_at: TimestampSecs,
|
next_day_at: TimestampSecs,
|
||||||
training: bool,
|
training: bool,
|
||||||
) -> Option<Vec<FSRSItem>> {
|
) -> Option<(Vec<FSRSItem>, bool)> {
|
||||||
let mut last_learn_entry = None;
|
let mut last_learn_entry = None;
|
||||||
|
let mut found_learn_entry = false;
|
||||||
for (index, entry) in entries.iter().enumerate().rev() {
|
for (index, entry) in entries.iter().enumerate().rev() {
|
||||||
if entry.review_kind == RevlogReviewKind::Learning {
|
if matches!(
|
||||||
|
(entry.review_kind, entry.button_chosen),
|
||||||
|
(RevlogReviewKind::Learning, 1..=4)
|
||||||
|
) {
|
||||||
last_learn_entry = Some(index);
|
last_learn_entry = Some(index);
|
||||||
|
found_learn_entry = true;
|
||||||
} else if last_learn_entry.is_some() {
|
} else if last_learn_entry.is_some() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -199,7 +205,7 @@ pub(crate) fn single_card_revlog_to_items(
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(items)
|
Some((items, found_learn_entry))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +235,7 @@ pub(crate) mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn convert(revlog: &[RevlogEntry], training: bool) -> Option<Vec<FSRSItem>> {
|
pub(crate) fn convert(revlog: &[RevlogEntry], training: bool) -> Option<Vec<FSRSItem>> {
|
||||||
single_card_revlog_to_items(revlog.to_vec(), NEXT_DAY_AT, training)
|
single_card_revlog_to_items(revlog.to_vec(), NEXT_DAY_AT, training).map(|i| i.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
Loading…
Reference in a new issue