ayaka_model/
view_model.rs

1use crate::*;
2use anyhow::Result;
3use serde::Serialize;
4use stream_future::stream;
5use trylog::macros::*;
6
7/// The status when calling [`GameViewModel::open_game`].
8#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
9#[serde(tag = "t", content = "data")]
10pub enum OpenGameStatus {
11    /// Loading the settings.
12    LoadSettings,
13    /// Loading the global records.
14    LoadGlobalRecords,
15    /// Loading the records.
16    LoadRecords,
17    /// The game is loaded.
18    Loaded,
19}
20
21/// A view model of Ayaka.
22/// It manages all settings and provides high-level APIs.
23pub struct GameViewModel<S: SettingsManager, M: RawModule + Send + Sync + 'static> {
24    context: Option<Context<M>>,
25    current_record: ActionRecord,
26    current_raw_context: Option<RawContext>,
27    settings_manager: S,
28    settings: Option<Settings>,
29    records: Vec<ActionRecord>,
30    global_record: Option<GlobalRecord>,
31}
32
33impl<S: SettingsManager, M: RawModule + Send + Sync + 'static> GameViewModel<S, M> {
34    /// Create a [`GameViewModel`] with a settings manager.
35    pub fn new(settings_manager: S) -> Self {
36        Self {
37            settings_manager,
38            context: None,
39            current_record: ActionRecord::default(),
40            current_raw_context: None,
41            settings: None,
42            records: vec![],
43            global_record: None,
44        }
45    }
46
47    /// Open the game with context.
48    #[stream(OpenGameStatus, lifetime = 'a)]
49    pub async fn open_game<'a>(&'a mut self, context: Context<M>) -> Result<()> {
50        yield OpenGameStatus::LoadSettings;
51        let settings = unwrap_or_default_log!(
52            self.settings_manager.load_settings(),
53            "Load settings failed"
54        );
55        self.settings = Some(settings);
56
57        yield OpenGameStatus::LoadGlobalRecords;
58        let global_record = unwrap_or_default_log!(
59            self.settings_manager
60                .load_global_record(&context.game().config.title),
61            "Load global records failed"
62        );
63        self.global_record = Some(global_record);
64
65        yield OpenGameStatus::LoadRecords;
66        self.records = unwrap_or_default_log!(
67            self.settings_manager
68                .load_records(&context.game().config.title),
69            "Load records failed"
70        );
71        self.context = Some(context);
72
73        yield OpenGameStatus::Loaded;
74
75        Ok(())
76    }
77
78    /// The [`Context`], should be called after [`Self::open_game`].
79    pub fn context(&self) -> &Context<M> {
80        self.context
81            .as_ref()
82            .expect("should be called after open_game")
83    }
84
85    /// The [`Context`], should be called after [`Self::open_game`].
86    pub fn context_mut(&mut self) -> &mut Context<M> {
87        self.context
88            .as_mut()
89            .expect("should be called after open_game")
90    }
91
92    /// The current [`ActionRecord`].
93    pub fn record(&self) -> &ActionRecord {
94        &self.current_record
95    }
96
97    /// The loaded [`Settings`].
98    pub fn settings(&self) -> &Settings {
99        self.settings
100            .as_ref()
101            .expect("should be called after open_game")
102    }
103
104    /// Set the [`Settings`].
105    pub fn set_settings(&mut self, settings: Settings) {
106        self.settings = Some(settings);
107    }
108
109    /// The loaded [`ActionRecord`]s.
110    pub fn records(&self) -> &[ActionRecord] {
111        &self.records
112    }
113
114    /// The loaded [`GlobalRecord`].
115    pub fn global_record(&self) -> &GlobalRecord {
116        self.global_record
117            .as_ref()
118            .expect("should be called after open_game")
119    }
120
121    /// The loaded [`GlobalRecord`].
122    pub fn global_record_mut(&mut self) -> &mut GlobalRecord {
123        self.global_record
124            .as_mut()
125            .expect("should be called after open_game")
126    }
127
128    /// Get the avaliable locales from paragraphs.
129    pub fn avaliable_locale(&self) -> impl Iterator<Item = &Locale> {
130        self.context().game().paras.keys()
131    }
132
133    /// Start a new game.
134    /// The state of the model after this call is actually invalid.
135    /// You should call `next_run` immediately after this call,
136    /// to ensure there's content in the game, and switch to the
137    /// first line of the first paragraph.
138    pub fn init_new(&mut self) {
139        let ctx = self.context().game().start_context();
140        self.current_record = ActionRecord::default();
141        // This is the start.
142        self.current_raw_context = None;
143        self.context_mut().set_context(ctx);
144    }
145
146    /// Start a game with record.
147    pub fn init_context(&mut self, record: ActionRecord) {
148        let mut ctx = record.last_ctx_with_game(self.context().game());
149        self.current_record = record;
150        // Update current raw context.
151        self.current_raw_context = self.current_record.history.last().cloned();
152        log::debug!("Context: {:?}", self.current_raw_context);
153        // `ctx` points to the next raw context.
154        ctx.cur_act += 1;
155        self.context_mut().set_context(ctx);
156    }
157
158    /// Start a game with the index of records.
159    pub fn init_context_by_index(&mut self, index: usize) {
160        self.init_context(self.records()[index].clone())
161    }
162
163    fn push_history(&mut self, ctx: &RawContext) {
164        let cur_text = self
165            .context()
166            .game()
167            .find_para(
168                &self.context().game().config.base_lang,
169                &ctx.cur_base_para,
170                &ctx.cur_para,
171            )
172            .and_then(|p| p.texts.get(ctx.cur_act));
173        let is_text = cur_text
174            .map(|line| matches!(line, Line::Text(_)))
175            .unwrap_or_default();
176        if is_text {
177            self.current_record.history.push(ctx.clone());
178        }
179    }
180
181    /// Step to the next run.
182    pub fn next_run(&mut self) -> bool {
183        let ctx = self.context_mut().next_run();
184        if let Some(ctx) = &ctx {
185            self.push_history(ctx);
186            self.global_record_mut().update(ctx);
187            log::debug!("{ctx:?}");
188        }
189        self.current_raw_context = ctx;
190        self.current_raw_context.is_some()
191    }
192
193    /// Step back to the last run.
194    pub fn next_back_run(&mut self) -> bool {
195        if self.current_record.history.len() <= 1 {
196            log::debug!("No action in the history.");
197            false
198        } else {
199            // The last entry is the current one.
200            // We don't assume that a user could call next_back_run when the
201            // current run is empty.
202            self.current_record.history.pop();
203            // When we pop the current run, the last entry is what we want.
204            self.current_raw_context = self.current_record.history.last().cloned();
205            debug_assert!(self.current_raw_context.is_some());
206            // We clone the (new) current run to set the "next" raw context.
207            // We don't use the popped run to set the raw context,
208            // because the empty runs are not recorded.
209            let mut ctx = self
210                .current_raw_context
211                .clone()
212                .expect("current raw context cannot be None");
213            ctx.cur_act += 1;
214            self.context_mut().set_context(ctx);
215            true
216        }
217    }
218
219    /// Get the current [`RawContext`].
220    pub fn current_run(&self) -> Option<&RawContext> {
221        self.current_raw_context.as_ref()
222    }
223
224    /// Get the current paragraph title.
225    pub fn current_title(&self) -> Option<&String> {
226        self.context()
227            .current_paragraph_title(&self.settings().lang)
228    }
229
230    /// Get the current action by language.
231    pub fn current_action(&self) -> Option<Action> {
232        self.current_run().map(|raw_ctx| {
233            unwrap_or_default_log!(
234                self.context().get_action(&self.settings().lang, raw_ctx),
235                "Cannot get action"
236            )
237        })
238    }
239
240    /// Get the current action by language and secondary language.
241    pub fn current_actions(&self) -> Option<(Action, Option<Action>)> {
242        self.current_run().map(|raw_ctx| self.get_actions(raw_ctx))
243    }
244
245    fn get_actions(&self, raw_ctx: &RawContext) -> (Action, Option<Action>) {
246        let action = unwrap_or_default_log!(
247            self.context().get_action(&self.settings().lang, raw_ctx),
248            "Cannot get action"
249        );
250        let base_action = self.settings().sub_lang.as_ref().map(|sub_lang| {
251            unwrap_or_default_log!(
252                self.context().get_action(sub_lang, raw_ctx),
253                "Cannot get sub action"
254            )
255        });
256        (action, base_action)
257    }
258
259    /// Choose a switch item by index.
260    pub fn switch(&mut self, i: usize) {
261        log::debug!("Switch {i}");
262        self.context_mut().switch(i);
263    }
264
265    /// Save current [`ActionRecord`] to the records.
266    pub fn save_current_to(&mut self, index: usize) {
267        let record = self.current_record.clone();
268        if index >= self.records.len() {
269            self.records.push(record);
270        } else {
271            self.records[index] = record;
272        }
273    }
274
275    /// Save all settings and records.
276    pub fn save_settings(&self) -> Result<()> {
277        let game = &self.context().game().config.title;
278        self.settings_manager.save_settings(self.settings())?;
279        self.settings_manager
280            .save_global_record(game, self.global_record())?;
281        self.settings_manager.save_records(game, self.records())?;
282        Ok(())
283    }
284
285    /// Determine if current run has been visited.
286    pub fn current_visited(&self) -> bool {
287        self.current_run()
288            .map(|ctx| self.global_record().visited(ctx))
289            .unwrap_or_default()
290    }
291
292    /// Get the last action text from each record.
293    pub fn records_text(&self) -> impl Iterator<Item = ActionText> + '_ {
294        self.records().iter().map(|record| {
295            let raw_ctx = record
296                .last_ctx()
297                .expect("there should be at least one RawContext in the ActionRecord");
298            let action = unwrap_or_default_log!(
299                self.context().get_action(&self.settings().lang, raw_ctx),
300                "Cannot get action"
301            );
302            if let Action::Text(action) = action {
303                action
304            } else {
305                panic!("action in the record should be text action")
306            }
307        })
308    }
309
310    /// Get the current history by language and secondary language.
311    pub fn current_history(
312        &self,
313    ) -> impl DoubleEndedIterator<Item = (Action, Option<Action>)> + '_ {
314        self.record()
315            .history
316            .iter()
317            .map(|raw_ctx| self.get_actions(raw_ctx))
318    }
319}