diff --git a/compiler-core/Taskfile.yml b/compiler-core/Taskfile.yml index 0c2e4b6b..8dd8a437 100644 --- a/compiler-core/Taskfile.yml +++ b/compiler-core/Taskfile.yml @@ -14,3 +14,8 @@ tasks: - src/lang/tempstr/grammar.rs - src/lang/rich/grammar.rs - src/lang/preset/grammar.rs + + watch: + desc: Run compiler core tests in watch mode + cmds: + - cargo watch -x test diff --git a/compiler-core/src/comp/comp_line.rs b/compiler-core/src/comp/comp_line.rs index b1e7c4f0..c667101c 100644 --- a/compiler-core/src/comp/comp_line.rs +++ b/compiler-core/src/comp/comp_line.rs @@ -287,6 +287,7 @@ impl Compiler { if let Some(i) = ref_stack.last() { if let CompMovement::To { to, .. } = &output.movements[*i] { output.map_coord = to.clone(); + self.coord = to.clone(); } else { unreachable!(); } @@ -1242,7 +1243,7 @@ mod test { "".to_string(), GameCoord(1.0, 2.0, 3.0), ); - let mut compiler = builder.build(); + let mut compiler = builder.clone().build(); test_comp_ok( &mut compiler, @@ -1259,7 +1260,9 @@ mod test { }, ) .await; + assert_eq!(compiler.coord, GameCoord(4.0, 5.0, 6.0)); + let mut compiler = builder.clone().build(); test_comp_ok( &mut compiler, json!({ @@ -1284,6 +1287,7 @@ mod test { ) .await; + let mut compiler = builder.build(); test_comp_err( &mut compiler, json!({ diff --git a/compiler-core/src/comp/comp_movement.rs b/compiler-core/src/comp/comp_movement.rs index 1ce013b6..e6554e4a 100644 --- a/compiler-core/src/comp/comp_movement.rs +++ b/compiler-core/src/comp/comp_movement.rs @@ -15,15 +15,21 @@ pub enum CompMovement { To { /// The target coord to move to to: GameCoord, + /// If the movement is a warp warp: bool, + /// If the movement coord should be excluded /// /// This affects if the map will consider this coord when /// changing view to the line exclude: bool, + /// Optional color to override the color of the line color: Option, + + /// Optional icon at the movement point + icon: Option, }, Push, Pop, @@ -37,6 +43,7 @@ impl CompMovement { warp: false, exclude: false, color: None, + icon: None, } } } @@ -80,6 +87,7 @@ impl Compiler { let mut warp = false; let mut exclude = false; let mut color = None; + let mut icon = None; let mut should_fail = false; @@ -121,6 +129,14 @@ impl Compiler { ))); } }, + prop::ICON => match value { + Value::Array(_) | Value::Object(_) => { + errors.push(CompilerError::InvalidLinePropertyType(format!( + "{prop_name}.{}", prop::ICON + ))); + } + _ => icon = Some(value.coerce_to_string()), + } _ => { errors .push(CompilerError::UnusedProperty(format!("{prop_name}.{key}"))); @@ -142,6 +158,7 @@ impl Compiler { warp, exclude, color, + icon, }) } } @@ -244,6 +261,7 @@ mod test { warp: true, exclude: false, color: None, + icon: None, }) ); assert_eq!(errors, vec![]); @@ -266,6 +284,7 @@ mod test { warp: true, exclude: true, color: None, + icon: None, }) ); assert_eq!(errors, vec![]); @@ -288,6 +307,7 @@ mod test { warp: false, exclude: true, color: Some("red".to_string()), + icon: None, }) ); assert_eq!(errors, vec![]); @@ -309,10 +329,60 @@ mod test { warp: false, exclude: false, color: None, + icon: None, + }) + ); + assert_eq!(errors, vec![]); + + errors.clear(); + assert_eq!( + compiler + .comp_movement( + "", + json!({ + "to": [1, 2, 4], + "icon": "something", + }), + &mut errors + ) + .await, + Some(CompMovement::To { + to: GameCoord(1.0, 2.0, 4.0), + warp: false, + exclude: false, + color: None, + icon: Some("something".to_string()), }) ); assert_eq!(errors, vec![]); + errors.clear(); + assert_eq!( + compiler + .comp_movement( + "te", + json!({ + "to": [1, 2, 4], + "icon": [] + }), + &mut errors + ) + .await, + Some(CompMovement::To { + to: GameCoord(1.0, 2.0, 4.0), + warp: false, + exclude: false, + color: None, + icon: None, + }) + ); + assert_eq!( + errors, + vec![CompilerError::InvalidLinePropertyType( + "te.icon".to_string() + )] + ); + errors.clear(); assert_eq!( compiler diff --git a/compiler-core/src/comp/mod.rs b/compiler-core/src/comp/mod.rs index 814f2da9..50e92e25 100644 --- a/compiler-core/src/comp/mod.rs +++ b/compiler-core/src/comp/mod.rs @@ -74,23 +74,23 @@ pub enum CompilerError { /// When a line property type is invalid. /// /// Arg is property name or path - #[error("Line property {0} has invalid type")] + #[error("Line property `{0}` has invalid type")] InvalidLinePropertyType(String), /// When a preset string is malformed, like `foo` or `_foo::` or `_bar, icon: &str) { + let site_origin = util::get_site_origin(); + add_engine_diagnostics(diagnostics, "warning", &format!("Cannot find icon `{icon}`. Icons need to be defined in the config. See {site_origin}/docs/route/config/icons for more details")); +} impl CompLine { /// Execute the line. /// /// Map features will be added to the builder pub async fn exec( - self, + mut self, + project: &RouteMetadata, section_number: usize, line_number: usize, map_builder: &mut MapSectionBuilder, ) -> ExecResult { if let Some(icon) = self.map_icon { + if !project.icons.contains_key(&icon) { + add_missing_icon_diagnostics(&mut self.diagnostics, &icon); + } map_builder.icons.push(MapIcon { id: icon, coord: self.map_coord, line_index: line_number, section_index: section_number, priority: self.map_icon_priority, - }) + }); } async_for!(marker in self.markers, { @@ -42,14 +51,27 @@ impl CompLine { warp, exclude, color, + icon, } => { if warp { map_builder.commit(false); } map_builder.add_coord(color.as_ref().unwrap_or(&self.line_color), &to); + if let Some(icon) = icon { + if !project.icons.contains_key(&icon) { + add_missing_icon_diagnostics(&mut self.diagnostics, &icon); + } + map_builder.icons.push(MapIcon { + id: icon, + coord: to.clone(), + line_index: line_number, + section_index: section_number, + priority: self.map_icon_priority, + }) + } if !exclude { - map_coords.push(to.clone()); + map_coords.push(to); } } CompMovement::Push => { @@ -95,12 +117,14 @@ mod test { warp: false, exclude: false, color: Some("blue".to_string()), + icon: None, }, CompMovement::To { to: GameCoord(3.4, 7.0, 6.0), warp: false, exclude: false, color: Some("red".to_string()), + icon: Some("test icon 1".to_string()), }, CompMovement::Pop, CompMovement::Push, @@ -109,12 +133,14 @@ mod test { warp: false, exclude: false, color: Some("blue".to_string()), + icon: None, }, CompMovement::To { to: GameCoord(3.5, 7.4, 6.2), warp: false, exclude: true, color: Some("red".to_string()), + icon: Some("test icon 2".to_string()), }, CompMovement::Pop, CompMovement::to(GameCoord(1.2, 55.0, 37.8)), @@ -123,6 +149,7 @@ mod test { warp: true, exclude: false, color: None, + icon: None, }, CompMovement::to(GameCoord(1.2, 55.0, 37.8)), ] @@ -203,7 +230,13 @@ mod test { notes: test_notes.clone(), ..Default::default() }; - let exec_line = line.exec(3, 4, &mut map_section).await.unwrap(); + let project = RouteMetadata { + icons: vec![("test-icon".to_string(), "".to_string())] + .into_iter() + .collect(), + ..Default::default() + }; + let exec_line = line.exec(&project, 3, 4, &mut map_section).await.unwrap(); assert_eq!(exec_line.section, 3); assert_eq!(exec_line.index, 4); assert_eq!(exec_line.text, test_text); @@ -222,7 +255,10 @@ mod test { ..Default::default() }; let mut map_section = Default::default(); - let exec_line = test_line.exec(0, 0, &mut map_section).await.unwrap(); + let exec_line = test_line + .exec(&Default::default(), 0, 0, &mut map_section) + .await + .unwrap(); let expected = vec![ GameCoord(3.4, 5.0, 6.0), GameCoord(3.4, 7.0, 6.0), @@ -247,20 +283,47 @@ mod test { color: Some("test marker override".to_string()), }, ], - map_coord: GameCoord(1.2, 0.0, 87.8), + map_coord: GameCoord(1.2, 55.0, 87.8), + movements: create_test_movements(), ..Default::default() }; let mut builder = Default::default(); - test_line.exec(4, 5, &mut builder).await.unwrap(); + let project = RouteMetadata { + icons: vec![ + ("test icon".to_string(), "".to_string()), + ("test icon 1".to_string(), "".to_string()), + ("test icon 2".to_string(), "".to_string()), + ] + .into_iter() + .collect(), + ..Default::default() + }; + test_line.exec(&project, 4, 5, &mut builder).await.unwrap(); assert_eq!( builder.icons, - vec![MapIcon { - id: "test icon".to_string(), - coord: GameCoord(1.2, 0.0, 87.8), - line_index: 5, - section_index: 4, - priority: 3, - }] + vec![ + MapIcon { + id: "test icon".to_string(), + coord: GameCoord(1.2, 55.0, 87.8), + line_index: 5, + section_index: 4, + priority: 3, + }, + MapIcon { + id: "test icon 1".to_string(), + coord: GameCoord(3.4, 7.0, 6.0), + line_index: 5, + section_index: 4, + priority: 3, + }, + MapIcon { + id: "test icon 2".to_string(), + coord: GameCoord(3.5, 7.4, 6.2), + line_index: 5, + section_index: 4, + priority: 3, + }, + ] ); assert_eq!( builder.markers, @@ -290,7 +353,10 @@ mod test { }; let mut map_builder = MapSectionBuilder::default(); map_builder.add_coord("blue", &GameCoord::default()); - test_line.exec(0, 0, &mut map_builder).await.unwrap(); + test_line + .exec(&Default::default(), 0, 0, &mut map_builder) + .await + .unwrap(); let map_section = map_builder.build(); assert_eq!( map_section.lines, @@ -332,7 +398,10 @@ mod test { let mut map_builder = MapSectionBuilder::default(); map_builder.add_coord("blue", &GameCoord::default()); map_builder.add_coord("blue", &GameCoord::default()); - test_line.exec(0, 0, &mut map_builder).await.unwrap(); + test_line + .exec(&Default::default(), 0, 0, &mut map_builder) + .await + .unwrap(); map_builder.add_coord("test color", &GameCoord::default()); let map = map_builder.build(); @@ -377,9 +446,31 @@ mod test { }; let exec_line = test_line - .exec(0, 0, &mut MapSectionBuilder::default()) + .exec(&Default::default(), 0, 0, &mut MapSectionBuilder::default()) .await .unwrap(); assert_eq!(exec_line.split_name.unwrap(), "test1 test test3"); } + + #[tokio::test] + async fn test_missing_icons() { + let test_line = CompLine { + map_icon: Some("test icon".to_string()), + map_coord: GameCoord(1.2, 55.0, 87.8), + movements: create_test_movements(), + ..Default::default() + }; + let mut builder = Default::default(); + let exec_line = test_line + .exec(&Default::default(), 4, 5, &mut builder) + .await + .unwrap(); + assert_eq!(exec_line.diagnostics.len(), 3); + assert_eq!(exec_line.diagnostics[0].msg_type, "warning"); + assert_eq!(exec_line.diagnostics[0].source, "celer/engine"); + assert_eq!(exec_line.diagnostics[1].msg_type, "warning"); + assert_eq!(exec_line.diagnostics[1].source, "celer/engine"); + assert_eq!(exec_line.diagnostics[2].msg_type, "warning"); + assert_eq!(exec_line.diagnostics[2].source, "celer/engine"); + } } diff --git a/compiler-core/src/exec/exec_section.rs b/compiler-core/src/exec/exec_section.rs index 13762d9f..06785dc5 100644 --- a/compiler-core/src/exec/exec_section.rs +++ b/compiler-core/src/exec/exec_section.rs @@ -1,4 +1,4 @@ -use celerctypes::ExecSection; +use celerctypes::{ExecSection, RouteMetadata}; use crate::comp::CompSection; use crate::util::async_for; @@ -11,12 +11,13 @@ impl CompSection { /// Map features will be added to the builder pub async fn exec( self, + project: &RouteMetadata, section_number: usize, map_builder: &mut MapSectionBuilder, ) -> ExecResult { let mut lines = vec![]; async_for!((index, line) in self.lines.into_iter().enumerate(), { - let exec_line = line.exec(section_number, index, map_builder).await?; + let exec_line = line.exec(project, section_number, index, map_builder).await?; lines.push(exec_line); })?; Ok(ExecSection { @@ -42,7 +43,7 @@ mod test { ..Default::default() }; let exec_section = test_section - .exec(1, &mut MapSectionBuilder::default()) + .exec(&Default::default(), 1, &mut MapSectionBuilder::default()) .await .unwrap(); @@ -56,7 +57,7 @@ mod test { ..Default::default() }; let exec_section = test_section - .exec(3, &mut MapSectionBuilder::default()) + .exec(&Default::default(), 3, &mut MapSectionBuilder::default()) .await .unwrap(); assert_eq!(exec_section.lines[0].section, 3); @@ -82,7 +83,7 @@ mod test { }; let exec_section = test_section - .exec(4, &mut MapSectionBuilder::default()) + .exec(&Default::default(), 4, &mut MapSectionBuilder::default()) .await .unwrap(); assert_eq!( @@ -133,7 +134,7 @@ mod test { }; let exec_section = test_section - .exec(4, &mut MapSectionBuilder::default()) + .exec(&Default::default(), 4, &mut MapSectionBuilder::default()) .await .unwrap(); assert_eq!( @@ -188,7 +189,10 @@ mod test { let mut builder = MapSectionBuilder::default(); builder.add_coord("test", &GameCoord(1.0, 1.0, 3.0)); - let exec_section = test_section.exec(4, &mut builder).await.unwrap(); + let exec_section = test_section + .exec(&Default::default(), 4, &mut builder) + .await + .unwrap(); assert_eq!( exec_section.map.lines, diff --git a/compiler-core/src/exec/mod.rs b/compiler-core/src/exec/mod.rs index ccdb6d30..730d8e34 100644 --- a/compiler-core/src/exec/mod.rs +++ b/compiler-core/src/exec/mod.rs @@ -7,6 +7,7 @@ use std::convert::Infallible; mod exec_line; +use celerctypes::DocDiagnostic; pub use exec_line::*; mod exec_map; pub use exec_map::*; @@ -15,6 +16,7 @@ pub use exec_section::*; mod exec_doc; pub use exec_doc::*; +use crate::lang::parse_poor; #[cfg(feature = "wasm")] use crate::util::WasmError; @@ -41,3 +43,11 @@ impl From for ExecError { unreachable!() } } + +pub fn add_engine_diagnostics(diagnostics: &mut Vec, msg_type: &str, msg: &str) { + diagnostics.push(DocDiagnostic { + msg: parse_poor(msg), + msg_type: msg_type.to_string(), + source: "celer/engine".to_string(), + }); +} diff --git a/compiler-core/src/pack/resource/loader_cache.rs b/compiler-core/src/pack/resource/loader_cache.rs index f5595147..df5e3491 100644 --- a/compiler-core/src/pack/resource/loader_cache.rs +++ b/compiler-core/src/pack/resource/loader_cache.rs @@ -6,19 +6,19 @@ use crate::pack::PackerResult; use super::{ArcLoader, ResourceLoader}; /// A loader that caches loaded resources in memory. The cache is global. -pub struct GlobalCacheLoader { - delegate: ArcLoader, +pub struct GlobalCacheLoader { + delegate: L, } -impl GlobalCacheLoader { - pub fn new(delegate: ArcLoader) -> Self { +impl GlobalCacheLoader { + pub fn new(delegate: L) -> Self { Self { delegate } } } #[cfg_attr(not(feature = "wasm"), async_trait::async_trait)] #[cfg_attr(feature = "wasm", async_trait::async_trait(?Send))] -impl ResourceLoader for GlobalCacheLoader { +impl ResourceLoader for GlobalCacheLoader where L: ResourceLoader { async fn load_raw(&self, r: &str) -> PackerResult> { load_raw_internal(&self.delegate, r).await } @@ -39,10 +39,10 @@ impl ResourceLoader for GlobalCacheLoader { size=256, key="String", convert = r#"{ path.to_string() }"#, - // TTL of 5 minutes - time=300, + // TTL of 10 minutes + time=600, )] -async fn load_raw_internal(loader: &ArcLoader, path: &str) -> PackerResult> { +async fn load_raw_internal(loader: &dyn ResourceLoader, path: &str) -> PackerResult> { loader.load_raw(path).await } @@ -50,10 +50,10 @@ async fn load_raw_internal(loader: &ArcLoader, path: &str) -> PackerResult PackerResult { +async fn load_image_url_internal(loader: &dyn ResourceLoader, path: &str) -> PackerResult { loader.load_image_url(path).await } @@ -61,9 +61,9 @@ async fn load_image_url_internal(loader: &ArcLoader, path: &str) -> PackerResult size=256, key="String", convert = r#"{ path.to_string() }"#, - // TTL of 5 minutes - time=300, + // TTL of 10 minutes + time=600, )] -async fn load_structured_internal(loader: &ArcLoader, path: &str) -> PackerResult { +async fn load_structured_internal(loader: &dyn ResourceLoader, path: &str) -> PackerResult { loader.load_structured(path).await } diff --git a/compiler-core/src/pack/resource/mod.rs b/compiler-core/src/pack/resource/mod.rs index f45a129d..2aebddfa 100644 --- a/compiler-core/src/pack/resource/mod.rs +++ b/compiler-core/src/pack/resource/mod.rs @@ -1,7 +1,5 @@ mod loader; pub use loader::*; -mod loader_cache; -pub use loader_cache::*; mod loader_empty; pub use loader_empty::*; mod resource_github; diff --git a/compiler-wasm/src/loader_cache.rs b/compiler-wasm/src/loader_cache.rs new file mode 100644 index 00000000..4585eeed --- /dev/null +++ b/compiler-wasm/src/loader_cache.rs @@ -0,0 +1,50 @@ +// Currently not used. The implementation is kept for reference. + +use cached::proc_macro::cached; +use serde_json::Value; + +use celerc::pack::{ ResourceLoader, PackerResult}; + + +/// A loader that caches loaded JSON object in memory to skip parsing. The cache is global. +pub struct CachedLoader { + delegate: L, +} + +impl CachedLoader { + pub fn new(delegate: L) -> Self { + Self { delegate } + } + + pub fn inner(&self) -> &L { + &self.delegate + } +} + +#[async_trait::async_trait(?Send)] +impl ResourceLoader for CachedLoader where L: ResourceLoader { + async fn load_raw(&self, r: &str) -> PackerResult> { + self.delegate.load_raw(r).await + } + + async fn load_image_url(&self, path: &str) -> PackerResult { + self.delegate.load_image_url(path).await + } + + async fn load_structured(&self, path: &str) -> PackerResult { + load_structured_internal(&self.delegate, path).await + } +} + +// associative function not supported by cached crate +// so we need to use helpers + +#[cached( + size=512, + key="String", + convert = r#"{ path.to_string() }"#, + time=301, +)] +async fn load_structured_internal(loader: &dyn ResourceLoader, path: &str) -> PackerResult { + loader.load_structured(path).await +} diff --git a/docs/src/route/customizing-movements.md b/docs/src/route/customizing-movements.md index 6be4256b..ab4f75db 100644 --- a/docs/src/route/customizing-movements.md +++ b/docs/src/route/customizing-movements.md @@ -37,6 +37,7 @@ All properties except for `to` are optional. |`warp`|`boolean`|If `true`, no line is drawn from previous point to this point| |`exclude`|`boolean`|If `true`, this point is not considered part of the line when fitting the map to document. (Has no effect on the map line itself)| |`color`|`string`|Similar to the `color` property for the line, change the line color from this point on until next line| +|`icon`|`string`|ID of an icon to show at the movement point. The icon will inherit the priority of the line.| Examples: ```yaml @@ -56,6 +57,7 @@ movements: - to: [30, 40] warp: true + icon: chest ``` ## Movement Stack diff --git a/web-client/src/low/fetch.ts b/web-client/src/low/fetch.ts index 4570cce2..001809b7 100644 --- a/web-client/src/low/fetch.ts +++ b/web-client/src/low/fetch.ts @@ -5,9 +5,7 @@ export const fetchAsBytes = async (url: string): Promise => { let error: unknown; for (let i = 0; i < RETRY_COUNT; i++) { try { - const response = await fetch(url, { - cache: "reload", - }); + const response = await fetch(url); if (response.ok) { const buffer = await response.arrayBuffer(); return new Uint8Array(buffer);