pub mod hooks;
use std::collections::BTreeMap;
use crate::ecs::bootstrap::{build_engine_ecs, register_core_components};
use crate::ecs::world::{CORE, RENDER, World};
use nightshade_ecs::EntityAllocator;
use nightshade_ecs::dynamic::{ComponentRegistry, DynWorld, DynWorldSnapshot, SnapshotError};
#[derive(serde::Serialize, serde::Deserialize)]
pub struct WorldSnapshot {
pub allocator: EntityAllocator,
pub core: DynWorldSnapshot,
pub game_worlds: Vec<DynWorldSnapshot>,
pub capability_worlds: BTreeMap<String, DynWorldSnapshot>,
}
fn clone_allocator(allocator: &EntityAllocator) -> EntityAllocator {
EntityAllocator {
next_id: allocator.next_id,
free_ids: allocator.free_ids.clone(),
slots: allocator.slots.clone(),
}
}
pub fn snapshot_world(world: &World) -> Result<WorldSnapshot, SnapshotError> {
let owners: Vec<(usize, &'static str)> = hooks::snapshot_capabilities(world)
.iter()
.filter_map(|capability| {
(capability.member_world)(world).map(|index| (index, capability.key))
})
.collect();
let mut game_worlds = Vec::new();
let mut capability_worlds = BTreeMap::new();
for (index, member) in world.ecs.worlds.iter().enumerate().skip(RENDER + 1) {
match owners.iter().find(|(owned, _)| *owned == index) {
Some((_, key)) => {
capability_worlds.insert((*key).to_string(), member.snapshot()?);
}
None => game_worlds.push(member.snapshot()?),
}
}
Ok(WorldSnapshot {
allocator: clone_allocator(&world.ecs.allocator),
core: world.ecs.worlds[CORE].snapshot()?,
game_worlds,
capability_worlds,
})
}
pub fn restore_world(
world: &mut World,
snapshot: &WorldSnapshot,
game_registries: Vec<ComponentRegistry>,
) -> Result<(), SnapshotError> {
if game_registries.len() != snapshot.game_worlds.len() {
return Err(SnapshotError::SchemaMismatch {
expected: format!("{} app member worlds", snapshot.game_worlds.len()),
found: format!("{} registries", game_registries.len()),
});
}
let mut core = DynWorld::from_snapshot(register_core_components(), &snapshot.core)?;
core.insert_missing_rows = true;
core.set_change_detection(true);
core.structural_logging = true;
let mut ecs = build_engine_ecs();
ecs.allocator = clone_allocator(&snapshot.allocator);
ecs.worlds[CORE] = core;
for (registry, member_snapshot) in game_registries.into_iter().zip(&snapshot.game_worlds) {
let mut member = DynWorld::from_snapshot(registry, member_snapshot)?;
member.insert_missing_rows = true;
member.set_change_detection(true);
member.structural_logging = true;
ecs.worlds.push(member);
}
let capabilities = hooks::snapshot_capabilities(world);
let mut restored = Vec::with_capacity(capabilities.len());
for capability in capabilities {
let index = restore_capability_world(
&mut ecs,
snapshot.capability_worlds.get(capability.key),
capability.registry,
)?;
restored.push((capability.restore, index));
}
std::mem::swap(&mut ecs.resources, &mut world.ecs.resources);
world.ecs = ecs;
*world.res_mut::<nightshade_ecs::dynamic::HierarchyIndex>() =
nightshade_ecs::dynamic::HierarchyIndex::default();
world
.res_mut::<crate::ecs::world::commands::CommandQueues>()
.ecs
.clear();
world
.res_mut::<crate::ecs::world::commands::CommandQueues>()
.render
.clear();
rebuild_entity_registry(world);
reset_retained_ui(world);
reconcile_texture_ledger(world);
world
.res_mut::<crate::render::mesh_state::MeshRenderState>()
.request_full_rebuild();
for (restore, index) in restored {
restore(world, index);
}
Ok(())
}
fn restore_capability_world(
ecs: &mut nightshade_ecs::dynamic::DynEcs,
snapshot: Option<&DynWorldSnapshot>,
registry: fn() -> ComponentRegistry,
) -> Result<Option<usize>, SnapshotError> {
let Some(snapshot) = snapshot else {
return Ok(None);
};
let mut member = DynWorld::from_snapshot(registry(), snapshot)?;
member.insert_missing_rows = true;
member.set_change_detection(true);
member.structural_logging = true;
let index = ecs.worlds.len();
ecs.worlds.push(member);
Ok(Some(index))
}
fn rebuild_entity_registry(world: &mut World) {
let mut names = std::collections::HashMap::new();
for (entity, name) in world.ecs.worlds[CORE]
.query_ref::<&crate::ecs::primitives::Name>()
.iter()
{
names.insert(name.0.clone(), entity);
}
let mut guid_index = std::collections::HashMap::new();
let mut highest_guid = 0u64;
for (entity, guid) in world.ecs.worlds[CORE]
.query_ref::<&crate::ecs::primitives::Guid>()
.iter()
{
guid_index.insert(guid.0, entity);
highest_guid = highest_guid.max(guid.0);
}
let registry = &mut world.res_mut::<crate::ecs::entity_registry::EntityRegistry>();
registry.names = names;
registry.guid_index = guid_index;
registry.tags.clear();
registry.next_guid = registry.next_guid.max(highest_guid.saturating_add(1));
}
fn reconcile_texture_ledger(world: &mut World) {
let mut texture_cache =
std::mem::take(world.res_mut::<crate::render::wgpu::texture_cache::TextureCache>());
crate::render::wgpu::texture_cache::texture_cache_release_dead_entities(
&mut texture_cache,
|entity| world.is_alive(entity),
);
*world.res_mut::<crate::render::wgpu::texture_cache::TextureCache>() = texture_cache;
}
fn reset_retained_ui(world: &mut World) {
let dirty = world.res_mut::<crate::ui::resources::RetainedUiDirty>();
dirty.layout_dirty = true;
dirty.render_dirty = true;
let interaction = world.res_mut::<crate::ui::resources::RetainedUiInteraction>();
interaction.focused_entity = None;
interaction.hovered_entity = None;
interaction.active_entity = None;
interaction.last_click = None;
}
#[cfg(feature = "assets")]
pub fn save_world_snapshot_to_bytes(world: &World) -> Result<Vec<u8>, SnapshotError> {
let snapshot = snapshot_world(world)?;
bincode::serde::encode_to_vec(&snapshot, bincode::config::legacy())
.map_err(|error| SnapshotError::Codec(error.to_string()))
}
#[cfg(feature = "assets")]
pub fn load_world_snapshot_from_bytes(
world: &mut World,
bytes: &[u8],
game_registries: Vec<ComponentRegistry>,
) -> Result<(), SnapshotError> {
let (snapshot, _): (WorldSnapshot, usize) =
bincode::serde::decode_from_slice(bytes, bincode::config::legacy())
.map_err(|error| SnapshotError::Codec(error.to_string()))?;
restore_world(world, &snapshot, game_registries)
}