From c9ad39496444356f022ebefa256a1584ebc8234b Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Sun, 19 Nov 2023 04:00:47 +0000
Subject: [PATCH] Update JSON Loader to assets v2

---
 Cargo.toml                    |  4 +-
 README.md                     |  2 +
 src/fqpath.rs                 | 13 ++++-
 src/json_loader/components.rs | 97 ++++++++++++++++++++++++++---------
 src/json_loader/context.rs    |  2 +
 src/lib.rs                    | 12 ++---
 6 files changed, 98 insertions(+), 32 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index fe6bde3..4f5979e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "micro_games_macros"
-version = "0.1.1"
+version = "0.2.0"
 edition = "2021"
 authors = ["Louis Capitanchik <contact@louiscap.co>"]
 description = "Utility macros to make it easier to build complex systems with Bevy"
@@ -28,7 +28,7 @@ anyhow = "1.0.72"
 
 
 [dev-dependencies.bevy]
-version = "0.11.0"
+version = "0.12.0"
 default-features = false
 features = [
 	"bevy_asset",
diff --git a/README.md b/README.md
index 1dc3028..04dfdd0 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,8 @@
 
 A collection of utility macros for building games
 
+**Current Version Support: 0.2.x -> Bevy 0.12**
+
 ## Macros
 
 For executable examples, visit the rustdoc and/or read the doctests in `src/lib.rs`
diff --git a/src/fqpath.rs b/src/fqpath.rs
index 1fa9809..7d9b5c7 100644
--- a/src/fqpath.rs
+++ b/src/fqpath.rs
@@ -24,7 +24,15 @@ fq!(FQDefault => ::core::default::Default);
 fq!(FQSend => ::core::marker::Send);
 fq!(FQSync => ::core::marker::Sync);
 fq!(FQFrom => ::std::convert::From);
+fq!(FQIOError => ::std::io::Error);
+fq!(FQError => ::std::error::Error);
+fq!(FQFormatter => ::std::fmt::Formatter);
+fq!(FQFormatResult => ::std::fmt::Result);
+fq!(FQOption => ::std::option::Option);
+fq!(FQResult => ::std::result::Result);
+fq!(FQBox => ::std::boxed::Box);
 
+fq!(SerdeJsonError => ::serde_json::Error);
 fq!(ImportBevyPrelude => use ::bevy::prelude::*);
 
 fq!(BevyApp => ::bevy::app::App);
@@ -40,6 +48,7 @@ fq!(BevyTypeUuid => ::bevy::reflect::TypeUuid);
 fq!(BevyDeref => ::bevy::prelude::Deref);
 fq!(BevyDerefMut => ::bevy::prelude::DerefMut);
 fq!(BevyHandle => ::bevy::asset::Handle);
+fq!(BevyAsset => ::bevy::asset::Asset);
 fq!(BevyAssets => ::bevy::asset::Assets);
 fq!(BevyAssetEvent => ::bevy::asset::AssetEvent);
 fq!(BevyAssetLoader => ::bevy::asset::AssetLoader);
@@ -47,7 +56,9 @@ fq!(BevyBoxedFuture => ::bevy::asset::BoxedFuture);
 fq!(BevyLoadContext => ::bevy::asset::LoadContext);
 fq!(BevyLoadedAsset => ::bevy::asset::LoadedAsset);
 fq!(BevyAssetServer => ::bevy::asset::AssetServer);
-fq!(BevyAddAsset => ::bevy::asset::AddAsset);
+fq!(BevyAddAsset => ::bevy::asset::AssetApp);
+fq!(BevyAsyncRead => ::bevy::asset::AsyncReadExt);
+fq!(BevyAssetReader => ::bevy::asset::io::Reader);
 
 #[cfg(feature = "kayak")]
 fq!(KayakWidget => ::kayak_ui::prelude::Widget);
diff --git a/src/json_loader/components.rs b/src/json_loader/components.rs
index 2c8cff1..7b331b3 100644
--- a/src/json_loader/components.rs
+++ b/src/json_loader/components.rs
@@ -10,6 +10,7 @@ pub fn json_loader(input: DeriveInput) -> TokenStream {
 		Err(stream) => return stream,
 	};
 
+	let error_type = define_error_type(&context);
 	let asset_set = define_loading_type(&context);
 	let index_type = define_index_type(&context);
 	let plugin = define_plugin(&context);
@@ -17,6 +18,7 @@ pub fn json_loader(input: DeriveInput) -> TokenStream {
 	let load_handler = define_load_handler(&context);
 
 	quote! {
+		#error_type
 		#asset_set
 		#index_type
 		#loader
@@ -56,57 +58,105 @@ pub fn define_index_type(
 	}: &IdentContext,
 ) -> TokenStream {
 	quote! {
-		#[derive(#FQDebug, #BevyTypePath, #BevyTypeUuid, #BevyDeref, #BevyDerefMut)]
+		#[derive(#FQDebug, #BevyTypePath, #BevyTypeUuid, #BevyDeref, #BevyDerefMut, #BevyAsset)]
 		#[uuid = #uuid]
 		#vis struct #index_name(pub #FQHashMap<String, #BevyHandle<#asset_name>>);
 	}
 }
 
+pub fn define_error_type(IdentContext { error_name, .. }: &IdentContext) -> TokenStream {
+	quote! {
+		#[derive(#FQDebug)]
+		pub enum #error_name {
+			Io(#FQIOError),
+			Json(#SerdeJsonError)
+		}
+
+		impl #FQDisplay for #error_name {
+			fn fmt(&self, f: &mut #FQFormatter<'_>) -> #FQFormatResult {
+				match self {
+					#error_name::Io(err) => err.fmt(f),
+					#error_name::Json(err) => err.fmt(f),
+				}
+			}
+		}
+
+		impl #FQError for #error_name {
+			fn source(&self) -> #FQOption<&(dyn #FQError + 'static)> {
+				match self {
+					#error_name::Io(err) => Some(err),
+					#error_name::Json(err) => Some(err),
+				}
+			}
+		}
+
+		impl #FQFrom<#FQIOError> for #error_name {
+			fn from(value: #FQIOError) -> #error_name {
+				#error_name::Io(value)
+			}
+		}
+
+		impl #FQFrom<#SerdeJsonError> for #error_name {
+			fn from(value: #SerdeJsonError) -> #error_name {
+				#error_name::Json(value)
+			}
+		}
+	}
+}
+
 pub fn define_loader(
 	IdentContext {
 		vis,
 		loader_name,
 		index_name,
 		set_name,
+		error_name,
 		id_field,
 		extension,
 		..
 	}: &IdentContext,
 ) -> TokenStream {
 	quote! {
+		#[derive(#FQDefault)]
 		#vis struct #loader_name;
 		impl #BevyAssetLoader for #loader_name {
+			type Asset = #index_name;
+			type Settings = ();
+			type Error = #error_name;
+
 			fn load<'a>(
 				&'a self,
-				bytes: &'a [u8],
+				mut reader: &'a mut #BevyAssetReader,
+				_settings: &'a Self::Settings,
 				load_context: &'a mut #BevyLoadContext,
-			) -> #BevyBoxedFuture<'a, ::anyhow::Result<()>> {
-				Box::pin(async {
-					let data: #set_name = ::serde_json::from_slice(bytes)?;
+			) -> #BevyBoxedFuture<'a, #FQResult<Self::Asset, Self::Error>> {
+				#FQBox::pin(async move {
+					let mut bytes = #FQVec::new();
+					#BevyAsyncRead::read_to_end(&mut reader, &mut bytes).await?;
+					let data: #set_name = ::serde_json::from_slice(bytes.as_slice())?;
 
 					let mut asset_map = #FQHashMap::new();
 					match data {
 						#set_name::One(single_asset) => {
 							let asset_id = format!("{}", &single_asset.#id_field);
-							let handle = load_context.set_labeled_asset(
-								asset_id.as_str(),
-								#BevyLoadedAsset::new(single_asset),
+							let handle = load_context.add_labeled_asset(
+								asset_id.clone(),
+								single_asset,
 							);
 							asset_map.insert(asset_id, handle);
 						}
 						#set_name::Many(asset_list) => {
 							for single_asset in asset_list.into_iter() {
 								let asset_id = format!("{}", &single_asset.#id_field);
-								let handle = load_context.set_labeled_asset(
-									asset_id.as_str(),
-									#BevyLoadedAsset::new(single_asset),
+								let handle = load_context.add_labeled_asset(
+									asset_id.clone(),
+									single_asset,
 								);
 								asset_map.insert(asset_id, handle);
 							}
 						}
 					}
-					load_context.set_default_asset(#BevyLoadedAsset::new(#index_name(asset_map)));
-					Ok(())
+					Ok(#index_name(asset_map))
 				})
 			}
 
@@ -132,9 +182,9 @@ pub fn define_plugin(
 		#vis struct #plugin_name;
 		impl #BevyPlugin for #plugin_name {
 			fn build(&self, app: &mut #BevyApp) {
-				#BevyAddAsset::add_asset::<#asset_name>(app);
-				#BevyAddAsset::add_asset::<#index_name>(app);
-				#BevyAddAsset::add_asset_loader(app, #loader_name);
+				#BevyAddAsset::init_asset::<#asset_name>(app);
+				#BevyAddAsset::init_asset::<#index_name>(app);
+				#BevyAddAsset::init_asset_loader::<#loader_name>(app);
 				app.add_systems(#BevyUpdate, #handler_name);
 			}
 		}
@@ -163,21 +213,22 @@ pub fn define_load_handler(
 
 			for event in events.iter() {
 				match event {
-					#BevyAssetEvent::Created { handle } | #BevyAssetEvent::Modified { handle } => {
+					#BevyAssetEvent::LoadedWithDependencies { id } | #BevyAssetEvent::Added { id } | #BevyAssetEvent::Modified { id } => {
+						let handle = #BevyHandle::Weak(*id);
 						if let Some(asset_container) = asset_data.get(handle) {
 							for (id, handle) in asset_container.iter() {
 								asset_storage.#storage_asset_name.insert(id.clone(), handle.clone());
 							}
 						}
 					}
-					#BevyAssetEvent::Removed { handle } => {
-						asset_storage.#storage_index_name.iter().for_each(|(id, stored)| {
-							if stored == handle {
+					#BevyAssetEvent::Removed { id } => {
+						asset_storage.#storage_index_name.iter().for_each(|(key, stored)| {
+							if stored.id() == *id {
 								if let Some(asset_container) = asset_data.get(stored) {
-									for (id, _) in asset_container.iter() {
-										removed_assets.push(id.clone());
+									for (key, _) in asset_container.iter() {
+										removed_assets.push(key.clone());
 									}
-									removed_containers.push(id.clone());
+									removed_containers.push(key.clone());
 								}
 							}
 						});
diff --git a/src/json_loader/context.rs b/src/json_loader/context.rs
index a6930d6..9ec8c38 100644
--- a/src/json_loader/context.rs
+++ b/src/json_loader/context.rs
@@ -13,6 +13,7 @@ pub struct IdentContext {
 	pub plugin_name: Ident,
 	pub loader_name: Ident,
 	pub handler_name: Ident,
+	pub error_name: Ident,
 	// -- Property names
 	pub id_field: Ident,
 	pub storage_type: TokenStream,
@@ -105,6 +106,7 @@ impl IdentContext {
 			loader_name: ident_suffix(&ident, "Loader"),
 			set_name: ident_suffix(&ident, "Set"),
 			plugin_name: ident_suffix(&ident, "Plugin"),
+			error_name: ident_suffix(&ident, "Error"),
 			handler_name: ident_suffix(&snake_case_ident(&ident), "_load_handler"),
 			asset_name: ident.clone(),
 
diff --git a/src/lib.rs b/src/lib.rs
index e58555f..4a5d491 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,7 +5,7 @@
 //! to the generated asset system
 //!
 //! ```rust
-//! use bevy::prelude::{App, DefaultPlugins, Image, Plugin, Res, ResMut, Resource, Assets, TextureAtlas};
+//! use bevy::prelude::{App, DefaultPlugins, Image, Plugin, Res, ResMut, Resource, Assets, Asset, TextureAtlas};
 //! use bevy::reflect::{TypePath, TypeUuid};
 //! use micro_games_macros::{asset_system, JsonLoader};
 //! use serde::{Deserialize, Serialize};
@@ -38,7 +38,7 @@
 //!     some_index_of_resources: MyCoolResourceIndex,
 //! }
 //!
-//! #[derive(JsonLoader, TypePath, TypeUuid, Serialize, Deserialize)]
+//! #[derive(JsonLoader, TypePath, TypeUuid, Serialize, Deserialize, Asset)]
 //! #[loader(extension = "mcr", uuid = "00000000-0000-0000-0000-000000000000",
 //!      asset_name = some_resource, index_name = some_index_of_resources)]
 //! #[uuid = "10000000-0000-0000-0000-000000000001"]
@@ -128,7 +128,7 @@ pub(crate) mod std_traits;
 ///
 /// ```rust
 /// # use std::collections::HashMap;
-/// # use bevy::asset::Handle;
+/// # use bevy::asset::{Handle, Asset};
 /// # use bevy::prelude::Resource;
 /// # use bevy::reflect::{TypePath, TypeUuid};
 /// # use serde::{Deserialize, Serialize};
@@ -140,7 +140,7 @@ pub(crate) mod std_traits;
 ///     simple_asset_index: HashMap<String, Handle<SimpleAssetIndex>>,
 /// }
 ///
-/// #[derive(JsonLoader, TypePath, TypeUuid, Serialize, Deserialize)]
+/// #[derive(JsonLoader, TypePath, TypeUuid, Serialize, Deserialize, Asset)]
 /// #[loader(extension = "satt", uuid = "00000000-0000-0000-0000-000000000000")]
 /// #[uuid = "00000000-0000-0000-0000-000000000001"]
 /// pub struct SimpleAsset {
@@ -159,13 +159,13 @@ pub(crate) mod std_traits;
 ///
 /// ```rust
 /// # use std::collections::HashMap;
-/// # use bevy::asset::Handle;
+/// # use bevy::asset::{Handle, Asset};
 /// # use bevy::prelude::Resource;
 /// # use bevy::reflect::{TypePath, TypeUuid};
 /// # use serde::{Deserialize, Serialize};
 /// # use micro_games_macros::JsonLoader;
 ///
-/// #[derive(JsonLoader, TypePath, TypeUuid, Serialize, Deserialize)]
+/// #[derive(JsonLoader, TypePath, TypeUuid, Serialize, Deserialize, Asset)]
 /// #[loader(
 ///     extension = "asset.json", uuid = "00000000-0000-0000-0000-000000000000",
 ///     storage = inner_module::SimpleAssetLocator,
-- 
GitLab