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;
#[derive(Deserialize, Debug, Clone, Component)]
pub struct Scene {
pub name: String,
pub entities: Vec<EntityDescriptor>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct EntityDescriptor {
pub id: String,
pub components: Vec<ComponentDescriptor>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct ComponentDescriptor {
pub id: String,
pub config: Value,
}
#[derive(Debug, Default)]
pub struct ActiveScene {
pub name: Option<String>,
pub loaded: bool,
pub entities: Vec<specs::Entity>,
}
#[derive(Debug)]
pub enum SceneCommand {
SwitchTo(String),
Reload,
Unload,
}
#[derive(Default, Debug)]
pub struct SceneCommands {
queue: Vec<SceneCommand>,
}
#[derive(Debug, Default)]
pub struct SceneLibrary {
pub scenes: HashMap<String, Handle<Scene>>,
}
impl SceneCommands {
pub fn switch_to(&mut self, name: impl Into<String>) {
self.queue.push(SceneCommand::SwitchTo(name.into()));
}
pub fn reload(&mut self) {
self.queue.push(SceneCommand::Reload);
}
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(..)
}
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
}
impl ActiveScene {
pub fn set(&mut self, name: impl Into<String>) {
self.name = Some(name.into());
self.loaded = false;
}
pub fn clear(&mut self) {
debug!("unloading scene {:?}", self.name);
self.name = None;
self.loaded = false;
self.entities.clear();
}
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))
}
}
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;
}
}