Skip to content

Commit

Permalink
feat: documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
eerii committed Aug 2, 2024
1 parent 7f1df94 commit 4b9e8ad
Show file tree
Hide file tree
Showing 29 changed files with 355 additions and 214 deletions.
48 changes: 0 additions & 48 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 0 additions & 54 deletions macros/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ edition = "2021"
proc-macro = true

[dependencies]
darling = { version = "0.20" }
quote = { version = "1.0" }
syn = { version = "2.0" }
proc-macro2 = { version = "1.0" }
62 changes: 41 additions & 21 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
// For a more complete solution, look at https://github.com/umut-sahin/bevy-persistent
//! Procedural macro helpers for some game systems.
#![warn(missing_docs)]

use darling::{ast::NestedMeta, FromMeta};
use proc_macro as pm;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse2, Data, DeriveInput, Meta};

const DATA_PATH: &str = ".data";

#[derive(FromMeta)]
struct PersistentArgs {
name: String,
}

/// Declares a bevy resource that can serialize data locally and persist it
/// between game restarts.
///
/// # Examples
///
/// ```
/// pub fn plugin(app: &mut App) {
/// app.insert_resource(SomeData::load());
/// }
///
/// #[persistent]
/// #[derive(Default)]
/// pub struct SomeData {
/// pub test: bool,
/// }
/// ```
#[proc_macro_attribute]
pub fn persistent(args: pm::TokenStream, input: pm::TokenStream) -> pm::TokenStream {
pub fn persistent(_: pm::TokenStream, input: pm::TokenStream) -> pm::TokenStream {
let input: TokenStream = input.into();
let DeriveInput { ident, .. } = parse2(input.clone()).unwrap();

let attr_args = match NestedMeta::parse_meta_list(args.into()) {
Ok(v) => v,
Err(e) => {
return pm::TokenStream::from(darling::Error::from(e).write_errors());
},
};
let args = match PersistentArgs::from_list(&attr_args) {
Ok(v) => v,
Err(e) => {
return pm::TokenStream::from(e.write_errors());
},
};
let path = format!("{}/{}.toml", DATA_PATH, args.name);
let name = ident.to_string();
let path = format!("{}/{}.toml", DATA_PATH, name.to_lowercase());

// TODO: Wasm support
let output = quote! {
Expand Down Expand Up @@ -73,11 +73,31 @@ pub fn persistent(args: pm::TokenStream, input: pm::TokenStream) -> pm::TokenStr
output.into()
}

/// Helper macro to allow deriving `asset` attributes for struct fields.
/// There may be better ways to do this.
#[proc_macro_derive(AssetAttr, attributes(asset))]
pub fn derive_asset_attr(_item: pm::TokenStream) -> pm::TokenStream {
pm::TokenStream::new()
}

/// Defines an `AssetKey`, a collection of assets of the same type that are
/// loaded together.
///
/// # Examples
///
/// ```ignore
/// use game::prelude::*;
///
/// pub fn plugin(app: &mut App) {
/// app.load_asset::<SomeAssetKey>();
/// }
///
/// #[asset_key(Image)]
/// pub enum SomeAssetKey {
/// #[asset = "some/asset.png"]
/// SomeVariant,
/// }
/// ```
#[proc_macro_attribute]
pub fn asset_key(args: pm::TokenStream, input: pm::TokenStream) -> pm::TokenStream {
let input: TokenStream = input.into();
Expand Down
85 changes: 70 additions & 15 deletions src/assets.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! `Asset`s represent external files that are loaded into the game. This module
//! provides helpful functions for handling assets, loading them automatically
//! and making them easily available.
use std::{
any::TypeId,
sync::{LazyLock, Mutex},
Expand All @@ -8,19 +12,23 @@ use bevy::reflect::{GetTypeRegistration, ReflectFromPtr};
use crate::prelude::*;

#[cfg(feature = "embedded")]
pub(crate) mod embedded;
mod fonts;
mod meta;
mod music;
mod sound;

pub mod embedded;
pub mod fonts;
pub mod meta;
pub mod music;
pub mod sound;

/// Keeps track of all of the registered asset collections that have not yet
/// been loaded. Used to query the loading state of the assets and transition
/// into the next game state.
static ASSET_MAP: LazyLock<Mutex<Vec<TypeId>>> = LazyLock::new(|| Mutex::new(vec![]));

pub(super) fn plugin(app: &mut App) {
app.add_plugins((fonts::plugin, meta::plugin, music::plugin, sound::plugin))
.add_systems(Update, check_loaded.run_if(in_state(GameState::Startup)));
}

/// The prelude of this module.
pub mod prelude {
pub use super::{
fonts::FontAssetKey,
Expand All @@ -33,19 +41,39 @@ pub mod prelude {
};
}

/// Represents a handle to any asset type
// Resources
// ---

/// A resource that holds asset `Handle`s for a particular type of `AssetKey`.
///
/// # Example
///
/// ```ignore
/// use game::prelude::*;
///
/// #[asset_key(Image)]
/// pub enum SomeAssetKey {
/// #[asset = "some/asset.png"]
/// SomeAsset,
/// }
///
/// // Query the asset map in any system
/// fn system(some_assets: Res<AssetMap<SomeAssetKey>>) {
/// let asset = some_assets.get(&SomeAssetKey::SomeAsset).clone_weak();
/// }
/// ```
#[derive(Resource, Reflect, Deref, DerefMut)]
#[reflect(AssetsLoaded)]
pub struct AssetMap<K: AssetKey>(HashMap<K, Handle<K::Asset>>);

/// Represents a handle to a collection of assets of a certain type type.
pub trait AssetKey:
Sized + Eq + std::hash::Hash + Reflect + FromReflect + TypePath + GetTypeRegistration
{
/// The type of the assets in this collection.
type Asset: Asset;
}

/// A resource that holds asset `Handle`s for a particular type of `AssetKey`
/// Easy to access on any system using `Res<AssetMap<...>>`
#[derive(Resource, Reflect, Deref, DerefMut)]
#[reflect(AssetsLoaded)]
pub struct AssetMap<K: AssetKey>(HashMap<K, Handle<K::Asset>>);

impl<K: AssetKey, T> From<T> for AssetMap<K>
where
T: Into<HashMap<K, Handle<K::Asset>>>,
Expand All @@ -56,15 +84,16 @@ where
}

impl<K: AssetKey> AssetMap<K> {
/// Returns a weak clone of the asset handle
/// Returns a weak clone of the asset handle.
pub fn get(&self, key: &K) -> Handle<K::Asset> {
self[key].clone_weak()
}
}

/// Local trait to query the loading state of all of the asset maps.
#[reflect_trait]
trait AssetsLoaded {
/// Check if all of the assets are loaded
/// Check if all of the assets are loaded.
fn all_loaded(&self, asset_server: &AssetServer) -> bool;
}

Expand All @@ -75,7 +104,28 @@ impl<K: AssetKey> AssetsLoaded for AssetMap<K> {
}
}

/// Helpers
/// ---
/// Commodity function to create an asset map from a key in the app.
///
/// # Examples
///
/// ```ignore
/// use game::prelude::*;
///
/// pub fn plugin(app: &mut App) {
/// app.load_asset::<SomeAssetKey>();
/// }
///
/// #[asset_key(Image)]
/// pub enum SomeAssetKey {
/// #[asset = "some/asset.png"]
/// SomeVariant,
/// }
/// ```
pub trait AssetExt {
/// Loads an asset key.
fn load_asset<K: AssetKey>(&mut self) -> &mut Self
where
AssetMap<K>: FromWorld;
Expand All @@ -92,6 +142,9 @@ impl AssetExt for App {
}
}

/// Checks the elements of `ASSET_MAP` to check if they are loaded, and if they
/// are, removes them from it. When there are no resources left to load,
/// progress into the next `GameState`.
fn check_loaded(world: &mut World) {
let mut map = ASSET_MAP.lock().unwrap();
let mut loaded = vec![];
Expand All @@ -115,6 +168,8 @@ fn check_loaded(world: &mut World) {
}
}

/// Checks if an `AssetMap` has finished loading. It has to use some quirky
/// reflection tricks since each asset map is a different type.
fn is_resource_loaded(id: TypeId, world: &World) -> Result<bool> {
// Get world resources
let asset_server = world
Expand Down
Loading

0 comments on commit 4b9e8ad

Please sign in to comment.