1use std::sync::Arc;
4
5use itertools::EitherOrBoth;
6use itertools::Itertools;
7use tokio::sync::RwLock;
8
9use crate::ExecOutcome;
10use crate::ExecutorContext;
11use crate::execution::EnvironmentRef;
12use crate::execution::ExecutorSettings;
13use crate::execution::annotations;
14use crate::execution::memory::Stack;
15use crate::execution::state::ModuleInfoMap;
16use crate::execution::state::{self as exec_state};
17use crate::front::Object;
18use crate::parsing::ast::types::Annotation;
19use crate::parsing::ast::types::Node;
20use crate::parsing::ast::types::Program;
21use crate::walk::Node as WalkNode;
22
23lazy_static::lazy_static! {
24 static ref OLD_AST: Arc<RwLock<Option<GlobalState>>> = Default::default();
26 static ref PREV_MEMORY: Arc<RwLock<Option<SketchModeState>>> = Default::default();
28}
29
30pub(super) async fn read_old_ast() -> Option<GlobalState> {
32 let old_ast = OLD_AST.read().await;
33 old_ast.clone()
34}
35
36pub(super) async fn write_old_ast(old_state: GlobalState) {
37 let mut old_ast = OLD_AST.write().await;
38 *old_ast = Some(old_state);
39}
40
41pub(crate) async fn read_old_memory() -> Option<SketchModeState> {
42 let old_mem = PREV_MEMORY.read().await;
43 old_mem.clone()
44}
45
46pub(crate) async fn write_old_memory(mem: SketchModeState) {
47 let mut old_mem = PREV_MEMORY.write().await;
48 *old_mem = Some(mem);
49}
50
51pub async fn bust_cache() {
52 let mut old_ast = OLD_AST.write().await;
53 *old_ast = None;
54}
55
56pub async fn clear_mem_cache() {
57 let mut old_mem = PREV_MEMORY.write().await;
58 *old_mem = None;
59}
60
61#[derive(Debug, Clone)]
63pub struct CacheInformation<'a> {
64 pub ast: &'a Node<Program>,
65 pub settings: &'a ExecutorSettings,
66}
67
68#[derive(Debug, Clone)]
70pub(super) struct GlobalState {
71 pub(super) main: ModuleState,
72 pub(super) exec_state: exec_state::GlobalState,
74 pub(super) settings: ExecutorSettings,
76}
77
78impl GlobalState {
79 pub fn new(
80 state: exec_state::ExecState,
81 settings: ExecutorSettings,
82 ast: Node<Program>,
83 result_env: EnvironmentRef,
84 ) -> Self {
85 Self {
86 main: ModuleState {
87 ast,
88 exec_state: state.mod_local,
89 result_env,
90 },
91 exec_state: state.global,
92 settings,
93 }
94 }
95
96 pub fn with_settings(mut self, settings: ExecutorSettings) -> GlobalState {
97 self.settings = settings;
98 self
99 }
100
101 pub fn reconstitute_exec_state(&self) -> exec_state::ExecState {
102 exec_state::ExecState {
103 global: self.exec_state.clone(),
104 mod_local: self.main.exec_state.clone(),
105 }
106 }
107
108 pub async fn into_exec_outcome(self, ctx: &ExecutorContext) -> ExecOutcome {
109 ExecOutcome {
112 variables: self.main.exec_state.variables(self.main.result_env),
113 filenames: self.exec_state.filenames(),
114 #[cfg(feature = "artifact-graph")]
115 operations: self.exec_state.root_module_artifacts.operations,
116 #[cfg(feature = "artifact-graph")]
117 artifact_graph: self.exec_state.artifacts.graph,
118 #[cfg(feature = "artifact-graph")]
119 scene_objects: self.exec_state.root_module_artifacts.scene_objects,
120 #[cfg(feature = "artifact-graph")]
121 source_range_to_object: self.exec_state.root_module_artifacts.source_range_to_object,
122 #[cfg(feature = "artifact-graph")]
123 var_solutions: self.exec_state.root_module_artifacts.var_solutions,
124 errors: self.exec_state.errors,
125 default_planes: ctx.engine.get_default_planes().read().await.clone(),
126 }
127 }
128}
129
130#[derive(Debug, Clone)]
132pub(super) struct ModuleState {
133 pub(super) ast: Node<Program>,
135 pub(super) exec_state: exec_state::ModuleState,
137 pub(super) result_env: EnvironmentRef,
139}
140
141#[derive(Debug, Clone)]
143pub(crate) struct SketchModeState {
144 pub stack: Stack,
146 pub module_infos: ModuleInfoMap,
148 #[cfg_attr(not(feature = "artifact-graph"), expect(dead_code))]
150 pub scene_objects: Vec<Object>,
151}
152
153#[derive(Debug, Clone, PartialEq)]
155#[allow(clippy::large_enum_variant)]
156pub(super) enum CacheResult {
157 ReExecute {
158 clear_scene: bool,
160 reapply_settings: bool,
162 program: Node<Program>,
164 },
165 CheckImportsOnly {
169 reapply_settings: bool,
171 ast: Node<Program>,
173 },
174 NoAction(bool),
176}
177
178pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInformation<'_>) -> CacheResult {
186 let mut reapply_settings = false;
187
188 if old.settings != new.settings {
191 reapply_settings = true;
194 }
195
196 if old.ast == new.ast {
199 if !old.ast.has_import_statements() {
203 return CacheResult::NoAction(reapply_settings);
204 }
205
206 return CacheResult::CheckImportsOnly {
208 reapply_settings,
209 ast: old.ast.clone(),
210 };
211 }
212
213 let mut old_ast = old.ast.clone();
215 let mut new_ast = new.ast.clone();
216
217 old_ast.compute_digest();
220 new_ast.compute_digest();
221
222 if old_ast.digest == new_ast.digest {
224 if !old.ast.has_import_statements() {
228 return CacheResult::NoAction(reapply_settings);
229 }
230
231 return CacheResult::CheckImportsOnly {
233 reapply_settings,
234 ast: old.ast.clone(),
235 };
236 }
237
238 if !old_ast
240 .inner_attrs
241 .iter()
242 .filter(annotations::is_significant)
243 .zip_longest(new_ast.inner_attrs.iter().filter(annotations::is_significant))
244 .all(|pair| {
245 match pair {
246 EitherOrBoth::Both(old, new) => {
247 let Annotation { name, properties, .. } = &old.inner;
250 let Annotation {
251 name: new_name,
252 properties: new_properties,
253 ..
254 } = &new.inner;
255
256 name.as_ref().map(|n| n.digest) == new_name.as_ref().map(|n| n.digest)
257 && properties
258 .as_ref()
259 .map(|props| props.iter().map(|p| p.digest).collect::<Vec<_>>())
260 == new_properties
261 .as_ref()
262 .map(|props| props.iter().map(|p| p.digest).collect::<Vec<_>>())
263 }
264 _ => false,
265 }
266 })
267 {
268 return CacheResult::ReExecute {
272 clear_scene: true,
273 reapply_settings: true,
274 program: new.ast.clone(),
275 };
276 }
277
278 generate_changed_program(old_ast, new_ast, reapply_settings)
280}
281
282fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>, reapply_settings: bool) -> CacheResult {
294 if !old_ast.body.iter().zip(new_ast.body.iter()).all(|(old, new)| {
295 let old_node: WalkNode = old.into();
296 let new_node: WalkNode = new.into();
297 old_node.digest() == new_node.digest()
298 }) {
299 return CacheResult::ReExecute {
305 clear_scene: true,
306 reapply_settings,
307 program: new_ast,
308 };
309 }
310
311 match new_ast.body.len().cmp(&old_ast.body.len()) {
315 std::cmp::Ordering::Less => {
316 CacheResult::ReExecute {
325 clear_scene: true,
326 reapply_settings,
327 program: new_ast,
328 }
329 }
330 std::cmp::Ordering::Greater => {
331 new_ast.body = new_ast.body[old_ast.body.len()..].to_owned();
339
340 CacheResult::ReExecute {
341 clear_scene: false,
342 reapply_settings,
343 program: new_ast,
344 }
345 }
346 std::cmp::Ordering::Equal => {
347 CacheResult::NoAction(reapply_settings)
357 }
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use pretty_assertions::assert_eq;
364
365 use super::*;
366 use crate::execution::ExecTestResults;
367 use crate::execution::parse_execute;
368 use crate::execution::parse_execute_with_project_dir;
369
370 #[tokio::test(flavor = "multi_thread")]
371 async fn test_get_changed_program_same_code() {
372 let new = r#"// Remove the end face for the extrusion.
373firstSketch = startSketchOn(XY)
374 |> startProfile(at = [-12, 12])
375 |> line(end = [24, 0])
376 |> line(end = [0, -24])
377 |> line(end = [-24, 0])
378 |> close()
379 |> extrude(length = 6)
380
381// Remove the end face for the extrusion.
382shell(firstSketch, faces = [END], thickness = 0.25)"#;
383
384 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(new).await.unwrap();
385
386 let result = get_changed_program(
387 CacheInformation {
388 ast: &program.ast,
389 settings: &exec_ctxt.settings,
390 },
391 CacheInformation {
392 ast: &program.ast,
393 settings: &exec_ctxt.settings,
394 },
395 )
396 .await;
397
398 assert_eq!(result, CacheResult::NoAction(false));
399 exec_ctxt.close().await;
400 }
401
402 #[tokio::test(flavor = "multi_thread")]
403 async fn test_get_changed_program_same_code_changed_whitespace() {
404 let old = r#" // Remove the end face for the extrusion.
405firstSketch = startSketchOn(XY)
406 |> startProfile(at = [-12, 12])
407 |> line(end = [24, 0])
408 |> line(end = [0, -24])
409 |> line(end = [-24, 0])
410 |> close()
411 |> extrude(length = 6)
412
413// Remove the end face for the extrusion.
414shell(firstSketch, faces = [END], thickness = 0.25) "#;
415
416 let new = r#"// Remove the end face for the extrusion.
417firstSketch = startSketchOn(XY)
418 |> startProfile(at = [-12, 12])
419 |> line(end = [24, 0])
420 |> line(end = [0, -24])
421 |> line(end = [-24, 0])
422 |> close()
423 |> extrude(length = 6)
424
425// Remove the end face for the extrusion.
426shell(firstSketch, faces = [END], thickness = 0.25)"#;
427
428 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
429
430 let program_new = crate::Program::parse_no_errs(new).unwrap();
431
432 let result = get_changed_program(
433 CacheInformation {
434 ast: &program.ast,
435 settings: &exec_ctxt.settings,
436 },
437 CacheInformation {
438 ast: &program_new.ast,
439 settings: &exec_ctxt.settings,
440 },
441 )
442 .await;
443
444 assert_eq!(result, CacheResult::NoAction(false));
445 exec_ctxt.close().await;
446 }
447
448 #[tokio::test(flavor = "multi_thread")]
449 async fn test_get_changed_program_same_code_changed_code_comment_start_of_program() {
450 let old = r#" // Removed the end face for the extrusion.
451firstSketch = startSketchOn(XY)
452 |> startProfile(at = [-12, 12])
453 |> line(end = [24, 0])
454 |> line(end = [0, -24])
455 |> line(end = [-24, 0])
456 |> close()
457 |> extrude(length = 6)
458
459// Remove the end face for the extrusion.
460shell(firstSketch, faces = [END], thickness = 0.25) "#;
461
462 let new = r#"// Remove the end face for the extrusion.
463firstSketch = startSketchOn(XY)
464 |> startProfile(at = [-12, 12])
465 |> line(end = [24, 0])
466 |> line(end = [0, -24])
467 |> line(end = [-24, 0])
468 |> close()
469 |> extrude(length = 6)
470
471// Remove the end face for the extrusion.
472shell(firstSketch, faces = [END], thickness = 0.25)"#;
473
474 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
475
476 let program_new = crate::Program::parse_no_errs(new).unwrap();
477
478 let result = get_changed_program(
479 CacheInformation {
480 ast: &program.ast,
481 settings: &exec_ctxt.settings,
482 },
483 CacheInformation {
484 ast: &program_new.ast,
485 settings: &exec_ctxt.settings,
486 },
487 )
488 .await;
489
490 assert_eq!(result, CacheResult::NoAction(false));
491 exec_ctxt.close().await;
492 }
493
494 #[tokio::test(flavor = "multi_thread")]
495 async fn test_get_changed_program_same_code_changed_code_comments_attrs() {
496 let old = r#"@foo(whatever = whatever)
497@bar
498// Removed the end face for the extrusion.
499firstSketch = startSketchOn(XY)
500 |> startProfile(at = [-12, 12])
501 |> line(end = [24, 0])
502 |> line(end = [0, -24])
503 |> line(end = [-24, 0]) // my thing
504 |> close()
505 |> extrude(length = 6)
506
507// Remove the end face for the extrusion.
508shell(firstSketch, faces = [END], thickness = 0.25) "#;
509
510 let new = r#"@foo(whatever = 42)
511@baz
512// Remove the end face for the extrusion.
513firstSketch = startSketchOn(XY)
514 |> startProfile(at = [-12, 12])
515 |> line(end = [24, 0])
516 |> line(end = [0, -24])
517 |> line(end = [-24, 0])
518 |> close()
519 |> extrude(length = 6)
520
521// Remove the end face for the extrusion.
522shell(firstSketch, faces = [END], thickness = 0.25)"#;
523
524 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
525
526 let program_new = crate::Program::parse_no_errs(new).unwrap();
527
528 let result = get_changed_program(
529 CacheInformation {
530 ast: &program.ast,
531 settings: &exec_ctxt.settings,
532 },
533 CacheInformation {
534 ast: &program_new.ast,
535 settings: &exec_ctxt.settings,
536 },
537 )
538 .await;
539
540 assert_eq!(result, CacheResult::NoAction(false));
541 exec_ctxt.close().await;
542 }
543
544 #[tokio::test(flavor = "multi_thread")]
546 async fn test_get_changed_program_same_code_but_different_grid_setting() {
547 let new = r#"// Remove the end face for the extrusion.
548firstSketch = startSketchOn(XY)
549 |> startProfile(at = [-12, 12])
550 |> line(end = [24, 0])
551 |> line(end = [0, -24])
552 |> line(end = [-24, 0])
553 |> close()
554 |> extrude(length = 6)
555
556// Remove the end face for the extrusion.
557shell(firstSketch, faces = [END], thickness = 0.25)"#;
558
559 let ExecTestResults {
560 program, mut exec_ctxt, ..
561 } = parse_execute(new).await.unwrap();
562
563 exec_ctxt.settings.show_grid = !exec_ctxt.settings.show_grid;
565
566 let result = get_changed_program(
567 CacheInformation {
568 ast: &program.ast,
569 settings: &Default::default(),
570 },
571 CacheInformation {
572 ast: &program.ast,
573 settings: &exec_ctxt.settings,
574 },
575 )
576 .await;
577
578 assert_eq!(result, CacheResult::NoAction(true));
579 exec_ctxt.close().await;
580 }
581
582 #[tokio::test(flavor = "multi_thread")]
584 async fn test_get_changed_program_same_code_but_different_edge_visibility_setting() {
585 let new = r#"// Remove the end face for the extrusion.
586firstSketch = startSketchOn(XY)
587 |> startProfile(at = [-12, 12])
588 |> line(end = [24, 0])
589 |> line(end = [0, -24])
590 |> line(end = [-24, 0])
591 |> close()
592 |> extrude(length = 6)
593
594// Remove the end face for the extrusion.
595shell(firstSketch, faces = [END], thickness = 0.25)"#;
596
597 let ExecTestResults {
598 program, mut exec_ctxt, ..
599 } = parse_execute(new).await.unwrap();
600
601 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
603
604 let result = get_changed_program(
605 CacheInformation {
606 ast: &program.ast,
607 settings: &Default::default(),
608 },
609 CacheInformation {
610 ast: &program.ast,
611 settings: &exec_ctxt.settings,
612 },
613 )
614 .await;
615
616 assert_eq!(result, CacheResult::NoAction(true));
617
618 let old_settings = exec_ctxt.settings.clone();
620 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
621
622 let result = get_changed_program(
623 CacheInformation {
624 ast: &program.ast,
625 settings: &old_settings,
626 },
627 CacheInformation {
628 ast: &program.ast,
629 settings: &exec_ctxt.settings,
630 },
631 )
632 .await;
633
634 assert_eq!(result, CacheResult::NoAction(true));
635
636 let old_settings = exec_ctxt.settings.clone();
638 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
639
640 let result = get_changed_program(
641 CacheInformation {
642 ast: &program.ast,
643 settings: &old_settings,
644 },
645 CacheInformation {
646 ast: &program.ast,
647 settings: &exec_ctxt.settings,
648 },
649 )
650 .await;
651
652 assert_eq!(result, CacheResult::NoAction(true));
653 exec_ctxt.close().await;
654 }
655
656 #[tokio::test(flavor = "multi_thread")]
659 async fn test_get_changed_program_same_code_but_different_unit_setting_using_annotation() {
660 let old_code = r#"@settings(defaultLengthUnit = in)
661startSketchOn(XY)
662"#;
663 let new_code = r#"@settings(defaultLengthUnit = mm)
664startSketchOn(XY)
665"#;
666
667 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
668
669 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
670 new_program.compute_digest();
671
672 let result = get_changed_program(
673 CacheInformation {
674 ast: &program.ast,
675 settings: &exec_ctxt.settings,
676 },
677 CacheInformation {
678 ast: &new_program.ast,
679 settings: &exec_ctxt.settings,
680 },
681 )
682 .await;
683
684 assert_eq!(
685 result,
686 CacheResult::ReExecute {
687 clear_scene: true,
688 reapply_settings: true,
689 program: new_program.ast,
690 }
691 );
692 exec_ctxt.close().await;
693 }
694
695 #[tokio::test(flavor = "multi_thread")]
698 async fn test_get_changed_program_same_code_but_removed_unit_setting_using_annotation() {
699 let old_code = r#"@settings(defaultLengthUnit = in)
700startSketchOn(XY)
701"#;
702 let new_code = r#"
703startSketchOn(XY)
704"#;
705
706 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
707
708 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
709 new_program.compute_digest();
710
711 let result = get_changed_program(
712 CacheInformation {
713 ast: &program.ast,
714 settings: &exec_ctxt.settings,
715 },
716 CacheInformation {
717 ast: &new_program.ast,
718 settings: &exec_ctxt.settings,
719 },
720 )
721 .await;
722
723 assert_eq!(
724 result,
725 CacheResult::ReExecute {
726 clear_scene: true,
727 reapply_settings: true,
728 program: new_program.ast,
729 }
730 );
731 exec_ctxt.close().await;
732 }
733
734 #[tokio::test(flavor = "multi_thread")]
735 async fn test_multi_file_no_changes_does_not_reexecute() {
736 let code = r#"import "toBeImported.kcl" as importedCube
737
738importedCube
739
740sketch001 = startSketchOn(XZ)
741profile001 = startProfile(sketch001, at = [-134.53, -56.17])
742 |> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
743 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
744 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
745 |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
746 |> close()
747extrude001 = extrude(profile001, length = 100)
748sketch003 = startSketchOn(extrude001, face = seg02)
749sketch002 = startSketchOn(extrude001, face = seg01)
750"#;
751
752 let other_file = (
753 std::path::PathBuf::from("toBeImported.kcl"),
754 r#"sketch001 = startSketchOn(XZ)
755profile001 = startProfile(sketch001, at = [281.54, 305.81])
756 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
757 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
758 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
759 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
760 |> close()
761extrude(profile001, length = 100)"#
762 .to_string(),
763 );
764
765 let tmp_dir = std::env::temp_dir();
766 let tmp_dir = tmp_dir.join(uuid::Uuid::new_v4().to_string());
767
768 let tmp_file = tmp_dir.join(other_file.0);
770 std::fs::create_dir_all(tmp_file.parent().unwrap()).unwrap();
771 std::fs::write(tmp_file, other_file.1).unwrap();
772
773 let ExecTestResults { program, exec_ctxt, .. } =
774 parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
775 .await
776 .unwrap();
777
778 let mut new_program = crate::Program::parse_no_errs(code).unwrap();
779 new_program.compute_digest();
780
781 let result = get_changed_program(
782 CacheInformation {
783 ast: &program.ast,
784 settings: &exec_ctxt.settings,
785 },
786 CacheInformation {
787 ast: &new_program.ast,
788 settings: &exec_ctxt.settings,
789 },
790 )
791 .await;
792
793 let CacheResult::CheckImportsOnly { reapply_settings, .. } = result else {
794 panic!("Expected CheckImportsOnly, got {result:?}");
795 };
796
797 assert_eq!(reapply_settings, false);
798 exec_ctxt.close().await;
799 }
800
801 #[tokio::test(flavor = "multi_thread")]
802 async fn test_cache_multi_file_only_other_file_changes_should_reexecute() {
803 let code = r#"import "toBeImported.kcl" as importedCube
804
805importedCube
806
807sketch001 = startSketchOn(XZ)
808profile001 = startProfile(sketch001, at = [-134.53, -56.17])
809 |> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
810 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
811 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
812 |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
813 |> close()
814extrude001 = extrude(profile001, length = 100)
815sketch003 = startSketchOn(extrude001, face = seg02)
816sketch002 = startSketchOn(extrude001, face = seg01)
817"#;
818
819 let other_file = (
820 std::path::PathBuf::from("toBeImported.kcl"),
821 r#"sketch001 = startSketchOn(XZ)
822profile001 = startProfile(sketch001, at = [281.54, 305.81])
823 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
824 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
825 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
826 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
827 |> close()
828extrude(profile001, length = 100)"#
829 .to_string(),
830 );
831
832 let other_file2 = (
833 std::path::PathBuf::from("toBeImported.kcl"),
834 r#"sketch001 = startSketchOn(XZ)
835profile001 = startProfile(sketch001, at = [281.54, 305.81])
836 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
837 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
838 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
839 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
840 |> close()
841extrude(profile001, length = 100)
842|> translate(z=100)
843"#
844 .to_string(),
845 );
846
847 let tmp_dir = std::env::temp_dir();
848 let tmp_dir = tmp_dir.join(uuid::Uuid::new_v4().to_string());
849
850 let tmp_file = tmp_dir.join(other_file.0);
852 std::fs::create_dir_all(tmp_file.parent().unwrap()).unwrap();
853 std::fs::write(&tmp_file, other_file.1).unwrap();
854
855 let ExecTestResults { program, exec_ctxt, .. } =
856 parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
857 .await
858 .unwrap();
859
860 std::fs::write(tmp_file, other_file2.1).unwrap();
862
863 let mut new_program = crate::Program::parse_no_errs(code).unwrap();
864 new_program.compute_digest();
865
866 let result = get_changed_program(
867 CacheInformation {
868 ast: &program.ast,
869 settings: &exec_ctxt.settings,
870 },
871 CacheInformation {
872 ast: &new_program.ast,
873 settings: &exec_ctxt.settings,
874 },
875 )
876 .await;
877
878 let CacheResult::CheckImportsOnly { reapply_settings, .. } = result else {
879 panic!("Expected CheckImportsOnly, got {result:?}");
880 };
881
882 assert_eq!(reapply_settings, false);
883 exec_ctxt.close().await;
884 }
885
886 #[tokio::test(flavor = "multi_thread")]
887 async fn test_get_changed_program_added_outer_attribute() {
888 let old_code = r#"import "tests/inputs/cube.step"
889"#;
890 let new_code = r#"@(coords = opengl)
891import "tests/inputs/cube.step"
892"#;
893
894 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
895
896 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
897 new_program.compute_digest();
898
899 let result = get_changed_program(
900 CacheInformation {
901 ast: &program.ast,
902 settings: &exec_ctxt.settings,
903 },
904 CacheInformation {
905 ast: &new_program.ast,
906 settings: &exec_ctxt.settings,
907 },
908 )
909 .await;
910
911 assert_eq!(
912 result,
913 CacheResult::ReExecute {
914 clear_scene: true,
915 reapply_settings: false,
916 program: new_program.ast,
917 }
918 );
919 exec_ctxt.close().await;
920 }
921
922 #[tokio::test(flavor = "multi_thread")]
923 async fn test_get_changed_program_different_outer_attribute() {
924 let old_code = r#"@(coords = vulkan)
925import "tests/inputs/cube.step"
926"#;
927 let new_code = r#"@(coords = opengl)
928import "tests/inputs/cube.step"
929"#;
930
931 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
932
933 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
934 new_program.compute_digest();
935
936 let result = get_changed_program(
937 CacheInformation {
938 ast: &program.ast,
939 settings: &exec_ctxt.settings,
940 },
941 CacheInformation {
942 ast: &new_program.ast,
943 settings: &exec_ctxt.settings,
944 },
945 )
946 .await;
947
948 assert_eq!(
949 result,
950 CacheResult::ReExecute {
951 clear_scene: true,
952 reapply_settings: false,
953 program: new_program.ast,
954 }
955 );
956 exec_ctxt.close().await;
957 }
958}