nightshade 0.57.0

A cross-platform data-oriented game engine.
Documentation
use std::collections::HashMap;

use nightshade_ecs::Entity;
use serde::{Deserialize, Serialize};

use super::components::Scene;
use crate::assets::asset::{AssetUuid, TypedPayload};
use crate::ecs::world::World;

/// Field-type tag the editor uses to pick an inspector widget per
/// registered typed component. Keep additive: appending new variants
/// is fine, renaming or reordering breaks already-registered
/// descriptors.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FieldKind {
    Bool,
    I32,
    U32,
    F32,
    String,
    Vec3,
    Enum { variants: Vec<String> },
    Color,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldDescriptor {
    pub name: String,
    pub kind: FieldKind,
    #[serde(default)]
    pub display_name: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentDescriptor {
    pub name: String,
    #[serde(default)]
    pub display_name: Option<String>,
    pub fields: Vec<FieldDescriptor>,
    #[serde(default)]
    pub persistence: Persistence,
}

/// Engine-side registry of typed-component schemas. Apps register
/// descriptors at startup via `register_component_descriptor`; the
/// editor reads them to render inspector UI for entities whose
/// `SceneEntity::components::game_components` contain matching
/// `TypedComponent` entries.
#[derive(Debug, Clone, Default)]
pub struct ComponentTypeRegistry {
    pub descriptors: HashMap<String, ComponentDescriptor>,
}

pub fn register_component_descriptor(world: &mut World, descriptor: ComponentDescriptor) {
    world
        .res_mut::<crate::assets::scene::typed::ComponentTypeRegistry>()
        .descriptors
        .insert(descriptor.name.clone(), descriptor);
}

pub fn component_descriptor<'a>(world: &'a World, name: &str) -> Option<&'a ComponentDescriptor> {
    world
        .res::<crate::assets::scene::typed::ComponentTypeRegistry>()
        .descriptors
        .get(name)
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum Persistence {
    Authored,
    Runtime,
    #[default]
    Both,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TypedComponent {
    pub name: String,
    pub data: TypedPayload,
    #[serde(default)]
    pub persistence: Persistence,
    /// Field-level deltas that override individual values inside
    /// `data` after deserialization. `Persistence::Both` components
    /// keep their authored payload in `data` and layer runtime
    /// changes here, so a save file can carry "the door is now open"
    /// without re-emitting the rest of the authored door state.
    /// Reuses the `FieldOverride` shape.
    #[serde(default)]
    pub field_overrides: Vec<crate::assets::prefab::components::FieldOverride>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TypedResource {
    pub name: String,
    pub data: TypedPayload,
    #[serde(default)]
    pub persistence: Persistence,
}

/// Returns a fresh `serde_json::Value` obtained by overlaying the
/// `FieldOverride`s onto `base`. Object fields named by an override
/// are replaced; other fields fall through unchanged. Non-object
/// base values pass through untouched. Use this on the receiving
/// side of a `Persistence::Both` typed component to reconstruct the
/// final payload from authored data plus runtime deltas.
pub fn apply_field_overrides(
    base: &serde_json::Value,
    overrides: &[crate::assets::prefab::components::FieldOverride],
) -> serde_json::Value {
    let mut merged = base.clone();
    if let serde_json::Value::Object(map) = &mut merged {
        for entry in overrides {
            map.insert(entry.field_name.clone(), entry.value.0.clone());
        }
    }
    merged
}

/// Game-side hook invoked at scene save. Implementors iterate their
/// world(s), match entities to the engine entities listed in
/// `entity_uuids`, and append typed payloads to the matching
/// `SceneEntity::components::game_components` and/or
/// `Scene::game_resources`.
pub trait SceneSerializer {
    fn populate_scene(
        &self,
        world: &World,
        entity_uuids: &HashMap<Entity, AssetUuid>,
        scene: &mut Scene,
    );
}

/// Game-side hook invoked at scene spawn. Implementors walk
/// `scene.entities`, dispatch on each `TypedComponent::name`, and
/// write the decoded payload into whichever world the game uses.
/// `uuid_to_entity` maps scene-file uuids onto the freshly-spawned
/// engine entities so handlers can find their counterparts.
pub trait SceneDeserializer {
    fn populate_world(
        &mut self,
        world: &mut World,
        uuid_to_entity: &HashMap<AssetUuid, Entity>,
        scene: &Scene,
    );
}