use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;
use freecs::Entity;
use crate::ecs::name::components::Name;
use crate::ecs::transform::components::Parent;
use crate::ecs::world::{
NAME, PARENT, RENDER_LAYER, SPRITE, SPRITE_ANIMATOR, SPRITE_PARTICLE_EMITTER, TILEMAP, TWEEN,
VISIBILITY, World,
};
use super::components::{Scene2D, SceneEntity2D};
#[derive(Debug, thiserror::Error)]
pub enum Scene2DError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Cyclic parent reference detected")]
CyclicReference,
#[error("Parent not found: scene entity {0} references parent {1}")]
ParentNotFound(u32, u32),
}
pub fn save_scene_2d(scene: &Scene2D, path: &Path) -> Result<(), Scene2DError> {
let file = File::create(path)?;
let writer = BufWriter::new(file);
serde_json::to_writer_pretty(writer, scene)?;
Ok(())
}
pub fn load_scene_2d(path: &Path) -> Result<Scene2D, Scene2DError> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let scene: Scene2D = serde_json::from_reader(reader)?;
Ok(scene)
}
pub fn spawn_scene_2d(
world: &mut World,
scene: &Scene2D,
) -> Result<HashMap<u32, Entity>, Scene2DError> {
let spawn_order = compute_spawn_order_2d(&scene.entities)?;
let mut scene_id_to_entity: HashMap<u32, Entity> = HashMap::new();
for scene_entity_id in spawn_order {
let scene_entity = scene
.find_entity(scene_entity_id)
.expect("spawn order references valid entity id");
let parent_entity = scene_entity
.parent_id
.map(|parent_id| {
scene_id_to_entity
.get(&parent_id)
.copied()
.ok_or(Scene2DError::ParentNotFound(scene_entity.id, parent_id))
})
.transpose()?;
let mut core_mask = 0u64;
let mut sprite2d_mask = 0u64;
if scene_entity.sprite.is_some() {
sprite2d_mask |= SPRITE;
}
if scene_entity.tilemap.is_some() {
sprite2d_mask |= TILEMAP;
}
if scene_entity.sprite_animator.is_some() {
sprite2d_mask |= SPRITE_ANIMATOR;
}
if scene_entity.tween.is_some() {
core_mask |= TWEEN;
}
if scene_entity.sprite_particle_emitter.is_some() {
sprite2d_mask |= SPRITE_PARTICLE_EMITTER;
}
if scene_entity.visibility.is_some() {
core_mask |= VISIBILITY;
}
if scene_entity.render_layer.is_some() {
core_mask |= RENDER_LAYER;
}
if scene_entity.name.is_some() {
core_mask |= NAME;
}
if parent_entity.is_some() {
core_mask |= PARENT;
}
let entity = world.spawn();
if core_mask != 0 {
world.core.add_components(entity, core_mask);
}
if sprite2d_mask != 0 {
world.sprite2d.add_components(entity, sprite2d_mask);
}
if let Some(sprite) = &scene_entity.sprite {
world.sprite2d.set_sprite(entity, sprite.clone());
}
if let Some(tilemap) = &scene_entity.tilemap {
world.sprite2d.set_tilemap(entity, tilemap.clone());
}
if let Some(sprite_animator) = &scene_entity.sprite_animator {
world
.sprite2d
.set_sprite_animator(entity, sprite_animator.clone());
}
if let Some(tween) = &scene_entity.tween {
world.core.set_tween(entity, tween.clone());
}
if let Some(emitter) = &scene_entity.sprite_particle_emitter {
world
.sprite2d
.set_sprite_particle_emitter(entity, emitter.clone());
}
if let Some(visibility) = &scene_entity.visibility {
world.core.set_visibility(entity, visibility.clone());
}
if let Some(render_layer) = &scene_entity.render_layer {
world.core.set_render_layer(entity, *render_layer);
}
if let Some(name) = &scene_entity.name {
world.core.set_name(entity, Name(name.clone()));
world.resources.entity_names.insert(name.clone(), entity);
}
if let Some(parent) = parent_entity {
world.core.set_parent(entity, Parent(Some(parent)));
world
.resources
.children_cache
.entry(parent)
.or_default()
.push(entity);
}
scene_id_to_entity.insert(scene_entity.id, entity);
}
Ok(scene_id_to_entity)
}
pub fn world_to_scene_2d(world: &World, name: Option<&str>) -> Scene2D {
let mut scene = Scene2D::new(name);
let mut entity_to_id: HashMap<Entity, u32> = HashMap::new();
let mut next_id: u32 = 0;
let sprite_entities: Vec<Entity> = world.sprite2d.query_entities(SPRITE).collect();
let tilemap_entities: Vec<Entity> = world.sprite2d.query_entities(TILEMAP).collect();
let emitter_entities: Vec<Entity> = world
.sprite2d
.query_entities(SPRITE_PARTICLE_EMITTER)
.collect();
let mut all_entities: Vec<Entity> = Vec::new();
for entity in sprite_entities
.iter()
.chain(tilemap_entities.iter())
.chain(emitter_entities.iter())
{
if !entity_to_id.contains_key(entity) {
entity_to_id.insert(*entity, next_id);
all_entities.push(*entity);
next_id += 1;
}
}
for entity in &all_entities {
let entity = *entity;
let id = entity_to_id[&entity];
let parent_id = world
.core
.get_parent(entity)
.and_then(|parent| parent.0)
.and_then(|parent_entity| entity_to_id.get(&parent_entity).copied());
let name = world.core.get_name(entity).map(|name| name.0.clone());
let sprite = world.sprite2d.get_sprite(entity).cloned();
let tilemap = world.sprite2d.get_tilemap(entity).cloned();
let sprite_animator = world.sprite2d.get_sprite_animator(entity).cloned();
let tween = world.core.get_tween(entity).cloned();
let sprite_particle_emitter = world.sprite2d.get_sprite_particle_emitter(entity).cloned();
let visibility = world.core.get_visibility(entity).cloned();
let render_layer = world.core.get_render_layer(entity).copied();
let scene_entity = SceneEntity2D {
id,
parent_id,
name,
sprite,
tilemap,
sprite_animator,
tween,
sprite_particle_emitter,
visibility,
render_layer,
};
scene.add_entity(scene_entity);
}
scene
}
fn compute_spawn_order_2d(entities: &[SceneEntity2D]) -> Result<Vec<u32>, Scene2DError> {
let id_set: std::collections::HashSet<u32> = entities.iter().map(|entity| entity.id).collect();
let mut result: Vec<u32> = Vec::with_capacity(entities.len());
let mut visited: std::collections::HashSet<u32> = std::collections::HashSet::new();
let mut in_stack: std::collections::HashSet<u32> = std::collections::HashSet::new();
let entity_map: HashMap<u32, &SceneEntity2D> =
entities.iter().map(|entity| (entity.id, entity)).collect();
fn visit(
id: u32,
entity_map: &HashMap<u32, &SceneEntity2D>,
id_set: &std::collections::HashSet<u32>,
visited: &mut std::collections::HashSet<u32>,
in_stack: &mut std::collections::HashSet<u32>,
result: &mut Vec<u32>,
) -> Result<(), Scene2DError> {
if visited.contains(&id) {
return Ok(());
}
if in_stack.contains(&id) {
return Err(Scene2DError::CyclicReference);
}
in_stack.insert(id);
if let Some(entity) = entity_map.get(&id)
&& let Some(parent_id) = entity.parent_id
&& id_set.contains(&parent_id)
{
visit(parent_id, entity_map, id_set, visited, in_stack, result)?;
}
in_stack.remove(&id);
visited.insert(id);
result.push(id);
Ok(())
}
for entity in entities {
visit(
entity.id,
&entity_map,
&id_set,
&mut visited,
&mut in_stack,
&mut result,
)?;
}
Ok(result)
}