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 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}