Working repetitions
This commit is contained in:
1 parent
242b56fc21
commit
2e04450c76
7 files changed
+163
-260
No files matched your search
+74
-94
@@ -1,23 +1,23 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use chrono::Utc;
|
||||
use crate::app::AppState;
|
||||
use crate::core::srs::OrderModule::{Fsrs, RandomSRS, SemiRandomSRS, WorstWordsSRS};
|
||||
use crate::core::srs::{
|
||||
OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule,
|
||||
FsrsModule, OrderMode, OrderModule, RandomSRSModule, SRSModule, SemiRandomSRSModule,
|
||||
WordOpenMode, WorstWordsSRSModule,
|
||||
};
|
||||
use crate::data::entities::*;
|
||||
use crate::data_provider::card_states::{CardStateRepositoryInner};
|
||||
use crate::notification::{MaybeError, NotificationData};
|
||||
use crate::data::repositories::Repository;
|
||||
use crate::data_provider::card_states::CardStateRepositoryInner;
|
||||
use crate::notification::MaybeError;
|
||||
use chrono::Utc;
|
||||
use log::info;
|
||||
use rhai::{Engine, Scope};
|
||||
use crate::data::repositories::Repository;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DeckData {
|
||||
pub(crate) words: Vec<EntityHandle<WordData>>,
|
||||
pub(crate) set: Vec<EntityHandle<CardState>>,
|
||||
pub(crate) states: Vec<EntityHandle<CardState>>,
|
||||
current_word_index: Option<usize>,
|
||||
state: AppState,
|
||||
order_module: OrderModule,
|
||||
@@ -25,80 +25,58 @@ pub struct DeckData {
|
||||
settings: DeckSettings,
|
||||
}
|
||||
impl DeckData {
|
||||
pub fn new(
|
||||
settings: &DeckSettings,
|
||||
state: AppState,
|
||||
) -> Result<Self, NotificationData> {
|
||||
todo!()
|
||||
// let state_for = state.clone();
|
||||
//
|
||||
// let stats = state (settings, &state.connection.read())?;
|
||||
// let words = stats
|
||||
// .iter()
|
||||
// .map(|stat| {
|
||||
// state
|
||||
// .dictionary
|
||||
// .binary_search_by_key(&stat.word_id, |w| w.id)
|
||||
// .expect("No word found in dictionary")
|
||||
// })
|
||||
// .map(|index| &state.dictionary[index])
|
||||
// .cloned()
|
||||
// .collect();
|
||||
//
|
||||
// Ok(Self {
|
||||
// set: stats,
|
||||
// words,
|
||||
// current_word_index: None,
|
||||
// state: state_for,
|
||||
// order_module: Fsrs(FsrsModule::new()),
|
||||
// open_mode: settings.open_mode,
|
||||
// settings: settings.clone(),
|
||||
// })
|
||||
pub fn new(settings: &DeckSettings, open_mode: OrderMode, state: AppState) -> Self {
|
||||
let states_repo = state.card_states.read();
|
||||
let mut query = states_repo.deck_states(settings.id);
|
||||
let words = query
|
||||
.iter(&states_repo)
|
||||
.map(|card_state| EntityHandle::from_id(card_state.word_id))
|
||||
.collect();
|
||||
let states = query.handles().collect::<Vec<EntityHandle<CardState>>>();
|
||||
let fsrs = Fsrs(FsrsModule::new(Self::set_links(states.as_slice(), &states_repo).as_slice()));
|
||||
drop(states_repo);
|
||||
Self {
|
||||
states,
|
||||
words,
|
||||
current_word_index: None,
|
||||
state,
|
||||
order_module: fsrs,
|
||||
open_mode,
|
||||
settings: settings.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> (EntityHandle<WordData>, EntityHandle<CardState>) {
|
||||
if self.fsrs_finished() {
|
||||
self.switch_order_engine()
|
||||
}
|
||||
let repo = self.state.card_states.read();
|
||||
let links = Self::set_links(self.set.as_slice(), &repo);
|
||||
let links = Self::set_links(self.states.as_slice(), &repo);
|
||||
let index = match self.order_module {
|
||||
SemiRandomSRS(ref mut module) => {
|
||||
if !module.initialized {
|
||||
module.init(links.as_slice());
|
||||
}
|
||||
module.next(links.as_slice())
|
||||
}
|
||||
SemiRandomSRS(ref mut module) => module.next(links.as_slice()),
|
||||
|
||||
RandomSRS(ref mut module) => {
|
||||
if !module.initialized {
|
||||
module.init(links.as_slice())
|
||||
}
|
||||
module.next(links.as_slice())
|
||||
}
|
||||
WorstWordsSRS(ref mut module) => {
|
||||
if !module.initialized {
|
||||
module.init(links.as_slice())
|
||||
}
|
||||
module.next(links.as_slice())
|
||||
}
|
||||
Fsrs(ref mut module) => {
|
||||
if !module.initialized {
|
||||
module.init(links.as_slice())
|
||||
}
|
||||
module.next(links.as_slice())
|
||||
}
|
||||
RandomSRS(ref mut module) => module.next(links.as_slice()),
|
||||
WorstWordsSRS(ref mut module) => module.next(links.as_slice()),
|
||||
Fsrs(ref mut module) => module.next(links.as_slice()),
|
||||
};
|
||||
|
||||
self.current_word_index = Some(index);
|
||||
(self.words[index].clone(), self.set[index].clone())
|
||||
(self.words[index].clone(), self.states[index].clone())
|
||||
}
|
||||
|
||||
fn set_links<'a>(set: &[EntityHandle<CardState>], repo: &'a CardStateRepositoryInner) -> Vec<&'a CardState> {
|
||||
set.iter().map(|h| repo.get_ref_static(*h).unwrap()).collect::<Vec<_>>()
|
||||
fn set_links<'a>(
|
||||
set: &[EntityHandle<CardState>],
|
||||
repo: &'a CardStateRepositoryInner,
|
||||
) -> Vec<&'a CardState> {
|
||||
set.iter()
|
||||
.map(|h| repo.get_ref_static(*h).unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn open(&mut self, status: WordOpenMode) -> MaybeError {
|
||||
if let Some(index) = self.current_word_index {
|
||||
let mut repo = self.state.card_states.write();
|
||||
let card_state_handle = &mut self.set[index];
|
||||
let card_state_handle = &mut self.states[index];
|
||||
let card_state = repo.get_mut(card_state_handle).unwrap();
|
||||
let old_score = card_state.score;
|
||||
card_state.update(status);
|
||||
@@ -121,16 +99,6 @@ impl DeckData {
|
||||
}
|
||||
}
|
||||
|
||||
if let Fsrs(ref fsrs) = self.order_module
|
||||
&& fsrs.finished()
|
||||
{
|
||||
self.order_module = match self.open_mode {
|
||||
OrderMode::Default => SemiRandomSRS(SemiRandomSRSModule::new()),
|
||||
OrderMode::TrainWorstFirst => WorstWordsSRS(WorstWordsSRSModule::new()),
|
||||
OrderMode::FullRandom => RandomSRS(RandomSRSModule::new()),
|
||||
}
|
||||
}
|
||||
|
||||
let word_id = card_state.word_id;
|
||||
repo.save(card_state_handle)?;
|
||||
|
||||
@@ -145,23 +113,34 @@ impl DeckData {
|
||||
after: new_score,
|
||||
fsrs,
|
||||
})?;
|
||||
todo!("Implement history pushing")
|
||||
// let _ = push_note(
|
||||
// self.settings.id,
|
||||
// HistoryNote {
|
||||
// timestamp: Utc::now(),
|
||||
// word_id: word.word_id,
|
||||
// answer: status,
|
||||
// before: old_score,
|
||||
// after: new_score,
|
||||
// },
|
||||
// );
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn switch_order_engine(&mut self) {
|
||||
let repo = self.state.card_states.read();
|
||||
let links = Self::set_links(self.states.as_slice(), &repo);
|
||||
self.order_module = match self.open_mode {
|
||||
OrderMode::Default => SemiRandomSRS(SemiRandomSRSModule::new(links.as_slice())),
|
||||
OrderMode::TrainWorstFirst => WorstWordsSRS(WorstWordsSRSModule::new(links.as_slice())),
|
||||
OrderMode::FullRandom => RandomSRS(RandomSRSModule::new(links.as_slice())),
|
||||
}
|
||||
}
|
||||
|
||||
fn fsrs_finished(&self) -> bool {
|
||||
if let Fsrs(ref fsrs) = self.order_module
|
||||
&& fsrs.finished()
|
||||
{
|
||||
info!("Fsrs finished. Infinite mode enabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.set.len()
|
||||
self.states.len()
|
||||
}
|
||||
|
||||
pub fn list(deck: &DeckSettings, state: &AppState) -> Vec<EntityHandle<WordData>> {
|
||||
@@ -179,17 +158,18 @@ impl DeckData {
|
||||
more.insert(iced.0.clone().into(), iced.1.clone().into());
|
||||
}
|
||||
let mut scope = Scope::new();
|
||||
let group_name = groups[groups.binary_search_by_key(&word.group_id, |g| g.id).unwrap()].name.clone();
|
||||
let group_name = groups[groups
|
||||
.binary_search_by_key(&word.group_id, |g| g.id)
|
||||
.unwrap()]
|
||||
.name
|
||||
.clone();
|
||||
scope
|
||||
.push_constant("id", word.id.0 as i64)
|
||||
.push_constant("key", word.key.clone())
|
||||
.push_constant("value", word.value.clone())
|
||||
.push_constant("tags", word.tags.clone())
|
||||
.push_constant("more", more)
|
||||
.push_constant(
|
||||
"group",
|
||||
group_name,
|
||||
);
|
||||
.push_constant("group", group_name);
|
||||
|
||||
let result = engine.eval_ast_with_scope::<bool>(&mut scope, &ast);
|
||||
if let Ok(result) = result {
|
||||
@@ -199,7 +179,7 @@ impl DeckData {
|
||||
}));
|
||||
|
||||
info!("Collecting available words is {:?}", time.elapsed());
|
||||
let handles =query.handles().collect();
|
||||
let handles = query.handles().collect();
|
||||
drop(query);
|
||||
handles
|
||||
} else {
|
||||
|
||||
+50
-66
@@ -1,6 +1,7 @@
|
||||
use crate::data::entities::CardState;
|
||||
use chrono::{Local, TimeDelta};
|
||||
use fsrs::FSRS;
|
||||
use log::{debug, info};
|
||||
use rand::distr::weighted::WeightedIndex;
|
||||
use rand::prelude::ThreadRng;
|
||||
use rand::prelude::{Distribution, SliceRandom};
|
||||
@@ -31,24 +32,23 @@ pub enum OrderModule {
|
||||
Fsrs(FsrsModule),
|
||||
}
|
||||
pub trait SRSModule {
|
||||
fn new(set: &[&CardState]) -> Self;
|
||||
fn next(&mut self, set: &[&CardState]) -> usize;
|
||||
fn open(&mut self, status: WordOpenMode, index: usize, updated_word: &mut CardState);
|
||||
fn init(&mut self, set: &[&CardState]);
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct RandomSRSModule {
|
||||
basket: Vec<usize>,
|
||||
pub(crate) initialized: bool,
|
||||
}
|
||||
impl RandomSRSModule {
|
||||
pub(crate) fn new() -> RandomSRSModule {
|
||||
Self {
|
||||
basket: vec![],
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SRSModule for RandomSRSModule {
|
||||
fn new(set: &[&CardState]) -> Self {
|
||||
let mut basket = (0..set.len()).collect::<Vec<usize>>();
|
||||
basket.shuffle(&mut rand::rng());
|
||||
Self{
|
||||
basket,
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self, set: &[&CardState]) -> usize {
|
||||
match self.basket.pop() {
|
||||
Some(index) => index,
|
||||
@@ -61,29 +61,12 @@ impl SRSModule for RandomSRSModule {
|
||||
}
|
||||
|
||||
fn open(&mut self, _: WordOpenMode, _: usize, _: &mut CardState) {}
|
||||
|
||||
fn init(&mut self, set: &[&CardState]) {
|
||||
self.initialized = true;
|
||||
self.basket = (0..set.len()).collect::<Vec<usize>>();
|
||||
self.basket.shuffle(&mut rand::rng())
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct SemiRandomSRSModule {
|
||||
history: Vec<usize>,
|
||||
last_weights: WeightedIndex<f32>,
|
||||
generator: ThreadRng,
|
||||
pub(crate) initialized: bool,
|
||||
}
|
||||
impl SemiRandomSRSModule {
|
||||
pub(crate) fn new() -> SemiRandomSRSModule {
|
||||
SemiRandomSRSModule {
|
||||
history: vec![],
|
||||
last_weights: WeightedIndex::new([1.0]).unwrap(),
|
||||
generator: rng(),
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SRSModule for SemiRandomSRSModule {
|
||||
fn next(&mut self, set: &[&CardState]) -> usize {
|
||||
@@ -106,13 +89,18 @@ impl SRSModule for SemiRandomSRSModule {
|
||||
.update_weights(&[(index, &new_weight)])
|
||||
.expect("update weights error");
|
||||
}
|
||||
fn init(&mut self, set: &[&CardState]) {
|
||||
self.initialized = true;
|
||||
fn new(set: &[&CardState]) -> Self {
|
||||
|
||||
let weights = set
|
||||
.iter()
|
||||
.map(|s| (100.0 / s.calculated_score()).powf(2.0) * 2.0)
|
||||
.collect::<Vec<f32>>();
|
||||
self.last_weights = WeightedIndex::new(weights).expect("Incorrect weights update");
|
||||
|
||||
Self{
|
||||
generator: rng(),
|
||||
last_weights: WeightedIndex::new(weights).expect("Incorrect weights update"),
|
||||
history: vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SemiRandomSRSModule {
|
||||
@@ -122,7 +110,6 @@ impl SemiRandomSRSModule {
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct WorstWordsSRSModule {
|
||||
pub(crate) initialized: bool,
|
||||
pool_size: usize,
|
||||
rounds_remaining: u8,
|
||||
pool: Vec<usize>,
|
||||
@@ -148,14 +135,8 @@ impl SRSModule for WorstWordsSRSModule {
|
||||
|
||||
fn open(&mut self, _: WordOpenMode, _: usize, _: &mut CardState) {}
|
||||
|
||||
fn init(&mut self, _: &[&CardState]) {
|
||||
self.initialized = true;
|
||||
}
|
||||
}
|
||||
impl WorstWordsSRSModule {
|
||||
pub(crate) fn new() -> WorstWordsSRSModule {
|
||||
fn new(_: &[&CardState]) -> Self {
|
||||
WorstWordsSRSModule {
|
||||
initialized: false,
|
||||
pool_size: PARTIAL_POOL_SIZE,
|
||||
rounds_count: 2,
|
||||
rounds_remaining: 0,
|
||||
@@ -163,6 +144,8 @@ impl WorstWordsSRSModule {
|
||||
queue: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
impl WorstWordsSRSModule {
|
||||
fn fill_pool(&mut self, set: &[&CardState]) {
|
||||
let mut sorted = set.iter().enumerate().collect::<Vec<_>>();
|
||||
sorted.sort_by_key(|c| c.1.score);
|
||||
@@ -183,10 +166,31 @@ pub struct FsrsModule {
|
||||
queue: Vec<usize>,
|
||||
rng: ThreadRng,
|
||||
fsrs: FSRS,
|
||||
pub(crate) initialized: bool,
|
||||
}
|
||||
|
||||
impl SRSModule for FsrsModule {
|
||||
fn new(set: &[&CardState]) -> Self {
|
||||
let today = Local::now().date_naive();
|
||||
|
||||
let mut new = Self {
|
||||
today_set: set
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, c)| c.fsrs_open_day <= today)
|
||||
.map(|(index, _)| index)
|
||||
.collect(),
|
||||
current_group: vec![],
|
||||
queue: vec![],
|
||||
solved: vec![],
|
||||
rng: rng(),
|
||||
fsrs: FSRS::default(),
|
||||
};
|
||||
new.today_set.shuffle(&mut new.rng);
|
||||
new.fill_partial_pool();
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
fn next(&mut self, _: &[&CardState]) -> usize {
|
||||
if self.current_group.is_empty() {
|
||||
self.fill_partial_pool();
|
||||
@@ -222,45 +226,25 @@ impl SRSModule for FsrsModule {
|
||||
let offset = ((rng().random::<f32>() - 0.5) * 3.0).round() as i32;
|
||||
next_day += TimeDelta::days(offset as i64);
|
||||
}
|
||||
|
||||
let first_time = updated_word.fsrs_memory.is_none();
|
||||
|
||||
updated_word.fsrs_memory = Some(next.memory);
|
||||
updated_word.fsrs_open_day = next_day;
|
||||
updated_word.fsrs_opened = updated_word.last_open.date_naive();
|
||||
|
||||
if updated_word.fsrs_open_day != today {
|
||||
if (updated_word.fsrs_open_day != today && !first_time) || (first_time && status == WordOpenMode::Easy) {
|
||||
info!("For today word on index {} solved", index);
|
||||
self.solved.push(index);
|
||||
let index = self.current_group.iter().position(|x| *x == index).unwrap();
|
||||
self.current_group.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&mut self, set: &[&CardState]) {
|
||||
self.rng = rng();
|
||||
let today = Local::now().date_naive();
|
||||
self.today_set = set
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, c)| c.fsrs_open_day <= today)
|
||||
.map(|(index, _)| index)
|
||||
.collect();
|
||||
self.today_set.shuffle(&mut self.rng);
|
||||
self.fill_partial_pool();
|
||||
self.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl FsrsModule {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
today_set: vec![],
|
||||
current_group: vec![],
|
||||
queue: vec![],
|
||||
solved: vec![],
|
||||
rng: Default::default(),
|
||||
fsrs: FSRS::default(),
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
fn fill_partial_pool(&mut self) {
|
||||
debug!("Filling partial pool");
|
||||
self.current_group.extend(
|
||||
self.today_set
|
||||
.iter()
|
||||
@@ -281,7 +265,7 @@ impl FsrsModule {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WordOpenMode {
|
||||
Easy,
|
||||
Ok,
|
||||
|
||||
Reference in new issue
Block a user