From 82a9fa3331bc60ee7309a325c2eaa2e6a43e2fe9 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 15 Jul 2025 14:12:37 +0100 Subject: [PATCH] Feat/Use cached workload values --- proto/anki/deck_config.proto | 4 +-- rslib/src/deckconfig/service.rs | 44 +++++++++++------------ ts/routes/deck-options/FsrsOptions.svelte | 42 +++++++++++----------- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/proto/anki/deck_config.proto b/proto/anki/deck_config.proto index 9dae49c6a..0d03a1435 100644 --- a/proto/anki/deck_config.proto +++ b/proto/anki/deck_config.proto @@ -40,12 +40,10 @@ message DeckConfigId { message GetRetentionWorkloadRequest { repeated float w = 1; string search = 2; - float before = 3; - float after = 4; } message GetRetentionWorkloadResponse { - float factor = 1; + map costs = 1; } message GetIgnoredBeforeCountRequest { diff --git a/rslib/src/deckconfig/service.rs b/rslib/src/deckconfig/service.rs index bc6bce8f4..7a866c108 100644 --- a/rslib/src/deckconfig/service.rs +++ b/rslib/src/deckconfig/service.rs @@ -1,5 +1,8 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use std::collections::HashMap; + use anki_proto::generic; use crate::collection::Collection; @@ -139,30 +142,25 @@ impl crate::services::DeckConfigService for Collection { costs.pass_count, ); - let before = fsrs::expected_workload( - &input.w, - input.before, - LEARN_SPAN, - cost_success, - cost_failure, - cost_learn, - initial_pass_rate, - TERMINATION_PROB, - )?; - let after = fsrs::expected_workload( - &input.w, - input.after, - LEARN_SPAN, - cost_success, - cost_failure, - cost_learn, - initial_pass_rate, - TERMINATION_PROB, - )?; + let costs = (70u32..=99u32) + .map(|dr| { + Ok(( + dr, + fsrs::expected_workload( + &input.w, + dr as f32 / 100., + LEARN_SPAN, + cost_success, + cost_failure, + cost_learn, + initial_pass_rate, + TERMINATION_PROB, + )?, + )) + }) + .collect::>>()?; - Ok(anki_proto::deck_config::GetRetentionWorkloadResponse { - factor: after / before, - }) + Ok(anki_proto::deck_config::GetRetentionWorkloadResponse { costs }) } } diff --git a/ts/routes/deck-options/FsrsOptions.svelte b/ts/routes/deck-options/FsrsOptions.svelte index cfdea341c..a2f447d35 100644 --- a/ts/routes/deck-options/FsrsOptions.svelte +++ b/ts/routes/deck-options/FsrsOptions.svelte @@ -29,6 +29,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import SimulatorModal from "./SimulatorModal.svelte"; import { GetRetentionWorkloadRequest, + type GetRetentionWorkloadResponse, UpdateDeckConfigsMode, } from "@generated/anki/deck_config_pb"; import type Modal from "bootstrap/js/dist/modal"; @@ -66,19 +67,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html $: roundedRetention = Number($config.desiredRetention.toFixed(2)); $: desiredRetentionWarning = getRetentionLongShortWarning(roundedRetention); - let timeoutId: ReturnType | undefined = undefined; - const WORKLOAD_UPDATE_DELAY_MS = 100; - let desiredRetentionChangeInfo = ""; $: { - clearTimeout(timeoutId); - if (showDesiredRetentionTooltip) { - timeoutId = setTimeout(() => { - getRetentionChangeInfo(roundedRetention, fsrsParams($config)); - }, WORKLOAD_UPDATE_DELAY_MS); - } else { - desiredRetentionChangeInfo = ""; - } + showDesiredRetentionTooltip + ? getRetentionChangeInfo(roundedRetention, fsrsParams($config)) + : ""; } $: retentionWarningClass = getRetentionWarningClass(roundedRetention); @@ -111,21 +104,30 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } } + let retentionWorloadInfo: undefined | Promise = + undefined; + async function getRetentionChangeInfo(retention: number, params: number[]) { + if (!retentionWorloadInfo) { + const request = new GetRetentionWorkloadRequest({ + w: params, + search: defaultparamSearch, + }); + retentionWorloadInfo = getRetentionWorkload(request); + } if (+startingDesiredRetention == roundedRetention) { desiredRetentionChangeInfo = tr.deckConfigWorkloadFactorUnchanged(); return; } - const request = new GetRetentionWorkloadRequest({ - w: params, - search: defaultparamSearch, - before: +startingDesiredRetention, - after: retention, - }); - const resp = await getRetentionWorkload(request); + + const previous = +startingDesiredRetention * 100; + const after = retention * 100; + const resp = await retentionWorloadInfo; + const factor = resp.costs[after] / resp.costs[previous]; + desiredRetentionChangeInfo = tr.deckConfigWorkloadFactorChange({ - factor: resp.factor.toFixed(2), - previousDr: (+startingDesiredRetention * 100).toString(), + factor: factor.toFixed(2), + previousDr: previous.toString(), }); }