use concinnity_core::animation::skeleton;
use concinnity_core::components::SkeletonPose;
use concinnity_core::components::StoryReload;
use concinnity_core::ecs::World;
use concinnity_core::gfx::render_types::SkinnedIndex;
use concinnity_engine::live_edit;
use std::sync::Arc;
use super::report::ReloadReports;
use super::signals::ReloadSignals;
use super::state::{AssetHotReloadState, FrameHotReloadEffects, run_frame};
use super::world_path::WorldPathHandle;
use crate::frame_hook::FrameHook;
pub(crate) struct HotReloadDriver {
pub(super) state: Option<AssetHotReloadState>,
signals: Arc<ReloadSignals>,
notifier: Option<crate::editor::notify::Notifier>,
world_path: Option<WorldPathHandle>,
reload_reports: Option<ReloadReports>,
}
impl HotReloadDriver {
pub(crate) fn new() -> Self {
Self {
state: None,
signals: Arc::default(),
notifier: None,
world_path: None,
reload_reports: None,
}
}
pub(crate) fn with_notifier(mut self, notifier: crate::editor::notify::Notifier) -> Self {
self.notifier = Some(notifier);
self
}
pub(crate) fn with_world_path(mut self, handle: WorldPathHandle) -> Self {
self.world_path = Some(handle);
self
}
pub(crate) fn with_reload_reports(mut self, reports: ReloadReports) -> Self {
self.reload_reports = Some(reports);
self
}
pub(crate) fn signals(&self) -> Option<Arc<ReloadSignals>> {
self.state.as_ref().map(|_| Arc::clone(&self.signals))
}
pub(crate) fn arm(&mut self, sources: live_edit::hot_reload_sources::HotReloadSources) {
let world_jsonl_path = self.world_path.as_ref().map(WorldPathHandle::get);
let state =
AssetHotReloadState::from_sources(sources, world_jsonl_path, Arc::clone(&self.signals));
if let Some(reports) = &self.reload_reports {
reports.arm(state.shaders.subjects().chain(state.sdf_fields.subjects()));
}
self.state = Some(state);
}
pub(crate) fn drive(&mut self, world: &mut World) {
if let Some(sources) = concinnity_engine::live_edit::take_hot_reload_sources(world) {
self.arm(sources);
}
if let Some(anim) = concinnity_engine::ecs::animation_system_mut(world) {
super::animation::reload_clips_if_pending(anim, &self.signals);
}
let Some(state) = self.state.as_mut() else {
return;
};
let handoff = concinnity_engine::live_edit::render_handoff(world);
let (Some(backend), Some(fog)) = (handoff.backend, handoff.fog) else {
return;
};
let effects = run_frame(state, backend, fog, self.notifier.as_ref());
if let Some(reports) = &self.reload_reports {
reports.publish(&effects.reload_reports);
}
apply_effects(world, effects);
}
}
impl FrameHook for HotReloadDriver {
fn tick(&mut self, world: &mut World) {
self.drive(world);
}
}
pub(crate) fn apply_effects(world: &mut World, effects: FrameHotReloadEffects) {
if !effects.skeleton_updates.is_empty() {
let index_to_new: std::collections::HashMap<SkinnedIndex, skeleton::Skeleton> = effects
.skeleton_updates
.into_iter()
.map(|u| (u.skinned_index, u.new_skeleton))
.collect();
let mut applied = 0usize;
for pose in world.query_mut::<SkeletonPose>() {
if let Some(new_skel) = index_to_new.get(&pose.skinned_index) {
pose.skeleton = new_skel.clone();
pose.joint_matrices = pose.skeleton.bind_skinning_matrices();
pose.updated = true;
applied += 1;
}
}
tracing::info!(
"asset hot-reload: applied skeleton-shape change to {} SkeletonPose component(s)",
applied
);
}
for story in effects.story_updates {
world
.events_mut::<StoryReload>()
.send(StoryReload { story });
}
}