Convert FSRS to a global option

Allowing some decks to be FSRS and some SM-2 will lead to confusing
behavior when sorting on SM-2 or FSRS-specific fields, or when moving
cards between decks.
This commit is contained in:
Damien Elmes 2023-09-23 14:41:28 +10:00
parent 0071094e6c
commit c78de23cf9
11 changed files with 27 additions and 24 deletions

View file

@ -135,7 +135,6 @@ message DeckConfig {
bool bury_reviews = 28;
bool bury_interday_learning = 29;
bool fsrs_enabled = 36;
float desired_retention = 37; // for fsrs
bytes other = 255;
@ -179,6 +178,7 @@ message DeckConfigsForUpdate {
string card_state_customizer = 6;
// only applies to v3 scheduler
bool new_cards_ignore_review_limit = 7;
bool fsrs = 8;
}
message UpdateDeckConfigsRequest {
@ -191,4 +191,5 @@ message UpdateDeckConfigsRequest {
string card_state_customizer = 5;
DeckConfigsForUpdate.CurrentDeck.Limits limits = 6;
bool new_cards_ignore_review_limit = 7;
bool fsrs = 8;
}

View file

@ -37,6 +37,7 @@ pub enum BoolKey {
MergeNotetypes,
WithScheduling,
StopTimerOnAnswer,
Fsrs,
#[strum(to_string = "normalize_note_text")]
NormalizeNoteText,

View file

@ -66,7 +66,6 @@ const DEFAULT_DECK_CONFIG_INNER: DeckConfigInner = DeckConfigInner {
bury_new: false,
bury_reviews: false,
bury_interday_learning: false,
fsrs_enabled: false,
fsrs_weights: vec![],
desired_retention: 0.9,
other: Vec::new(),

View file

@ -68,8 +68,6 @@ pub struct DeckConfSchema11 {
#[serde(default)]
fsrs_weights: Vec<f32>,
#[serde(default)]
fsrs_enabled: bool,
#[serde(default)]
desired_retention: f32,
#[serde(flatten)]
@ -258,7 +256,6 @@ impl Default for DeckConfSchema11 {
new_gather_priority: 0,
bury_interday_learning: false,
fsrs_weights: vec![],
fsrs_enabled: false,
desired_retention: 0.9,
}
}
@ -329,7 +326,6 @@ impl From<DeckConfSchema11> for DeckConfig {
bury_reviews: c.rev.bury,
bury_interday_learning: c.bury_interday_learning,
fsrs_weights: c.fsrs_weights,
fsrs_enabled: c.fsrs_enabled,
desired_retention: c.desired_retention,
other: other_bytes,
},
@ -423,7 +419,6 @@ impl From<DeckConfig> for DeckConfSchema11 {
new_gather_priority: i.new_card_gather_priority,
bury_interday_learning: i.bury_interday_learning,
fsrs_weights: i.fsrs_weights,
fsrs_enabled: i.fsrs_enabled,
desired_retention: i.desired_retention,
}
}
@ -448,7 +443,6 @@ static RESERVED_DECKCONF_KEYS: Set<&'static str> = phf_set! {
"newGatherPriority",
"fsrsWeights",
"desiredRetention",
"fsrsEnabled",
};
static RESERVED_DECKCONF_NEW_KEYS: Set<&'static str> = phf_set! {

View file

@ -102,6 +102,7 @@ impl From<anki_proto::deck_config::UpdateDeckConfigsRequest> for UpdateDeckConfi
card_state_customizer: c.card_state_customizer,
limits: c.limits.unwrap_or_default(),
new_cards_ignore_review_limit: c.new_cards_ignore_review_limit,
fsrs: c.fsrs,
}
}
}

View file

@ -29,6 +29,7 @@ pub struct UpdateDeckConfigsRequest {
pub card_state_customizer: String,
pub limits: Limits,
pub new_cards_ignore_review_limit: bool,
pub fsrs: bool,
}
impl Collection {
@ -48,6 +49,7 @@ impl Collection {
v3_scheduler: self.get_config_bool(BoolKey::Sched2021),
card_state_customizer: self.get_config_string(StringKey::CardStateCustomizer),
new_cards_ignore_review_limit: self.get_config_bool(BoolKey::NewCardsIgnoreReviewLimit),
fsrs: self.get_config_bool(BoolKey::Fsrs),
})
}
@ -161,6 +163,10 @@ impl Collection {
let selected_config = input.configs.last().unwrap();
let mut decks_needing_memory_recompute: HashMap<DeckConfigId, Vec<SearchNode>> =
Default::default();
let fsrs_toggled = self.get_config_bool(BoolKey::Fsrs) != input.fsrs;
if fsrs_toggled {
self.set_config_bool_inner(BoolKey::Fsrs, input.fsrs)?;
}
for deck in self.storage.get_all_decks()? {
if let Ok(normal) = deck.normal() {
let deck_id = deck.id;
@ -171,9 +177,6 @@ impl Collection {
let previous_order = previous_config
.map(|c| c.inner.new_card_insert_order())
.unwrap_or_default();
let previous_fsrs_on = previous_config
.map(|c| c.inner.fsrs_enabled)
.unwrap_or_default();
let previous_weights = previous_config.map(|c| &c.inner.fsrs_weights);
// if a selected (sub)deck, or its old config was removed, update deck to point
@ -200,11 +203,8 @@ impl Collection {
}
// if weights differ, memory state needs to be recomputed
let current_fsrs_on = current_config
.map(|c| c.inner.fsrs_enabled)
.unwrap_or_default();
let current_weights = current_config.map(|c| &c.inner.fsrs_weights);
if current_fsrs_on != previous_fsrs_on || previous_weights != current_weights {
if fsrs_toggled || previous_weights != current_weights {
decks_needing_memory_recompute
.entry(current_config_id)
.or_default()
@ -220,7 +220,7 @@ impl Collection {
.into_iter()
.map(|(conf_id, search)| {
let weights = configs_after_update.get(&conf_id).and_then(|c| {
if c.inner.fsrs_enabled {
if input.fsrs {
Some(c.inner.fsrs_weights.clone())
} else {
None
@ -365,6 +365,7 @@ mod test {
card_state_customizer: "".to_string(),
limits: Limits::default(),
new_cards_ignore_review_limit: false,
fsrs: false,
};
assert!(!col.update_deck_configs(input.clone())?.changes.had_change());

View file

@ -351,7 +351,7 @@ impl Collection {
.get_deck(card.deck_id)?
.or_not_found(card.deck_id)?;
let config = self.home_deck_config(deck.config_id(), card.original_deck_id)?;
let fsrs_next_states = if config.inner.fsrs_enabled {
let fsrs_next_states = if self.get_config_bool(BoolKey::Fsrs) {
let fsrs = FSRS::new(Some(&config.inner.fsrs_weights))?;
let memory_state = if let Some(state) = card.memory_state {
Some(MemoryState::from(state))

View file

@ -28,6 +28,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const config = state.currentConfig;
const defaults = state.defaults;
const cardStateCustomizer = state.cardStateCustomizer;
const fsrs = state.fsrs;
const settings = {
maximumInterval: {
@ -76,7 +77,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
carousel.to(index);
}
$: fsrsClientWarning = $config.fsrsEnabled ? tr.deckConfigFsrsOnAllClients() : "";
$: fsrsClientWarning = $fsrs ? tr.deckConfigFsrsOnAllClients() : "";
</script>
<TitledContainer title={tr.deckConfigAdvancedTitle()}>
@ -93,7 +94,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<DynamicallySlottable slotHost={Item} {api}>
{#if state.v3Scheduler}
<Item>
<SwitchRow bind:value={$config.fsrsEnabled} defaultValue={false}>
<SwitchRow bind:value={$fsrs} defaultValue={false}>
<SettingTitle>FSRS</SettingTitle>
</SwitchRow>
</Item>
@ -117,7 +118,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</SpinBoxRow>
</Item>
{#if !$config.fsrsEnabled || !state.v3Scheduler}
{#if !$fsrs || !state.v3Scheduler}
<Item>
<SpinBoxFloatRow
bind:value={$config.initialEase}

View file

@ -26,6 +26,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const config = state.currentConfig;
const defaults = state.defaults;
const fsrs = state.fsrs;
let stepsExceedMinimumInterval: string;
let stepsTooLargeForFsrs: string;
@ -38,7 +39,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
? tr.deckConfigRelearningStepsAboveMinimumInterval()
: "";
stepsTooLargeForFsrs =
$config.fsrsEnabled && lastRelearnStepInDays >= 1
$fsrs && lastRelearnStepInDays >= 1
? tr.deckConfigStepsTooLargeForFsrs()
: "";
}
@ -106,7 +107,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<Warning warning={stepsTooLargeForFsrs} />
</Item>
{#if !$config.fsrsEnabled}
{#if !$fsrs}
<Item>
<SpinBoxRow
bind:value={$config.minimumLapseInterval}

View file

@ -27,6 +27,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const config = state.currentConfig;
const defaults = state.defaults;
const fsrs = state.fsrs;
let stepsExceedGraduatingInterval: string;
let stepsTooLargeForFsrs: string;
@ -39,7 +40,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
? tr.deckConfigLearningStepAboveGraduatingInterval()
: "";
stepsTooLargeForFsrs =
$config.fsrsEnabled && lastLearnStepInDays >= 1
$fsrs && lastLearnStepInDays >= 1
? tr.deckConfigStepsTooLargeForFsrs()
: "";
}
@ -118,7 +119,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<Warning warning={stepsTooLargeForFsrs} />
</Item>
{#if !$config.fsrsEnabled}
{#if !$fsrs}
<Item>
<SpinBoxRow
bind:value={$config.graduatingIntervalGood}

View file

@ -48,6 +48,7 @@ export class DeckOptionsState {
readonly addonComponents: Writable<DynamicSvelteComponent[]>;
readonly v3Scheduler: boolean;
readonly newCardsIgnoreReviewLimit: Writable<boolean>;
readonly fsrs: Writable<boolean>;
private targetDeckId: DeckOptionsId;
private configs: ConfigWithCount[];
@ -79,6 +80,7 @@ export class DeckOptionsState {
this.cardStateCustomizer = writable(data.cardStateCustomizer);
this.deckLimits = writable(data.currentDeck?.limits ?? createLimits());
this.newCardsIgnoreReviewLimit = writable(data.newCardsIgnoreReviewLimit);
this.fsrs = writable(data.fsrs);
// decrement the use count of the starting item, as we'll apply +1 to currently
// selected one at display time
@ -205,6 +207,7 @@ export class DeckOptionsState {
cardStateCustomizer: get(this.cardStateCustomizer),
limits: get(this.deckLimits),
newCardsIgnoreReviewLimit: get(this.newCardsIgnoreReviewLimit),
fsrs: get(this.fsrs),
};
}