#![allow(clippy::type_complexity)]
use crate::asset::{Wisp, WispHandle, WispLoader};
use crate::error::WispErrors;
use crate::globals::FrameGlobals;
use crate::inputs::{WispInputs, inputs_from_schema, rematch_inputs};
use bevy_app::prelude::*;
use bevy_asset::prelude::*;
use bevy_camera::RenderTarget;
use bevy_ecs::prelude::*;
use bevy_platform::collections::HashSet;
use bevy_window::{PresentMode, PrimaryWindow, Window, WindowRef};
mod align;
pub mod annot;
pub mod asset;
#[cfg(feature = "audio")]
pub mod audio;
pub mod error;
pub mod globals;
pub mod inputs;
pub mod reflect;
pub mod render;
pub mod schema;
pub mod targets;
#[cfg(feature = "ui")]
pub mod ui;
pub mod prelude {
pub use crate::asset::{Wisp, WispHandle};
#[cfg(feature = "audio")]
pub use crate::audio::WispAudio;
pub use crate::error::WispErrors;
pub use crate::inputs::{WispInputs, WispValue};
pub use crate::schema::WispSchema;
pub use crate::{WispConfig, WispPlugin};
}
#[derive(Resource, Clone, Debug)]
pub struct WispConfig {
pub prefer_mailbox: bool,
pub ui_window: bool,
}
impl Default for WispConfig {
fn default() -> Self {
Self {
prefer_mailbox: true,
ui_window: true,
}
}
}
pub struct WispPlugin;
impl Plugin for WispPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<Wisp>()
.init_asset_loader::<WispLoader>()
.init_resource::<FrameGlobals>()
.init_resource::<WispConfig>()
.init_resource::<WispErrors>()
.add_plugins(render::WispRenderPlugin)
.add_systems(
Update,
(
globals::update_frame_globals,
sync_wisp_inputs,
targets::update_pass_targets,
error::collect_load_errors,
prefer_mailbox_present_mode,
),
);
#[cfg(feature = "audio")]
app.init_resource::<audio::WispAudio>()
.add_systems(Update, audio::update_audio_textures.after(sync_wisp_inputs));
#[cfg(feature = "ui")]
app.add_systems(
Update,
ui::wisp_ui.run_if(resource_exists::<bevy_egui::EguiUserTextures>),
);
}
}
fn prefer_mailbox_present_mode(
config: Res<WispConfig>,
cameras: Query<&RenderTarget, With<WispHandle>>,
primary: Query<Entity, With<PrimaryWindow>>,
mut windows: Query<&mut Window>,
) {
if !config.prefer_mailbox {
return;
}
for target in &cameras {
let RenderTarget::Window(window_ref) = target else {
continue;
};
let entity = match window_ref {
WindowRef::Primary => primary.iter().next(),
WindowRef::Entity(entity) => Some(*entity),
};
let Some(mut window) = entity.and_then(|entity| windows.get_mut(entity).ok()) else {
continue;
};
if window.present_mode == PresentMode::default() {
window.present_mode = PresentMode::Mailbox;
}
}
}
fn sync_wisp_inputs(
mut commands: Commands,
wisps: Res<Assets<Wisp>>,
mut events: MessageReader<AssetEvent<Wisp>>,
mut cameras: Query<(Entity, Ref<WispHandle>, Option<&mut WispInputs>)>,
) {
let updated: HashSet<AssetId<Wisp>> = events
.read()
.filter_map(|event| match event {
AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id } => Some(*id),
_ => None,
})
.collect();
for (entity, handle, inputs) in cameras.iter_mut() {
let stale = handle.is_changed() || inputs.is_none() || updated.contains(&handle.id());
if !stale {
continue;
}
let Some(wisp) = wisps.get(&**handle) else {
continue;
};
match inputs {
Some(mut inputs) => {
let rematched = rematch_inputs(&inputs, &wisp.schema);
*inputs = rematched;
}
None => {
commands
.entity(entity)
.insert(inputs_from_schema(&wisp.schema));
}
}
}
}