Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Load Progress Tracking #164

Merged
merged 2 commits into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions Cargo.lock

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

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ authors = ["The Fish Fight Game & Spicy Lobster Developers"]
license = "MIT OR Apache-2.0"
edition = "2021"

[workspace]
members = [
".",
"macros"
]

[dependencies]
punchy_macros = { path = "./macros" }

anyhow = "1.0.58"
bevy = "0.7.0"
bevy-parallax = "0.1.2"
Expand Down
12 changes: 12 additions & 0 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "punchy_macros"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
syn = "1.0.98"
quote = "1.0.20"
proc-macro2 = "1.0.42"
111 changes: 111 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, quote_spanned};
use syn::{parse_quote, spanned::Spanned};

/// Derive macro for `HasLoadProgress`
///
/// May be used to implement `HasLoadProgress` on structs where all fields implement
/// `HasLoadProgress`.
///
/// Fields not implementing `HasLoadProgress` may be skipped with `#[has_load_progress(none)]` added
/// to the field.
///
/// `#[has_load_progress(none)]` may also be added to the struct itself to use the default
/// implementation of `HasLoadProgress` which returns no progress and nothing to load.
///
/// `#[has_load_progress(none)]` could also be added to enums to derive the default implementation
/// with no load progress, but cannot be added to enum variants.
#[proc_macro_derive(HasLoadProgress, attributes(has_load_progress))]
pub fn has_load_progress(input: TokenStream) -> TokenStream {
let input = syn::parse(input).unwrap();

impl_has_load_progress(&input).into()
}

fn impl_has_load_progress(input: &syn::DeriveInput) -> TokenStream2 {
// The attribute that may be added to skip fields or use the default implementation.
let no_load_attr: syn::Attribute = parse_quote! {
#[has_load_progress(none)]
};

let item_ident = &input.ident;
let mut impl_function_body = quote! {};

// Check for `#[has_load_progress(none)]` on the item itself
let mut skip_all_fields = false;
for attr in &input.attrs {
if attr.path == parse_quote!(has_load_progress) {
if attr == &no_load_attr {
skip_all_fields = true;
} else {
return quote_spanned!(attr.span() =>
compile_error!("Attribute must be `#[has_load_progress(none)]` if specified");
);
}
}
}

// If we are skipping all fields
if skip_all_fields {
impl_function_body = quote! {
crate::loading::progress::LoadProgress::default()
};

// If we should process struct fields
} else {
// Parse the struct
let in_struct = match &input.data {
syn::Data::Struct(s) => s,
syn::Data::Enum(_) | syn::Data::Union(_) => {
return quote_spanned! { input.ident.span() =>
compile_error!("You may only derive HasLoadProgress on structs");
};
}
};

// Start a list of the progresses for each field
let mut progresses = Vec::new();
'field: for field in &in_struct.fields {
// Skip this field if it has `#[has_load_progress(none)]`
for attr in &field.attrs {
if attr.path == parse_quote!(has_load_progress) {
if attr == &no_load_attr {
continue 'field;
} else {
impl_function_body = quote_spanned! { attr.span() =>
compile_error!("Attribute be `#[has_load_progress(none)]` if specified");
}
}
}
}

// Add this fields load progress to the list of progresses
let field_ident = field.ident.as_ref().expect("Field ident");
progresses.push(quote_spanned! { field_ident.span() =>
crate::loading::progress::HasLoadProgress::load_progress(
&self.#field_ident,
loading_resources
)
})
}

// Retrun the merged progress result
impl_function_body = quote! {
#impl_function_body
crate::loading::progress::LoadProgress::merged([ #( #progresses),* ])
};
}

// Fill out rest of impl block
quote! {
impl crate::loading::progress::HasLoadProgress for #item_ident {
fn load_progress(
&self,
loading_resources: &crate::loading::progress::LoadingResources
) -> crate::loading::progress::LoadProgress {
#impl_function_body
}
}
}
}
49 changes: 37 additions & 12 deletions src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,33 @@ impl AssetLoader for GameMetaLoader {
meta.main_menu.background_image.image_handle = main_menu_background;
dependencies.push(main_menu_background_path);

// Load the music
// Load UI border images
let mut load_border_image = |border: &mut BorderImageMeta| {
let (path, handle) = get_relative_asset(load_context, &self_path, &border.image);
dependencies.push(path);
border.handle = handle;
};
load_border_image(&mut meta.ui_theme.hud.portrait_frame);
load_border_image(&mut meta.ui_theme.panel.border);
load_border_image(&mut meta.ui_theme.hud.lifebar.background_image);
load_border_image(&mut meta.ui_theme.hud.lifebar.progress_image);
for button in meta.ui_theme.button_styles.values_mut() {
load_border_image(&mut button.borders.default);
if let Some(border) = &mut button.borders.clicked {
load_border_image(border);
}
if let Some(border) = &mut button.borders.focused {
load_border_image(border);
}
}

// Load the music
let (music_path, music_handle) =
get_relative_asset(load_context, &self_path, &meta.main_menu.music);
meta.main_menu.music_handle = music_handle;
dependencies.push(music_path);

// Load UI fonts

for (font_name, font_relative_path) in &meta.ui_theme.font_families {
let (font_path, font_handle) =
get_relative_asset(load_context, &self_path, font_relative_path);
Expand Down Expand Up @@ -150,15 +168,6 @@ impl AssetLoader for LevelMetaLoader {

let self_path = load_context.path();

// Convert all parallax paths to relative asset paths so that the convention matches the
// rest of the paths used by the asset loaders.
for layer in &mut meta.parallax_background.layers {
layer.path = relative_asset_path(self_path, &layer.path)
.to_str()
.unwrap()
.to_owned();
}

let mut dependencies = Vec::new();

// Load the players
Expand Down Expand Up @@ -189,8 +198,24 @@ impl AssetLoader for LevelMetaLoader {
item.item_handle = item_handle;
}

// Load the music
// Load parallax background layers
for layer in &mut meta.parallax_background.layers {
let (path, handle) = get_relative_asset(load_context, self_path, &layer.path);

// Update the layer path to use an absolute path so that it matches the conventione
// used by the bevy_parallax_background plugin.
layer.path = path
.path()
.as_os_str()
.to_str()
.expect("utf8-filename")
.to_string();

layer.image_handle = handle;
dependencies.push(path);
}

// Load the music
let (music_path, music_handle) =
get_relative_asset(load_context, self_path, &meta.music);
meta.music_handle = music_handle;
Expand Down
38 changes: 32 additions & 6 deletions src/loading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ use leafwing_input_manager::{
InputManagerBundle,
};

use progress::{HasLoadProgress, LoadingResources};
pub mod progress;

pub struct LoadingPlugin;

impl Plugin for LoadingPlugin {
Expand Down Expand Up @@ -74,6 +77,7 @@ impl Plugin for LoadingPlugin {
}
}

/// System param used to load and hot reload the game
#[derive(SystemParam)]
pub struct GameLoader<'w, 's> {
skip_next_asset_update_event: Local<'s, bool>,
Expand All @@ -82,8 +86,8 @@ pub struct GameLoader<'w, 's> {
game_handle: Res<'w, Handle<GameMeta>>,
assets: ResMut<'w, Assets<GameMeta>>,
egui_ctx: ResMut<'w, EguiContext>,
asset_server: Res<'w, AssetServer>,
events: EventReader<'w, 's, AssetEvent<GameMeta>>,
loading_resources: LoadingResources<'w, 's>,
}

impl<'w, 's> GameLoader<'w, 's> {
Expand All @@ -106,12 +110,22 @@ impl<'w, 's> GameLoader<'w, 's> {
game_handle,
mut assets,
mut egui_ctx,
asset_server,
..
} = self;

if let Some(game) = assets.get_mut(game_handle.clone_weak()) {
debug!("Loaded game");
// Track load progress
let load_progress = game.load_progress(&self.loading_resources);
debug!(
%load_progress,
"Loading game assets: {:.2}% ",
load_progress.as_percent()
);

// Wait until assets are loaded to start game
if load_progress.as_percent() < 1.0 {
return;
}

// Hot reload preparation
if is_hot_reload {
Expand Down Expand Up @@ -164,11 +178,10 @@ impl<'w, 's> GameLoader<'w, 's> {

// Helper to load border images
let mut load_border_image = |border: &mut BorderImageMeta| {
border.handle = asset_server.load(&border.image);
border.egui_texture = egui_ctx.add_image(border.handle.clone_weak());
};

// Load border images
// Add Border images to egui context
load_border_image(&mut game.ui_theme.hud.portrait_frame);
load_border_image(&mut game.ui_theme.panel.border);
load_border_image(&mut game.ui_theme.hud.lifebar.background_image);
Expand Down Expand Up @@ -310,9 +323,22 @@ fn load_level(
game: Res<GameMeta>,
windows: Res<Windows>,
mut storage: ResMut<Storage>,
loading_resources: LoadingResources,
) {
if let Some(level) = assets.get(level_handle.clone_weak()) {
debug!("Loaded level");
// Track load progress
let load_progress = level.load_progress(&loading_resources);
debug!(
%load_progress,
"Loading level assets: {:.2}% ",
load_progress.as_percent()
);

// Wait until assets are loaded to start game
if load_progress.as_percent() < 1.0 {
return;
}

let window = windows.primary();

// Setup the parallax background
Expand Down
Loading