furmint-runtime 0.1.0

Main package of furmint game engine providing higher level API
Documentation
//! Scenes are compositions of entities/components and scripts the user sees on their screen.
//! A scene has a name, a list of scripts it uses (doesn't actually own them, retrieves from
//! resource manager) and a list of entities (same as with scripts)

use furmint_registry::ComponentRegistry;
use furmint_resources::ResourceResult;
use furmint_resources::assets::Handle;
use furmint_resources::loader::ErasedAssetLoader;
use log::debug;
use ron::Value;
use serde::Deserialize;
use specs::{
    Component, DenseVecStorage, Entities, LazyUpdate, Read, ReadExpect, System, WriteExpect,
};
use std::any::Any;
use std::collections::HashMap;

/// Scene asset
#[derive(Deserialize, Debug, Clone, Component)]
pub struct Scene {
    /// Scene internal name.
    pub name: String,

    /// Entity descriptors in this scene.
    pub entities: Vec<EntityDescriptor>,
}

/// Descriptor of an entity in a scene file
#[derive(Deserialize, Debug, Clone)]
pub struct EntityDescriptor {
    /// Entity internal id.
    pub id: String,

    /// Components attached to this entity.
    pub components: Vec<ComponentDescriptor>,
}

/// Descriptor of a component in a scene file
#[derive(Deserialize, Debug, Clone)]
pub struct ComponentDescriptor {
    /// Component registry id.
    pub id: String,

    /// Component configuration.
    pub config: Value,
}

/// Runtime state for the currently active scene
#[derive(Debug, Default)]
pub struct ActiveScene {
    /// Active scene name
    pub name: Option<String>,

    /// Whether the active scene has been loaded
    pub loaded: bool,

    /// Entities owned by the active scene
    pub entities: Vec<specs::Entity>,
}

/// Scene commands
#[derive(Debug)]
pub enum SceneCommand {
    /// Switch to a scene
    SwitchTo(String),
    /// Reload scene
    Reload,
    /// Unload scene
    Unload,
}

/// Scene commands
#[derive(Default, Debug)]
pub struct SceneCommands {
    queue: Vec<SceneCommand>,
}

/// Scene library, handles keyed by name
#[derive(Debug, Default)]
pub struct SceneLibrary {
    /// Scenes themselves
    pub scenes: HashMap<String, Handle<Scene>>,
}

impl SceneCommands {
    /// Switch to another scene
    pub fn switch_to(&mut self, name: impl Into<String>) {
        self.queue.push(SceneCommand::SwitchTo(name.into()));
    }

    /// Reload current scene
    pub fn reload(&mut self) {
        self.queue.push(SceneCommand::Reload);
    }

    /// Unload current scne
    pub fn unload(&mut self) {
        self.queue.push(SceneCommand::Unload);
    }

    #[allow(dead_code)]
    pub(crate) fn drain(&mut self) -> impl Iterator<Item = SceneCommand> + '_ {
        self.queue.drain(..)
    }

    /// Is the scene commands queue empty
    pub fn is_empty(&self) -> bool {
        self.queue.is_empty()
    }
}

impl ActiveScene {
    /// Set the current scene
    pub fn set(&mut self, name: impl Into<String>) {
        self.name = Some(name.into());
        self.loaded = false;
    }

    /// Unload current scene
    pub fn clear(&mut self) {
        debug!("unloading scene {:?}", self.name);
        self.name = None;
        self.loaded = false;
        self.entities.clear();
    }

    /// Is the current scene loaded?
    pub fn is_loaded(&self) -> bool {
        self.loaded
    }
}

#[derive(Debug)]
pub(crate) struct SceneLoader;

impl ErasedAssetLoader for SceneLoader {
    fn load_erased(
        &self,
        reader: &mut dyn std::io::Read,
    ) -> ResourceResult<Box<dyn Any + Send + Sync>> {
        let scene: Scene = ron::de::from_reader(reader).unwrap();
        Ok(Box::new(scene))
    }
}

/// Scene loading system
pub(crate) struct SceneSystem;

impl<'a> System<'a> for SceneSystem {
    type SystemData = (
        Entities<'a>,
        WriteExpect<'a, ActiveScene>,
        ReadExpect<'a, ComponentRegistry>,
        Read<'a, LazyUpdate>,
        ReadExpect<'a, SceneLibrary>,
    );

    fn run(
        &mut self,
        (entities, mut active_scene, component_registry, lazy, library): Self::SystemData,
    ) {
        if active_scene.loaded {
            return;
        }

        let Some(active_name) = active_scene.name.as_deref() else {
            return;
        };

        let scene_handle = library
            .scenes
            .get(active_name)
            .unwrap_or_else(|| panic!("scene {active_name} not found"));

        let scene = scene_handle.read();

        debug!("loading scene '{}'", scene.name);

        for ent_def in &scene.entities {
            let entity = entities.create();

            debug!("creating scene entity '{}'", ent_def.id);

            for component in &ent_def.components {
                debug!(
                    "loading component '{}' with parameters {:?}",
                    component.id, component.config
                );

                let factory = component_registry
                    .get(&component.id)
                    .unwrap_or_else(|| panic!("component factory '{}' not found", component.id));

                factory.insert_lazy(&lazy, entity, &component.config);
            }

            active_scene.entities.push(entity);
        }

        active_scene.loaded = true;
    }
}