From c8ca444f6ccbdb4befdcd8d2e992f8c612164c3f Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Sat, 20 Aug 2022 18:09:57 +0100
Subject: [PATCH] Update asset loading & CI

---
 .gitlab-ci.yml                      | 64 +++++++++++++++++++-------
 game_core/src/assets/loader.rs      | 39 +++++++++-------
 game_core/src/assets/resources.rs   | 30 ++++++++++++
 game_core/src/system/camera.rs      | 21 ++++++++-
 game_core/src/system/load_config.rs | 71 +++++++++++++++++++++++++++++
 game_core/src/system/mod.rs         |  1 +
 game_core/src/system/resources.rs   | 21 ++-------
 7 files changed, 196 insertions(+), 51 deletions(-)
 create mode 100644 game_core/src/system/load_config.rs

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f253e0a..405afbd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,7 +1,8 @@
 image: "r.lcr.gr/microhacks/bevy-builder:latest"
 
 variables:
-  BINARY_NAME: game
+  BINARY_FOLDER: game_core
+  BINARY_NAME: game_core
 
 stages:
   - build
@@ -21,11 +22,11 @@ build-windows:
       - .cargo/bin/
       - target/
   script:
-    - cargo build --release -p game_core --target x86_64-pc-windows-gnu
+    - cargo build --release -p ${BINARY_FOLDER} --target x86_64-pc-windows-gnu
   artifacts:
     expire_in: 1 day
     paths:
-      - target/x86_64-pc-windows-gnu/release/game_core.exe
+      - target/x86_64-pc-windows-gnu/release/${BINARY_NAME}.exe
   only:
     - trunk
 
@@ -43,11 +44,36 @@ build-linux:
       - .cargo/bin/
       - target/
   script:
-    - cargo build --release -p game_core --target x86_64-unknown-linux-gnu
+    - cargo build --release -p ${BINARY_FOLDER} --target x86_64-unknown-linux-gnu
   artifacts:
     expire_in: 1 day
     paths:
-      - target/x86_64-unknown-linux-gnu/release/game_core
+      - target/x86_64-unknown-linux-gnu/release/${BINARY_NAME}
+  only:
+    - trunk
+
+build-arm64:
+  image: "r.lcr.gr/microhacks/bevy-builder:arm64"
+  tags:
+    - arm64
+  stage: build
+  before_script:
+    - export CARGO_HOME="${CI_PROJECT_DIR}/.cargo"
+    - export PATH="${CI_PROJECT_DIR}/.cargo/bin:$PATH"
+  cache:
+    key: build-cache-arm64
+    paths:
+      - .cargo/registry/cache
+      - .cargo/registry/index
+      - .cargo/git/db
+      - .cargo/bin/
+      - target/
+  script:
+    - cargo build --release -p ${BINARY_FOLDER} --target aarch64-unknown-linux-gnu
+  artifacts:
+    expire_in: 1 day
+    paths:
+      - target/aarch64-unknown-linux-gnu/release/${BINARY_NAME}
   only:
     - trunk
 
@@ -65,13 +91,16 @@ build-web:
       - .cargo/bin/
       - target/
   script:
+    - mkdir -p "${CI_PROJECT_DIR}/${BINARY_FOLDER}/fonts"
+    - mkdir "${CI_PROJECT_DIR}/${BINARY_FOLDER}/dist"
     - make assets
-    - cd "${CI_PROJECT_DIR}/game_core" && trunk build --release
+    - sed -i 's#public_url = "/"#public_url = "./"#' "${CI_PROJECT_DIR}/${BINARY_FOLDER}/Trunk.toml"
+    - cd "${CI_PROJECT_DIR}/${BINARY_FOLDER}" && trunk build --release
     - cd "${CI_PROJECT_DIR}"
   artifacts:
     expire_in: 1 day
     paths:
-      - game_core/dist/
+      - ${BINARY_FOLDER}/dist/
   only:
     - trunk
 
@@ -81,33 +110,34 @@ package-all:
     - mkdir -p dist/
     - make assets
     - cp -r assets dist/assets
-    - cp target/x86_64-unknown-linux-gnu/release/game_core "dist/$BINARY_NAME"
-    - cp target/x86_64-pc-windows-gnu/release/game_core.exe "dist/$BINARY_NAME.exe"
+    - cp target/x86_64-unknown-linux-gnu/release/${BINARY_NAME} "dist/${BINARY_NAME}"
+    - cp target/x86_64-pc-windows-gnu/release/${BINARY_NAME}.exe "dist/${BINARY_NAME}.exe"
+    - cp target/aarch64-unknown-linux-gnu/release/${BINARY_NAME} "dist/${BINARY_NAME}.arm64"
     - cd "${CI_PROJECT_DIR}/dist" && zip -r "windows.zip" "./${BINARY_NAME}.exe" ./assets
-    - cd "${CI_PROJECT_DIR}/dist" && zip -r "linux.zip" "./${BINARY_NAME}" ./assets
-    - ls "${CI_PROJECT_DIR}/game_core/dist"
-    - cd "${CI_PROJECT_DIR}/game_core/dist" && zip -r "web.zip" ./*
-    - cd "${CI_PROJECT_DIR}" && mv "${CI_PROJECT_DIR}/game_core/dist/web.zip" "${CI_PROJECT_DIR}/dist/web.zip"
+    - cd "${CI_PROJECT_DIR}/dist" && zip -r "linux.x86.zip" "./${BINARY_NAME}" ./assets
+    - cd "${CI_PROJECT_DIR}/dist" && zip -r "linux.arm64.zip" "./${BINARY_NAME}.arm64" ./assets
+    - cd "${CI_PROJECT_DIR}/${BINARY_FOLDER}/dist" && zip -r "web.zip" ./*
+    - cd "${CI_PROJECT_DIR}" && mv "${CI_PROJECT_DIR}/advent/dist/web.zip" "${CI_PROJECT_DIR}/dist/web.zip"
   dependencies:
     - build-windows
     - build-linux
+    - build-arm64
     - build-web
   artifacts:
     expire_in: 7 days
     paths:
       - dist/web.zip
       - dist/windows.zip
-      - dist/linux.zip
+      - dist/linux.x86.zip
+      - dist/linux.arm64.zip
   only:
     - trunk
 
 pages:
   stage: package
   script:
-    - ls game_core/
-    - ls game_core/dist/
     - mkdir -p public/
-    - cp -r game_core/dist/* public/
+    - cp -r ${BINARY_FOLDER}/dist/* public/
   artifacts:
     expire_in: 7 days
     paths:
diff --git a/game_core/src/assets/loader.rs b/game_core/src/assets/loader.rs
index 351e338..c5af903 100644
--- a/game_core/src/assets/loader.rs
+++ b/game_core/src/assets/loader.rs
@@ -17,6 +17,26 @@ pub struct AssetTypeLoader<'w, 's> {
 	marker: PhantomData<&'s usize>,
 }
 
+macro_rules! load_basic_type {
+	($name: tt, $type: ty => $key: ident) => {
+		pub fn $name(&mut self, assets: &[FixedAssetNameMapping]) -> Vec<Handle<$type>> {
+			self.load_list(assets, |loader, path, key| {
+				let handle: Handle<$type> = loader.asset_server.load(&path);
+				loader.handles.$key.insert(key, handle.clone());
+				handle
+			})
+		}
+	};
+}
+
+macro_rules! load_state {
+	($container: expr => $key: ident) => {
+		$container
+			.asset_server
+			.get_group_load_state($container.handles.$key.values().map(|f| f.id))
+	};
+}
+
 impl<'w, 's> AssetTypeLoader<'w, 's> {
 	fn load_list<
 		T: Sync + Send + TypeUuid + 'static,
@@ -32,13 +52,9 @@ impl<'w, 's> AssetTypeLoader<'w, 's> {
 			.collect()
 	}
 
-	pub fn load_images(&mut self, assets: &[FixedAssetNameMapping]) -> Vec<Handle<Image>> {
-		self.load_list(assets, |loader, path, key| {
-			let handle: Handle<Image> = loader.asset_server.load(&path);
-			loader.handles.images.insert(key, handle.clone());
-			handle
-		})
-	}
+	load_basic_type!(load_images, Image => images);
+	load_basic_type!(load_audio, AudioSource => sounds);
+	load_basic_type!(load_font, Font => fonts);
 
 	pub fn load_spritesheet(
 		&mut self,
@@ -66,15 +82,6 @@ impl<'w, 's> AssetTypeLoader<'w, 's> {
 			handle
 		})
 	}
-	pub fn load_audio(&mut self, assets: &[FixedAssetNameMapping]) -> Vec<Handle<AudioSource>> {
-		self.load_list(assets, |loader, path, key| {
-			let handle: Handle<AudioSource> = loader.asset_server.load(&path);
-
-			loader.handles.sounds.insert(key, handle.clone());
-
-			handle
-		})
-	}
 
 	pub fn get_all_load_state(&self) -> Vec<LoadState> {
 		let image_state = self
diff --git a/game_core/src/assets/resources.rs b/game_core/src/assets/resources.rs
index fb22eab..34bb68b 100644
--- a/game_core/src/assets/resources.rs
+++ b/game_core/src/assets/resources.rs
@@ -36,6 +36,36 @@ pub struct AssetHandles {
 	pub images: HashMap<String, Handle<Image>>,
 	pub atlas: HashMap<String, Handle<TextureAtlas>>,
 	pub sounds: HashMap<String, Handle<AudioSource>>,
+	pub fonts: HashMap<String, Handle<Font>>,
+}
+
+macro_rules! fetch_wrapper {
+	($name: tt, $type: ty => $key: ident) => {
+		pub fn $name<T: ToString>(&self, name: T) -> Handle<$type> {
+			let key = name.to_string();
+			match self.$key.get(&key) {
+				Some(handle) => handle.clone_weak(),
+				None => {
+					let keys = self.$key.keys();
+					panic!(
+						"\n\nTried to fetch {} asset with a missing key: {}.\nPossible keys: {}\n\n",
+						stringify!($name),
+						name.to_string(),
+						keys.map(|k| format!("'{}'", k))
+							.collect::<Vec<String>>()
+							.join(", ")
+					)
+				}
+			}
+		}
+	};
+}
+
+impl AssetHandles {
+	fetch_wrapper!(image, Image => images);
+	fetch_wrapper!(atlas, TextureAtlas => atlas);
+	fetch_wrapper!(sound, AudioSource => sounds);
+	fetch_wrapper!(font, Font => fonts);
 }
 
 impl SuppliesAudio for AssetHandles {
diff --git a/game_core/src/system/camera.rs b/game_core/src/system/camera.rs
index 0a9a253..e7d082b 100644
--- a/game_core/src/system/camera.rs
+++ b/game_core/src/system/camera.rs
@@ -1,11 +1,14 @@
 use bevy::app::App;
 use bevy::math::{Vec2, Vec3Swizzles};
 use bevy::prelude::{
-	Camera2dBundle, Commands, Component, CoreStage, Entity, Plugin, Query, Transform, With,
+	Camera2dBundle, Commands, Component, CoreStage, Entity, OrthographicProjection, Plugin, Query,
+	Transform, With,
 };
+use bevy::render::camera::ScalingMode;
 use iyes_loopless::prelude::AppLooplessStateExt;
 
 use crate::system::flow::AppState;
+use crate::system::load_config::initial_size;
 
 /// A flag component to indicate which entity should be followed by the camera
 #[derive(Component)]
@@ -16,8 +19,22 @@ pub struct GameCamera;
 
 /// System that creates a default orthographic camera, with correct tags for querying
 pub fn spawn_orthographic_camera(mut commands: Commands) {
+	let (target_width, target_height) = initial_size();
 	commands
-		.spawn_bundle(Camera2dBundle::default())
+		.spawn_bundle(Camera2dBundle {
+			projection: OrthographicProjection {
+				left: -(target_width / 2.0),
+				right: (target_width / 2.0),
+				top: (target_height / 2.0),
+				bottom: -(target_height / 2.0),
+				scaling_mode: ScalingMode::Auto {
+					min_width: target_width,
+					min_height: target_height,
+				},
+				..Default::default()
+			},
+			..Default::default()
+		})
 		.insert(GameCamera);
 }
 
diff --git a/game_core/src/system/load_config.rs b/game_core/src/system/load_config.rs
new file mode 100644
index 0000000..4eba511
--- /dev/null
+++ b/game_core/src/system/load_config.rs
@@ -0,0 +1,71 @@
+#[cfg(not(target_arch = "wasm32"))]
+mod setup {
+	pub fn get_asset_path_string() -> String {
+		std::env::current_dir()
+			.unwrap()
+			.join("assets")
+			.to_str()
+			.unwrap()
+			.to_string()
+	}
+
+	pub fn initial_size() -> (f32, f32) {
+		(1280.0, 720.0)
+	}
+}
+
+#[cfg(target_arch = "wasm32")]
+mod setup {
+	pub fn get_asset_path_string() -> String {
+		String::from("assets")
+	}
+
+	#[cfg(feature = "no_aspect")]
+	pub fn initial_size() -> (f32, f32) {
+		static default_width: f32 = 1280.0;
+		static default_height: f32 = 720.0;
+
+		web_sys::window()
+			.and_then(|window: web_sys::Window| {
+				let w = window
+					.inner_width()
+					.ok()
+					.and_then(|val| val.as_f64().map(|v| v as f32))
+					.unwrap_or(default_width);
+				let h = window
+					.inner_height()
+					.ok()
+					.and_then(|val| val.as_f64().map(|v| v as f32))
+					.unwrap_or(default_height);
+
+				Some((w, h))
+			})
+			.unwrap_or((default_width, default_height))
+	}
+
+	#[cfg(not(feature = "no_aspect"))]
+	pub fn initial_size() -> (f32, f32) {
+		static default_width: f32 = 1280.0;
+		static default_height: f32 = 720.0;
+		static ratio: f32 = 1280.0 / 720.0;
+
+		web_sys::window()
+			.and_then(|window: web_sys::Window| {
+				let w = window
+					.inner_width()
+					.ok()
+					.and_then(|val| val.as_f64().map(|v| v as f32))
+					.unwrap_or(default_width);
+				let h = window
+					.inner_height()
+					.ok()
+					.and_then(|val| val.as_f64().map(|v| v as f32))
+					.unwrap_or(default_height);
+
+				Some((w, h / ratio))
+			})
+			.unwrap_or((default_width, default_height))
+	}
+}
+
+pub use setup::*;
diff --git a/game_core/src/system/mod.rs b/game_core/src/system/mod.rs
index 229ec9f..309ab32 100644
--- a/game_core/src/system/mod.rs
+++ b/game_core/src/system/mod.rs
@@ -1,5 +1,6 @@
 pub mod camera;
 pub mod flow;
+pub mod load_config;
 pub mod resources;
 pub mod utilities;
 pub mod window;
diff --git a/game_core/src/system/resources.rs b/game_core/src/system/resources.rs
index 815c6b8..dfb7d23 100644
--- a/game_core/src/system/resources.rs
+++ b/game_core/src/system/resources.rs
@@ -4,27 +4,16 @@ use bevy::render::texture::ImageSettings;
 use bevy::window::PresentMode;
 
 use crate::system::camera::spawn_orthographic_camera;
-
-#[cfg(target_arch = "wasm32")]
-pub fn get_asset_path_string() -> String {
-	String::from("assets")
-}
-#[cfg(not(target_arch = "wasm32"))]
-pub fn get_asset_path_string() -> String {
-	std::env::current_dir()
-		.unwrap()
-		.join("assets")
-		.to_str()
-		.unwrap()
-		.to_string()
-}
+use crate::system::load_config::{get_asset_path_string, initial_size};
 
 pub struct DefaultResourcesPlugin;
 impl Plugin for DefaultResourcesPlugin {
 	fn build(&self, app: &mut App) {
+		let (width, height) = initial_size();
+
 		app.insert_resource(WindowDescriptor {
-			width: 1280.0,
-			height: 720.0,
+			width,
+			height,
 			resizable: true,
 			title: String::from("Bevy 2D Template"),
 			present_mode: PresentMode::AutoNoVsync,
-- 
GitLab