bladeink/story/
progress.rs1use crate::{
2 choice::Choice,
3 choice_point::ChoicePoint,
4 container::Container,
5 control_command::{CommandType, ControlCommand},
6 object::RTObject,
7 pointer::{self, Pointer},
8 push_pop::PushPopType,
9 story::{errors::ErrorType, OutputStateChange, Story},
10 story_error::StoryError,
11 value::Value,
12 void::Void,
13};
14use std::{self, rc::Rc};
15
16impl Story {
19 pub fn can_continue(&self) -> bool {
22 self.get_state().can_continue()
23 }
24
25 pub fn cont(&mut self) -> Result<String, StoryError> {
27 self.continue_async(0.0)?;
28 self.get_current_text()
29 }
30
31 pub fn continue_maximally(&mut self) -> Result<String, StoryError> {
34 self.if_async_we_cant("continue_maximally")?;
35
36 let mut sb = String::new();
37
38 while self.can_continue() {
39 sb.push_str(&self.cont()?);
40 }
41
42 Ok(sb)
43 }
44
45 pub fn continue_async(&mut self, millisecs_limit_async: f32) -> Result<(), StoryError> {
48 if !self.has_validated_externals {
49 self.validate_external_bindings()?;
50 }
51
52 self.continue_internal(millisecs_limit_async)
53 }
54
55 pub(crate) fn if_async_we_cant(&self, activity_str: &str) -> Result<(), StoryError> {
56 if self.async_continue_active {
57 return Err(StoryError::InvalidStoryState(format!("Can't {}. Story is in the middle of a continue_async(). Make more continue_async() calls or a single cont() call beforehand.", activity_str)));
58 }
59
60 Ok(())
61 }
62
63 pub(crate) fn continue_internal(
64 &mut self,
65 millisecs_limit_async: f32,
66 ) -> Result<(), StoryError> {
67 let is_async_time_limited = millisecs_limit_async > 0.0;
68
69 self.recursive_continue_count += 1;
70
71 if !self.async_continue_active {
75 self.async_continue_active = is_async_time_limited;
76 if !self.can_continue() {
77 return Err(StoryError::InvalidStoryState(
78 "Can't continue - should check can_continue before calling Continue".to_owned(),
79 ));
80 }
81
82 self.get_state_mut().set_did_safe_exit(false);
83
84 self.get_state_mut().reset_output(None);
85
86 if self.recursive_continue_count == 1 {
90 self.state.variables_state.start_variable_observation();
91 }
92 } else if self.async_continue_active && !is_async_time_limited {
93 self.async_continue_active = false;
94 }
95
96 let duration_stopwatch = match self.async_continue_active {
98 true => Some(web_time::Instant::now()),
99 false => None,
100 };
101
102 let mut output_stream_ends_in_newline = false;
103 self.saw_lookahead_unsafe_function_after_new_line = false;
104
105 loop {
106 match self.continue_single_step() {
107 Ok(r) => output_stream_ends_in_newline = r,
108 Err(e) => {
109 self.add_error(e.get_message(), false);
110 break;
111 }
112 }
113
114 if output_stream_ends_in_newline {
115 break;
116 }
117
118 if self.async_continue_active
120 && duration_stopwatch.as_ref().unwrap().elapsed().as_millis() as f32
121 > millisecs_limit_async
122 {
123 break;
124 }
125
126 if !self.can_continue() {
127 break;
128 }
129 }
130
131 let mut changed_variables_to_observe = None;
132
133 if output_stream_ends_in_newline || !self.can_continue() {
141 if self.state_snapshot_at_last_new_line.is_some() {
143 self.restore_state_snapshot();
144 }
145
146 if !self.can_continue() {
148 if self.state.get_callstack().borrow().can_pop_thread() {
149 self.add_error("Thread available to pop, threads should always be flat by the end of evaluation?", false);
150 }
151
152 if self.state.get_generated_choices().is_empty()
153 && !self.get_state().is_did_safe_exit()
154 && self.temporary_evaluation_container.is_none()
155 {
156 if self
157 .state
158 .get_callstack()
159 .borrow()
160 .can_pop_type(Some(PushPopType::Tunnel))
161 {
162 self.add_error("unexpectedly reached end of content. Do you need a '->->' to return from a tunnel?", false);
163 } else if self
164 .state
165 .get_callstack()
166 .borrow()
167 .can_pop_type(Some(PushPopType::Function))
168 {
169 self.add_error(
170 "unexpectedly reached end of content. Do you need a '~ return'?",
171 false,
172 );
173 } else if !self.get_state().get_callstack().borrow().can_pop() {
174 self.add_error(
175 "ran out of content. Do you need a '-> DONE' or '-> END'?",
176 false,
177 );
178 } else {
179 self.add_error("unexpectedly reached end of content for unknown reason. Please debug compiler!", false);
180 }
181 }
182 }
183 self.get_state_mut().set_did_safe_exit(false);
184 self.saw_lookahead_unsafe_function_after_new_line = false;
185
186 if self.recursive_continue_count == 1 {
187 changed_variables_to_observe =
188 Some(self.state.variables_state.complete_variable_observation());
189 }
190
191 self.async_continue_active = false;
192 }
193
194 self.recursive_continue_count -= 1;
195
196 if self.get_state().has_error() || self.get_state().has_warning() {
200 match &self.on_error {
201 Some(on_err) => {
202 if self.get_state().has_error() {
203 for err in self.get_state().get_current_errors() {
204 on_err.borrow_mut().error(err, ErrorType::Error);
205 }
206 }
207
208 if self.get_state().has_warning() {
209 for err in self.get_state().get_current_warnings() {
210 on_err.borrow_mut().error(err, ErrorType::Warning);
211 }
212 }
213
214 self.reset_errors();
215 }
216 None => {
218 let mut sb = String::new();
219 sb.push_str("Ink had ");
220
221 if self.get_state().has_error() {
222 sb.push_str(&self.get_state().get_current_errors().len().to_string());
223
224 if self.get_state().get_current_errors().len() == 1 {
225 sb.push_str(" error");
226 } else {
227 sb.push_str(" errors");
228 }
229
230 if self.get_state().has_warning() {
231 sb.push_str(" and ");
232 }
233 }
234
235 if self.get_state().has_warning() {
236 sb.push_str(
237 self.get_state()
238 .get_current_warnings()
239 .len()
240 .to_string()
241 .as_str(),
242 );
243 if self.get_state().get_current_errors().len() == 1 {
244 sb.push_str(" warning");
245 } else {
246 sb.push_str(" warnings");
247 }
248 }
249
250 sb.push_str(". It is strongly suggested that you assign an error handler to story.onError. The first issue was: ");
251
252 if self.get_state().has_error() {
253 sb.push_str(self.get_state().get_current_errors()[0].as_str());
254 } else {
255 sb.push_str(
256 self.get_state().get_current_warnings()[0]
257 .to_string()
258 .as_str(),
259 );
260 }
261
262 if let Some(changed) = changed_variables_to_observe {
264 for (variable_name, value) in changed {
265 self.notify_variable_changed(&variable_name, &value);
266 }
267 }
268
269 return Err(StoryError::InvalidStoryState(sb));
270 }
271 }
272 }
273
274 Ok(())
275 }
276
277 pub(crate) fn continue_single_step(&mut self) -> Result<bool, StoryError> {
278 self.step()?;
280
281 if !self.can_continue()
283 && !self
284 .get_state()
285 .get_callstack()
286 .borrow()
287 .element_is_evaluate_from_game()
288 {
289 self.try_follow_default_invisible_choice()?;
290 }
291
292 if !self.get_state().in_string_evaluation() {
294 if let Some(state_snapshot_at_last_new_line) =
297 self.state_snapshot_at_last_new_line.as_mut()
298 {
299 let change = Story::calculate_newline_output_state_change(
302 &state_snapshot_at_last_new_line.get_current_text(),
303 &self.state.get_current_text(),
304 state_snapshot_at_last_new_line.get_current_tags().len() as i32,
305 self.state.get_current_tags().len() as i32,
306 );
307
308 if change == OutputStateChange::ExtendedBeyondNewline
311 || self.saw_lookahead_unsafe_function_after_new_line
312 {
313 self.restore_state_snapshot();
314
315 return Ok(true);
317 }
318 else if change == OutputStateChange::NewlineRemoved {
321 self.state_snapshot_at_last_new_line = None;
322 self.discard_snapshot();
323 }
324 }
325
326 if self.get_state().output_stream_ends_in_newline() {
328 if self.can_continue() {
333 if self.state_snapshot_at_last_new_line.is_none() {
339 self.state_snapshot();
340 }
341 }
342 else {
345 self.discard_snapshot();
346 }
347 }
348 }
349
350 Ok(false)
351 }
352
353 pub(crate) fn step(&mut self) -> Result<(), StoryError> {
354 let mut should_add_to_stream = true;
355
356 let mut pointer = self.get_state().get_current_pointer().clone();
358
359 if pointer.is_null() {
360 return Ok(());
361 }
362
363 let r = pointer.resolve();
366
367 let mut container_to_enter = match r {
368 Some(o) => match o.into_any().downcast::<Container>() {
369 Ok(c) => Some(c),
370 Err(_) => None,
371 },
372 None => None,
373 };
374
375 while let Some(cte) = container_to_enter.as_ref() {
376 self.visit_container(cte, true);
378
379 if cte.content.is_empty() {
381 break;
382 }
383
384 pointer = Pointer::start_of(cte.clone());
385
386 let r = pointer.resolve();
387
388 container_to_enter = match r {
389 Some(o) => match o.into_any().downcast::<Container>() {
390 Ok(c) => Some(c),
391 Err(_) => None,
392 },
393 None => None,
394 };
395 }
396
397 self.get_state_mut().set_current_pointer(pointer.clone());
398
399 let mut current_content_obj = pointer.resolve();
406
407 let is_logic_or_flow_control = self.perform_logic_and_flow_control(¤t_content_obj)?;
408
409 if self.get_state().get_current_pointer().is_null() {
411 return Ok(());
412 }
413
414 if is_logic_or_flow_control {
415 should_add_to_stream = false;
416 }
417
418 if let Some(cco) = ¤t_content_obj {
420 if cco.as_any().is::<Container>() {
423 should_add_to_stream = false;
424 }
425
426 if let Ok(choice_point) = cco.clone().into_any().downcast::<ChoicePoint>() {
427 let choice = self.process_choice(&choice_point)?;
428 if let Some(choice) = choice {
429 self.get_state_mut()
430 .get_generated_choices_mut()
431 .push(choice);
432 }
433
434 current_content_obj = None;
435 should_add_to_stream = false;
436 }
437 }
438
439 if should_add_to_stream {
441 let var_pointer =
447 Value::get_variable_pointer_value(current_content_obj.as_ref().unwrap().as_ref());
448
449 if let Some(var_pointer) = var_pointer {
450 if var_pointer.context_index == -1 {
451 let context_idx = self
454 .get_state()
455 .get_callstack()
456 .borrow()
457 .context_for_variable_named(&var_pointer.variable_name);
458 current_content_obj = Some(Rc::new(Value::new_variable_pointer(
459 &var_pointer.variable_name,
460 context_idx as i32,
461 )));
462 }
463 }
464
465 if self.get_state().get_in_expression_evaluation() {
467 self.get_state_mut()
468 .push_evaluation_stack(current_content_obj.as_ref().unwrap().clone());
469 }
470 else {
472 self.get_state_mut()
473 .push_to_output_stream(current_content_obj.as_ref().unwrap().clone());
474 }
475 }
476
477 self.next_content()?;
479
480 if current_content_obj.is_some() {
485 if let Some(control_cmd) = current_content_obj
486 .as_ref()
487 .unwrap()
488 .as_any()
489 .downcast_ref::<ControlCommand>()
490 {
491 if control_cmd.command_type == CommandType::StartThread {
492 self.get_state().get_callstack().borrow_mut().push_thread();
493 }
494 }
495 }
496
497 Ok(())
498 }
499
500 pub(crate) fn next_content(&mut self) -> Result<(), StoryError> {
501 let cp = self.get_state().get_current_pointer();
504 self.get_state_mut().set_previous_pointer(cp);
505
506 if !self.get_state().diverted_pointer.is_null() {
508 let dp = self.get_state().diverted_pointer.clone();
509 self.get_state_mut().set_current_pointer(dp);
510 self.get_state_mut()
511 .set_diverted_pointer(pointer::NULL.clone());
512
513 self.visit_changed_containers_due_to_divert();
516
517 if !self.get_state().get_current_pointer().is_null() {
519 return Ok(());
520 }
521
522 }
528
529 let successful_pointer_increment = self.increment_content_pointer();
530
531 if !successful_pointer_increment {
534 let mut did_pop = false;
535
536 let can_pop_type = self
537 .get_state()
538 .get_callstack()
539 .as_ref()
540 .borrow()
541 .can_pop_type(Some(PushPopType::Function));
542 if can_pop_type {
543 self.get_state_mut()
545 .pop_callstack(Some(PushPopType::Function))?;
546
547 if self.get_state().get_in_expression_evaluation() {
552 self.get_state_mut()
553 .push_evaluation_stack(Rc::new(Void::new()));
554 }
555
556 did_pop = true;
557 } else if self
558 .get_state()
559 .get_callstack()
560 .as_ref()
561 .borrow()
562 .can_pop_thread()
563 {
564 self.get_state()
565 .get_callstack()
566 .as_ref()
567 .borrow_mut()
568 .pop_thread()?;
569
570 did_pop = true;
571 } else {
572 self.get_state_mut()
573 .try_exit_function_evaluation_from_game();
574 }
575
576 if did_pop && !self.get_state().get_current_pointer().is_null() {
578 self.next_content()?;
579 }
580 }
581
582 Ok(())
583 }
584
585 pub(crate) fn increment_content_pointer(&self) -> bool {
586 let mut successful_increment = true;
587
588 let mut pointer = self
589 .get_state()
590 .get_callstack()
591 .as_ref()
592 .borrow()
593 .get_current_element()
594 .current_pointer
595 .clone();
596 pointer.index += 1;
597
598 let mut container = pointer.container.as_ref().unwrap().clone();
599
600 while pointer.index >= container.content.len() as i32 {
604 successful_increment = false;
605
606 let next_ancestor = container.get_object().get_parent();
607
608 if next_ancestor.is_none() {
609 break;
610 }
611
612 let rto: Rc<dyn RTObject> = container;
613 let index_in_ancestor = next_ancestor
614 .as_ref()
615 .unwrap()
616 .content
617 .iter()
618 .position(|s| Rc::ptr_eq(s, &rto));
619 if index_in_ancestor.is_none() {
620 break;
621 }
622
623 pointer = Pointer::new(next_ancestor, index_in_ancestor.unwrap() as i32);
624 container = pointer.container.as_ref().unwrap().clone();
625
626 pointer.index += 1;
628
629 successful_increment = true;
630 }
631
632 if !successful_increment {
633 pointer = pointer::NULL.clone();
634 }
635
636 self.get_state()
637 .get_callstack()
638 .as_ref()
639 .borrow_mut()
640 .get_current_element_mut()
641 .current_pointer = pointer;
642
643 successful_increment
644 }
645
646 pub(crate) fn calculate_newline_output_state_change(
647 prev_text: &str,
648 curr_text: &str,
649 prev_tag_count: i32,
650 curr_tag_count: i32,
651 ) -> OutputStateChange {
652 let newline_still_exists = curr_text.len() >= prev_text.len()
655 && !prev_text.is_empty()
656 && curr_text.as_bytes()[prev_text.len() - 1] == b'\n';
657 if prev_tag_count == curr_tag_count
658 && prev_text.len() == curr_text.len()
659 && newline_still_exists
660 {
661 return OutputStateChange::NoChange;
662 }
663
664 if !newline_still_exists {
666 return OutputStateChange::NewlineRemoved;
667 }
668
669 if curr_tag_count > prev_tag_count {
671 return OutputStateChange::ExtendedBeyondNewline;
672 }
673
674 for c in curr_text.as_bytes().iter().skip(prev_text.len()) {
676 if *c != b' ' && *c != b'\t' {
677 return OutputStateChange::ExtendedBeyondNewline;
678 }
679 }
680
681 OutputStateChange::NoChange
685 }
686
687 pub(crate) fn visit_container(&mut self, container: &Rc<Container>, at_start: bool) {
688 if !container.counting_at_start_only || at_start {
689 if container.visits_should_be_counted {
690 self.get_state_mut()
691 .increment_visit_count_for_container(container);
692 }
693
694 if container.turn_index_should_be_counted {
695 self.get_state_mut()
696 .record_turn_index_visit_to_container(container);
697 }
698 }
699 }
700
701 pub fn get_current_choices(&self) -> Vec<Rc<Choice>> {
709 let mut choices = Vec::new();
711
712 if let Some(current_choices) = self.get_state().get_current_choices() {
713 for c in current_choices {
714 if !c.is_invisible_default {
715 c.index.replace(choices.len());
716 choices.push(c.clone());
717 }
718 }
719 }
720
721 choices
722 }
723
724 pub fn get_current_text(&mut self) -> Result<String, StoryError> {
728 self.if_async_we_cant("call currentText since it's a work in progress")?;
729 Ok(self.get_state_mut().get_current_text())
730 }
731}