resonance 0.1.0

A modular game engine. Heavy work in progress.
Documentation
use super::{
    plugin::{Plugin, PluginMetadata, PluginState},
    stage::Stage,
};
use bevy_ecs::{
    prelude::*,
    schedule::{IntoScheduleConfigs, Schedule},
    system::ScheduleSystem,
};
use std::{any::TypeId, collections::HashMap};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EngineMode {
    Client,
    Server,
}

pub struct Engine {
    pub world: World,
    pub schedules: Schedules,
    pub mode: EngineMode,
    pub running: bool,
    plugins: HashMap<TypeId, PluginMetadata>,
}

impl Engine {
    pub fn new() -> Self {
        Self::with_mode(EngineMode::Client)
    }

    pub fn with_mode(mode: EngineMode) -> Self {
        let world = World::new();
        let mut schedules = Schedules::new();

        schedules.insert(Schedule::new(Stage::Startup));
        schedules.insert(Schedule::new(Stage::PreUpdate));
        schedules.insert(Schedule::new(Stage::Update));
        schedules.insert(Schedule::new(Stage::PostUpdate));
        schedules.insert(Schedule::new(Stage::FixedUpdate));
        schedules.insert(Schedule::new(Stage::Render));
        schedules.insert(Schedule::new(Stage::Last));

        Self {
            world,
            schedules,
            mode,
            running: false,
            plugins: HashMap::new(),
        }
    }

    pub fn set_mode(mut self, mode: EngineMode) -> Self {
        self.mode = mode;
        self
    }

    pub fn add_plugin<P: Plugin>(mut self, plugin: P) -> Self {
        let type_id = plugin.type_id();
        let name = plugin.name().to_string();

        if self.plugins.contains_key(&type_id) {
            log::warn!("Plugin {} already loaded, skipping", name);
            return self;
        }

        let should_load = match self.mode {
            EngineMode::Client => plugin.is_client_plugin(),
            EngineMode::Server => plugin.is_server_plugin(),
        };

        if !should_load {
            log::debug!(
                "Plugin {} skipped (not compatible with {:?} mode)",
                name,
                self.mode
            );
            return self;
        }

        let dependencies = plugin.dependencies();
        for dep_id in &dependencies {
            if !self.plugins.contains_key(dep_id) {
                log::error!(
                    "Plugin {} missing dependency (TypeId: {:?})",
                    name,
                    dep_id
                );
                self.plugins.insert(
                    type_id,
                    PluginMetadata {
                        type_id,
                        name,
                        state: PluginState::Failed,
                        dependencies,
                    },
                );
                return self;
            }
        }

        self.plugins.insert(
            type_id,
            PluginMetadata {
                type_id,
                name: name.clone(),
                state: PluginState::Building,
                dependencies: dependencies.clone(),
            },
        );

        plugin.build(&mut self);

        if let Some(metadata) = self.plugins.get_mut(&type_id) {
            metadata.state = PluginState::Built;
        }

        self
    }

    pub fn has_plugin<P: Plugin>(&self) -> bool {
        let type_id = TypeId::of::<P>();
        self.plugins
            .get(&type_id)
            .map(|m| m.state == PluginState::Built)
            .unwrap_or(false)
    }

    pub fn get_plugin_metadata<P: Plugin>(&self) -> Option<&PluginMetadata> {
        let type_id = TypeId::of::<P>();
        self.plugins.get(&type_id)
    }

    pub fn add_system<M>(
        mut self,
        stage: Stage,
        system: impl IntoScheduleConfigs<ScheduleSystem, M>,
    ) -> Self {
        if let Some(schedule) = self.schedules.get_mut(stage) {
            schedule.add_systems(system);
        }
        self
    }

    pub fn add_startup_system<M>(
        mut self,
        system: impl IntoScheduleConfigs<ScheduleSystem, M>,
    ) -> Self {
        if let Some(schedule) = self.schedules.get_mut(Stage::Startup) {
            schedule.add_systems(system);
        }
        self
    }

    pub fn add_systems<M>(
        mut self,
        stage: Stage,
        systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
    ) -> Self {
        if let Some(schedule) = self.schedules.get_mut(stage) {
            schedule.add_systems(systems);
        }
        self
    }

    pub fn is_client(&self) -> bool {
        self.mode == EngineMode::Client
    }

    pub fn is_server(&self) -> bool {
        self.mode == EngineMode::Server
    }

    pub fn run_schedule(&mut self, stage: Stage) {
        if let Some(schedule) = self.schedules.get_mut(stage) {
            schedule.run(&mut self.world);
        }
    }

    pub fn update(&mut self) {
        {
            let mut time = self.world.resource_mut::<crate::core::Time>();
            time.update();
        }

        self.run_schedule(Stage::PreUpdate);
        self.run_schedule(Stage::Update);

        {
            let delta = self.world.resource::<crate::core::Time>().delta();
            let mut fixed_time = self.world.resource_mut::<crate::core::FixedTime>();
            fixed_time.accumulate(delta);
        }

        while self
            .world
            .resource::<crate::core::FixedTime>()
            .should_update()
        {
            {
                let mut tick = self.world.resource_mut::<crate::core::GameTick>();
                tick.increment();
            }

            self.run_schedule(Stage::FixedUpdate);

            self.world
                .resource_mut::<crate::core::FixedTime>()
                .consume_step();
        }

        self.run_schedule(Stage::PostUpdate);

        if self.is_client() {
            self.run_schedule(Stage::Render);
        }

        self.run_schedule(Stage::Last);
    }

    pub fn startup(&mut self) {
        self.running = true;
        self.run_schedule(Stage::Startup);
    }

    pub fn stop(&mut self) {
        self.running = false;
    }

    pub fn is_running(&self) -> bool {
        self.running
    }
}

impl Default for Engine {
    fn default() -> Self {
        Self::new()
    }
}