ssr_facade/
lib.rs

1#![warn(clippy::pedantic)]
2#![feature(extract_if, hash_extract_if, iter_collect_into)]
3
4use std::time::{Duration, SystemTime};
5
6use rand::{thread_rng, Rng};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use ssr_core::{
9    task::{SharedStateExt, Task},
10    tasks_facade::{TaskId, TasksFacade},
11};
12
13fn serialize_id<S>(id: &TaskId, serializer: S) -> Result<S::Ok, S::Error>
14where
15    S: Serializer,
16{
17    id.serialize(serializer)
18}
19fn deserialize_id<'de, D>(deserializer: D) -> Result<TaskId, D::Error>
20where
21    D: Deserializer<'de>,
22{
23    const GEN_RANDOM: bool = false;
24
25    let id = TaskId::deserialize(deserializer)?;
26    let id = if GEN_RANDOM { rand::random() } else { id };
27    Ok(id)
28}
29
30#[derive(Serialize, Deserialize, Debug)]
31#[serde(bound(deserialize = "T: Task<'de>"))]
32struct TaskWrapper<T> {
33    task: T,
34    #[serde(serialize_with = "serialize_id", deserialize_with = "deserialize_id")]
35    id: TaskId,
36}
37
38impl<'a, T: Task<'a>> TaskWrapper<T> {
39    fn new(value: T) -> Self {
40        Self {
41            task: value,
42            id: rand::random(),
43        }
44    }
45}
46
47#[derive(Serialize, Deserialize, Debug)]
48#[serde(bound(deserialize = "'a: 'de, 'de: 'a"))]
49pub struct Facade<'a, T>
50where
51    T: Task<'a>,
52{
53    name: String,
54    tasks_pool: Vec<TaskWrapper<T>>,
55    tasks_to_recall: Vec<TaskWrapper<T>>,
56    desired_retention: f64,
57    state: T::SharedState,
58}
59
60impl<'a, T: Task<'a>> Facade<'a, T> {
61    pub fn find_tasks_to_recall(&mut self) {
62        let now = SystemTime::now() + Duration::from_secs(10);
63        self.tasks_pool
64            .extract_if(.., |t| {
65                t.task.next_repetition(&self.state, self.desired_retention) <= now
66            })
67            .collect_into(&mut self.tasks_to_recall);
68    }
69    pub fn reload_all_tasks_timings(&mut self) {
70        self.tasks_to_recall
71            .drain(..)
72            .collect_into(&mut self.tasks_pool);
73        self.find_tasks_to_recall();
74    }
75
76    fn take_random_task(&mut self) -> Option<TaskWrapper<T>> {
77        if self.tasks_to_recall.is_empty() {
78            return None;
79        }
80        let index = thread_rng().gen_range(0..self.tasks_to_recall.len());
81        Some(self.tasks_to_recall.swap_remove(index))
82    }
83
84    pub fn until_next_repetition(&self) -> Option<Duration> {
85        if self.tasks_total() == 0 {
86            None
87        } else if self.tasks_to_complete() > 0 {
88            Some(Duration::default())
89        } else {
90            self.tasks_pool
91                .iter()
92                .map(|t| {
93                    t.task
94                        .next_repetition(&self.state, self.desired_retention)
95                        .duration_since(SystemTime::now())
96                        .unwrap_or(Duration::default())
97                })
98                .min()
99        }
100    }
101}
102impl<'a, F: Task<'a>> Facade<'a, F> {
103    /// # Warning
104    /// You will loose all progress.
105    pub fn migrate<T: Task<'a> + std::fmt::Debug>(&self) -> Facade<'a, T>
106    where
107        T::SharedState: std::fmt::Debug,
108    {
109        let task_templates = self
110            .tasks_pool
111            .iter()
112            .chain(self.tasks_to_recall.iter())
113            .map(|t| t.task.get_blocks());
114        let mut new_facade = Facade::new(self.name.clone(), self.desired_retention);
115        for i in task_templates {
116            new_facade.create_task(i);
117        }
118        new_facade
119    }
120}
121impl<'a, T: Task<'a>> TasksFacade<'a, T> for Facade<'a, T> {
122    fn new(name: String, desired_retention: f64) -> Self {
123        Self {
124            name,
125            tasks_pool: Vec::default(),
126            tasks_to_recall: Vec::default(),
127            desired_retention,
128            state: T::SharedState::default(),
129        }
130    }
131
132    fn get_name(&self) -> &str {
133        &self.name
134    }
135
136    fn tasks_total(&self) -> usize {
137        self.tasks_pool.len() + self.tasks_to_recall.len()
138    }
139    fn tasks_to_complete(&self) -> usize {
140        self.tasks_to_recall.len()
141    }
142
143    fn complete_task(
144        &mut self,
145        interaction: &mut impl FnMut(
146            TaskId,
147            s_text_input_f::Blocks,
148        ) -> std::io::Result<s_text_input_f::Response>,
149    ) -> Result<(), ssr_core::tasks_facade::Error> {
150        self.find_tasks_to_recall();
151        let Some(TaskWrapper { mut task, id }) = self.take_random_task() else {
152            return match self.until_next_repetition() {
153                Some(time_until_next_repetition) => {
154                    Err(ssr_core::tasks_facade::Error::NoTaskToComplete {
155                        time_until_next_repetition,
156                    })
157                }
158                None => Err(ssr_core::tasks_facade::Error::NoTask),
159            };
160        };
161        task.complete(&mut self.state, self.desired_retention, &mut |blocks| {
162            interaction(id, blocks)
163        })?;
164        self.tasks_pool.push(TaskWrapper { task, id });
165        Ok(())
166    }
167
168    fn insert(&mut self, task: T) {
169        self.tasks_pool.push(TaskWrapper::new(task));
170    }
171
172    fn iter<'t>(&'t self) -> impl Iterator<Item = (&'t T, TaskId)>
173    where
174        T: 't,
175    {
176        self.tasks_pool
177            .iter()
178            .chain(self.tasks_to_recall.iter())
179            .map(|TaskWrapper { task, id }| (task, *id))
180    }
181
182    fn remove(&mut self, id: TaskId) -> bool {
183        let mut removed = false;
184        self.tasks_to_recall.retain(|task_wrapper| {
185            if task_wrapper.id == id {
186                removed = true;
187                false
188            } else {
189                true
190            }
191        });
192        if !removed {
193            self.tasks_pool.retain(|task_wrapper| {
194                if task_wrapper.id == id {
195                    removed = true;
196                    false
197                } else {
198                    true
199                }
200            });
201        }
202        removed
203    }
204
205    fn get_desired_retention(&self) -> f64 {
206        self.desired_retention
207    }
208
209    fn set_desired_retention(&mut self, desired_retention: f64) {
210        self.desired_retention = desired_retention;
211
212        self.reload_all_tasks_timings();
213    }
214
215    fn create_task(&mut self, input: s_text_input_f::BlocksWithAnswer) {
216        self.insert(T::new(input));
217    }
218
219    fn optimize(&mut self) -> Result<(), Box<dyn std::error::Error>>
220    where
221        T::SharedState: SharedStateExt<'a, T>,
222    {
223        let items = self
224            .tasks_pool
225            .iter()
226            .chain(self.tasks_to_recall.iter())
227            .map(|x| &x.task);
228        self.state.optimize(items)?;
229
230        self.reload_all_tasks_timings();
231        Ok(())
232    }
233}