use crate::ecs::world::World;
use crate::state::{RenderResources, State};
use freecs::dynamic::ResourceHostExt;
pub trait Plugin {
fn build(&self, app: &mut App);
}
pub trait PluginGroup: Sized {
fn build(self) -> PluginGroupBuilder;
fn set<P: Plugin + 'static>(self, plugin: P) -> PluginGroupBuilder {
self.build().set(plugin)
}
fn disable<P: Plugin + 'static>(self) -> PluginGroupBuilder {
self.build().disable::<P>()
}
}
#[derive(Default)]
pub struct PluginGroupBuilder {
entries: Vec<PluginGroupEntry>,
}
struct PluginGroupEntry {
id: std::any::TypeId,
name: &'static str,
enabled: bool,
plugin: Box<dyn Plugin>,
}
impl PluginGroupBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_plugin<P: Plugin + 'static>(mut self, plugin: P) -> Self {
self.entries.push(PluginGroupEntry {
id: std::any::TypeId::of::<P>(),
name: std::any::type_name::<P>(),
enabled: true,
plugin: Box::new(plugin),
});
self
}
pub fn set<P: Plugin + 'static>(mut self, plugin: P) -> Self {
let entry = self.entry_mut::<P>().unwrap_or_else(|| {
panic!("{} is not in this plugin group", std::any::type_name::<P>())
});
entry.plugin = Box::new(plugin);
self
}
pub fn disable<P: Plugin + 'static>(mut self) -> Self {
let entry = self.entry_mut::<P>().unwrap_or_else(|| {
panic!("{} is not in this plugin group", std::any::type_name::<P>())
});
entry.enabled = false;
self
}
fn entry_mut<P: Plugin + 'static>(&mut self) -> Option<&mut PluginGroupEntry> {
let id = std::any::TypeId::of::<P>();
self.entries.iter_mut().find(|entry| entry.id == id)
}
}
impl PluginGroup for PluginGroupBuilder {
fn build(self) -> PluginGroupBuilder {
self
}
}
macro_rules! impl_plugin_group_for_tuple {
($(($plugin_type:ident, $plugin_value:ident)),+) => {
impl<$($plugin_type: Plugin + 'static),+> PluginGroup for ($($plugin_type,)+) {
fn build(self) -> PluginGroupBuilder {
let ($($plugin_value,)+) = self;
PluginGroupBuilder::new()$(.add_plugin($plugin_value))+
}
}
};
}
impl_plugin_group_for_tuple!((P1, plugin1));
impl_plugin_group_for_tuple!((P1, plugin1), (P2, plugin2));
impl_plugin_group_for_tuple!((P1, plugin1), (P2, plugin2), (P3, plugin3));
impl_plugin_group_for_tuple!((P1, plugin1), (P2, plugin2), (P3, plugin3), (P4, plugin4));
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5)
);
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5),
(P6, plugin6)
);
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5),
(P6, plugin6),
(P7, plugin7)
);
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5),
(P6, plugin6),
(P7, plugin7),
(P8, plugin8)
);
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5),
(P6, plugin6),
(P7, plugin7),
(P8, plugin8),
(P9, plugin9)
);
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5),
(P6, plugin6),
(P7, plugin7),
(P8, plugin8),
(P9, plugin9),
(P10, plugin10)
);
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5),
(P6, plugin6),
(P7, plugin7),
(P8, plugin8),
(P9, plugin9),
(P10, plugin10),
(P11, plugin11)
);
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5),
(P6, plugin6),
(P7, plugin7),
(P8, plugin8),
(P9, plugin9),
(P10, plugin10),
(P11, plugin11),
(P12, plugin12)
);
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5),
(P6, plugin6),
(P7, plugin7),
(P8, plugin8),
(P9, plugin9),
(P10, plugin10),
(P11, plugin11),
(P12, plugin12),
(P13, plugin13)
);
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5),
(P6, plugin6),
(P7, plugin7),
(P8, plugin8),
(P9, plugin9),
(P10, plugin10),
(P11, plugin11),
(P12, plugin12),
(P13, plugin13),
(P14, plugin14)
);
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5),
(P6, plugin6),
(P7, plugin7),
(P8, plugin8),
(P9, plugin9),
(P10, plugin10),
(P11, plugin11),
(P12, plugin12),
(P13, plugin13),
(P14, plugin14),
(P15, plugin15)
);
impl_plugin_group_for_tuple!(
(P1, plugin1),
(P2, plugin2),
(P3, plugin3),
(P4, plugin4),
(P5, plugin5),
(P6, plugin6),
(P7, plugin7),
(P8, plugin8),
(P9, plugin9),
(P10, plugin10),
(P11, plugin11),
(P12, plugin12),
(P13, plugin13),
(P14, plugin14),
(P15, plugin15),
(P16, plugin16)
);
pub trait SystemTuple {
fn push_onto(self, schedule: &mut freecs::Schedule<World>);
}
macro_rules! impl_system_tuple {
($(($system_type:ident, $system_value:ident)),+) => {
impl<$($system_type: FnMut(&mut World) + Send + 'static),+> SystemTuple
for ($($system_type,)+)
{
fn push_onto(self, schedule: &mut freecs::Schedule<World>) {
let ($($system_value,)+) = self;
$(schedule.push(std::any::type_name_of_val(&$system_value), $system_value);)+
}
}
};
}
impl_system_tuple!((S1, system1));
impl_system_tuple!((S1, system1), (S2, system2));
impl_system_tuple!((S1, system1), (S2, system2), (S3, system3));
impl_system_tuple!((S1, system1), (S2, system2), (S3, system3), (S4, system4));
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5)
);
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5),
(S6, system6)
);
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5),
(S6, system6),
(S7, system7)
);
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5),
(S6, system6),
(S7, system7),
(S8, system8)
);
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5),
(S6, system6),
(S7, system7),
(S8, system8),
(S9, system9)
);
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5),
(S6, system6),
(S7, system7),
(S8, system8),
(S9, system9),
(S10, system10)
);
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5),
(S6, system6),
(S7, system7),
(S8, system8),
(S9, system9),
(S10, system10),
(S11, system11)
);
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5),
(S6, system6),
(S7, system7),
(S8, system8),
(S9, system9),
(S10, system10),
(S11, system11),
(S12, system12)
);
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5),
(S6, system6),
(S7, system7),
(S8, system8),
(S9, system9),
(S10, system10),
(S11, system11),
(S12, system12),
(S13, system13)
);
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5),
(S6, system6),
(S7, system7),
(S8, system8),
(S9, system9),
(S10, system10),
(S11, system11),
(S12, system12),
(S13, system13),
(S14, system14)
);
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5),
(S6, system6),
(S7, system7),
(S8, system8),
(S9, system9),
(S10, system10),
(S11, system11),
(S12, system12),
(S13, system13),
(S14, system14),
(S15, system15)
);
impl_system_tuple!(
(S1, system1),
(S2, system2),
(S3, system3),
(S4, system4),
(S5, system5),
(S6, system6),
(S7, system7),
(S8, system8),
(S9, system9),
(S10, system10),
(S11, system11),
(S12, system12),
(S13, system13),
(S14, system14),
(S15, system15),
(S16, system16)
);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Stage {
First,
Update,
LateUpdate,
}
impl Stage {
const ALL: [Stage; 3] = [Stage::First, Stage::Update, Stage::LateUpdate];
fn name(self) -> &'static str {
match self {
Stage::First => "first",
Stage::Update => "update",
Stage::LateUpdate => "late_update",
}
}
}
type RenderGraphConfigFn = Box<
dyn FnMut(
&mut crate::render::wgpu::rendergraph::RenderGraph<
crate::render::wgpu::render_configs::RenderInputs,
>,
&wgpu::Device,
wgpu::TextureFormat,
RenderResources,
),
>;
type PreRenderFn = Box<dyn FnMut(&mut crate::render::wgpu::WgpuRenderer, &mut World)>;
type RunnerFn = Box<dyn FnOnce(App) -> Result<(), Box<dyn std::error::Error>>>;
type UpdateRenderGraphFn = Box<
dyn FnMut(
&mut crate::render::wgpu::rendergraph::RenderGraph<
crate::render::wgpu::render_configs::RenderInputs,
>,
&World,
),
>;
pub struct App {
pub world: World,
pub startup: freecs::Schedule<World>,
pub stages: freecs::Stages<World>,
pub(crate) log_guards: Vec<Box<dyn std::any::Any>>,
render_graph_config: Vec<RenderGraphConfigFn>,
pre_render: Vec<PreRenderFn>,
update_render_graph: Vec<UpdateRenderGraphFn>,
runner: Option<RunnerFn>,
added_plugins: Vec<std::any::TypeId>,
}
impl Default for App {
fn default() -> Self {
Self::new()
}
}
impl App {
pub fn new() -> Self {
let mut stages = freecs::Stages::new();
for stage in Stage::ALL {
stages.add_stage(stage.name());
}
let mut world = World::default();
world.resources.window.start_hidden = true;
world.resources.window.create_renderer = false;
Self {
world,
startup: freecs::Schedule::new(),
stages,
log_guards: Vec::new(),
render_graph_config: Vec::new(),
pre_render: Vec::new(),
update_render_graph: Vec::new(),
runner: None,
added_plugins: Vec::new(),
}
}
pub fn add_plugin<P: Plugin + 'static>(mut self, plugin: P) -> Self {
self.track_plugin(std::any::TypeId::of::<P>(), std::any::type_name::<P>());
plugin.build(&mut self);
self
}
pub fn add_plugins(mut self, group: impl PluginGroup) -> Self {
for entry in group.build().entries {
if !entry.enabled {
continue;
}
self.track_plugin(entry.id, entry.name);
entry.plugin.build(&mut self);
}
self
}
fn track_plugin(&mut self, id: std::any::TypeId, name: &'static str) {
if self.added_plugins.contains(&id) {
panic!("plugin {name} was added twice, each plugin type is added at most once");
}
self.added_plugins.push(id);
}
pub fn add_system(
&mut self,
stage: Stage,
system: impl FnMut(&mut World) + Send + 'static,
) -> &mut Self {
let name = std::any::type_name_of_val(&system);
self.stages.stage_mut(stage.name()).push(name, system);
self
}
pub fn add_systems(&mut self, stage: Stage, systems: impl SystemTuple) -> &mut Self {
systems.push_onto(self.stages.stage_mut(stage.name()));
self
}
pub fn add_startup_system(
&mut self,
system: impl FnMut(&mut World) + Send + 'static,
) -> &mut Self {
let name = std::any::type_name_of_val(&system);
self.startup.push(name, system);
self
}
pub fn add_startup_systems(&mut self, systems: impl SystemTuple) -> &mut Self {
systems.push_onto(&mut self.startup);
self
}
pub fn add_game_system<T: Send + Sync + 'static>(
&mut self,
stage: Stage,
mut system: impl FnMut(&mut T, &mut World) + Send + 'static,
) -> &mut Self {
let name = std::any::type_name_of_val(&system);
self.stages
.stage_mut(stage.name())
.push(name, move |world| {
game_scope(world, |state: &mut T, world| system(state, world));
});
self
}
pub fn add_game_startup_system<T: Send + Sync + 'static>(
&mut self,
mut system: impl FnMut(&mut T, &mut World) + Send + 'static,
) -> &mut Self {
let name = std::any::type_name_of_val(&system);
self.startup.push(name, move |world| {
game_scope(world, |state: &mut T, world| system(state, world));
});
self
}
pub fn insert_resource<T: Send + Sync + 'static>(&mut self, value: T) -> &mut Self {
self.world.ecs.insert_resource(value);
self
}
pub fn add_render_graph_config(
&mut self,
hook: impl FnMut(
&mut crate::render::wgpu::rendergraph::RenderGraph<
crate::render::wgpu::render_configs::RenderInputs,
>,
&wgpu::Device,
wgpu::TextureFormat,
RenderResources,
) + 'static,
) -> &mut Self {
self.render_graph_config.push(Box::new(hook));
self
}
pub fn on_pre_render(
&mut self,
hook: impl FnMut(&mut crate::render::wgpu::WgpuRenderer, &mut World) + 'static,
) -> &mut Self {
self.pre_render.push(Box::new(hook));
self
}
pub fn on_update_render_graph(
&mut self,
hook: impl FnMut(
&mut crate::render::wgpu::rendergraph::RenderGraph<
crate::render::wgpu::render_configs::RenderInputs,
>,
&World,
) + 'static,
) -> &mut Self {
self.update_render_graph.push(Box::new(hook));
self
}
pub fn set_runner(
&mut self,
runner: impl FnOnce(App) -> Result<(), Box<dyn std::error::Error>> + 'static,
) -> &mut Self {
self.runner = Some(Box::new(runner));
self
}
pub fn run(mut self) -> Result<(), Box<dyn std::error::Error>> {
if let Some(runner) = self.runner.take() {
return runner(self);
}
if self.world.resources.window.start_hidden && !self.world.resources.window.create_renderer
{
let mut world = self.world;
self.startup.run(&mut world);
self.stages.run(&mut world);
return Ok(());
}
let log_guards = std::mem::take(&mut self.log_guards);
let (world, state) = self.into_parts();
let result = crate::run::launch_with_world(state, world);
drop(log_guards);
result
}
pub fn take_log_guards(&mut self) -> Vec<Box<dyn std::any::Any>> {
std::mem::take(&mut self.log_guards)
}
pub fn into_parts(self) -> (World, impl State + 'static) {
let App {
world,
startup,
stages,
render_graph_config,
pre_render,
update_render_graph,
..
} = self;
(
world,
StageState {
startup,
stages,
render_graph_config,
pre_render,
update_render_graph,
},
)
}
}
pub fn game_scope<T: Send + Sync + 'static, R>(
world: &mut World,
body: impl FnOnce(&mut T, &mut World) -> R,
) -> R {
world.resource_scope(|world, value| body(value, world))
}
struct StageState {
startup: freecs::Schedule<World>,
stages: freecs::Stages<World>,
render_graph_config: Vec<RenderGraphConfigFn>,
pre_render: Vec<PreRenderFn>,
update_render_graph: Vec<UpdateRenderGraphFn>,
}
impl State for StageState {
fn initialize(&mut self, world: &mut World) {
self.startup.run(world);
}
fn run_systems(&mut self, world: &mut World) {
self.stages.run(world);
}
fn configure_render_graph(
&mut self,
graph: &mut crate::render::wgpu::rendergraph::RenderGraph<
crate::render::wgpu::render_configs::RenderInputs,
>,
device: &wgpu::Device,
format: wgpu::TextureFormat,
resources: RenderResources,
) {
for hook in &mut self.render_graph_config {
hook(graph, device, format, resources);
}
}
fn pre_render(&mut self, renderer: &mut crate::render::wgpu::WgpuRenderer, world: &mut World) {
for hook in &mut self.pre_render {
hook(renderer, world);
}
}
fn update_render_graph(
&mut self,
graph: &mut crate::render::wgpu::rendergraph::RenderGraph<
crate::render::wgpu::render_configs::RenderInputs,
>,
world: &World,
) {
for hook in &mut self.update_render_graph {
hook(graph, world);
}
}
}
pub struct WindowPlugin {
pub title: String,
pub size: Option<(u32, u32)>,
}
impl Default for WindowPlugin {
fn default() -> Self {
Self {
title: "Nightshade".to_string(),
size: None,
}
}
}
impl Plugin for WindowPlugin {
fn build(&self, app: &mut App) {
app.world.resources.window.title = self.title.clone();
if self.size.is_some() {
app.world.resources.window.initial_size = self.size;
}
app.world.resources.window.start_hidden = false;
}
}
pub struct DefaultPlugins;
impl PluginGroup for DefaultPlugins {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::new()
.add_plugin(WindowPlugin::default())
.add_plugin(crate::plugins::log::LogPlugin::default())
.add_plugin(crate::plugins::render::RenderPlugin::default())
.add_plugin(crate::plugins::ui::UiPlugin)
}
}