use crate::components::{
DespawnRequest, EntityTarget, ReparentRequest, SpawnRequest, VisibilityRequest,
};
use crate::ecs::asset_id::AssetId;
use crate::ecs::{ActiveRenderQueues, PipelineContext, StepResult, System};
use crate::gfx::ops::RenderOps;
use crate::gfx::render_slots::RenderSlots;
use crate::gfx::transform_propagation;
use std::time::Instant;
fn resolve_target(ctx: &PipelineContext, target: EntityTarget) -> Option<crate::ecs::Entity> {
match target {
EntityTarget::Name(name) => resolve_name(ctx, name),
EntityTarget::Entity(entity) => Some(entity),
}
}
fn resolve_name(ctx: &PipelineContext, name: AssetId) -> Option<crate::ecs::Entity> {
ctx.resource::<crate::ecs::decompose::EntityByName>()?
.0
.get(&name)
.copied()
}
mod despawn;
mod visibility;
use concinnity_core::spawn as template;
fn record_clone_static(
ops: &mut RenderOps,
slots: &mut RenderSlots,
src: usize,
model: [[f32; 4]; 4],
) -> Option<usize> {
use crate::gfx::draw_slot::SlotAlloc;
let dst = slots.allocate_draw();
let idx = match dst {
SlotAlloc::Reuse(i) | SlotAlloc::Append(i) => i,
};
ops.record(move |backend| {
if let Err(e) = backend.clone_static_draw_object(src, model, dst) {
tracing::warn!("SpawnSystem: draw-slot clone of {} failed: {}", src, e);
}
});
Some(idx)
}
fn record_skinned_claim(
ops: &mut RenderOps,
slots: &mut RenderSlots,
template: usize,
model: [[f32; 4]; 4],
) -> Option<usize> {
let instance = slots.claim_skinned(template)?;
ops.record(move |backend| backend.reveal_skinned_instance(instance, model));
Some(instance)
}
#[derive(Debug, Default)]
pub(crate) struct SpawnSystem {
despawn_cmd_cursor: crate::ecs::EventCursor,
reparent_cmd_cursor: crate::ecs::EventCursor,
spawn_cmd_cursor: crate::ecs::EventCursor,
visibility_cmd_cursor: crate::ecs::EventCursor,
start_time: Option<Instant>,
prev_elapsed: f32,
}
impl SpawnSystem {
pub(crate) fn new() -> Self {
Self::default()
}
}
impl System for SpawnSystem {
fn step(&mut self, ctx: &mut PipelineContext) -> StepResult {
let Some(mut queues) = ActiveRenderQueues::take(ctx.resources) else {
return StepResult::Continue;
};
self.drain(ctx, &mut queues.ops, &mut queues.slots);
ActiveRenderQueues::put(ctx.resources, queues);
StepResult::Continue
}
}
impl SpawnSystem {
fn drain(&mut self, ctx: &mut PipelineContext, ops: &mut RenderOps, slots: &mut RenderSlots) {
let elapsed = self
.start_time
.get_or_insert_with(Instant::now)
.elapsed()
.as_secs_f32();
let dt = (elapsed - self.prev_elapsed).max(0.0);
self.prev_elapsed = elapsed;
let frame = ctx.frame;
let menu_active = ctx
.resource::<crate::ecs::MenuActive>()
.map(|m| m.0)
.unwrap_or(false);
if !menu_active {
let expired = template::tick_lifetimes(ctx, dt);
for &entity in expired.iter() {
despawn::despawn_subtree(ctx, ops, slots, entity);
}
}
let despawn_targets = match ctx.events::<DespawnRequest>() {
Some(events) => {
frame.collect(events.read(&mut self.despawn_cmd_cursor).map(|r| r.target))
}
None => frame.collect([]),
};
for &target in &despawn_targets {
if let Some(entity) = resolve_target(ctx, target) {
despawn::despawn_subtree(ctx, ops, slots, entity);
}
}
let vis_reqs = match ctx.events::<VisibilityRequest>() {
Some(events) => frame.collect(events.read(&mut self.visibility_cmd_cursor).copied()),
None => frame.collect([]),
};
for &req in &vis_reqs {
if let Some(entity) = resolve_target(ctx, req.target) {
visibility::set_subtree_visibility(ctx, ops, entity, req.visible);
}
}
let reparents = match ctx.events::<ReparentRequest>() {
Some(events) => frame.collect(events.read(&mut self.reparent_cmd_cursor).copied()),
None => frame.collect([]),
};
for &req in &reparents {
let Some(child) = resolve_target(ctx, req.child) else {
continue;
};
let parent = req.parent.and_then(|p| resolve_target(ctx, p));
if req.parent.is_some() && parent.is_none() {
continue;
}
transform_propagation::reparent(ctx, child, parent);
}
let spawn_reqs = match ctx.events::<SpawnRequest>() {
Some(events) => frame.collect(events.read(&mut self.spawn_cmd_cursor).copied()),
None => frame.collect([]),
};
for &req in spawn_reqs.iter() {
let Some(template) = resolve_name(ctx, req.template) else {
continue;
};
if ctx
.get::<crate::components::SkeletonPose>(template)
.is_some()
{
template::spawn_skinned_from_template(
ctx,
template,
req.name,
req.transform,
req.lifetime_secs,
|tmpl, model| record_skinned_claim(ops, slots, tmpl, model),
);
} else {
template::spawn_from_template(
ctx,
template,
req.name,
req.transform,
req.lifetime_secs,
|src, model| record_clone_static(ops, slots, src, model),
);
}
}
let due_spawns = if menu_active {
frame.collect([])
} else {
template::tick_spawners(ctx, dt)
};
for &due in due_spawns.iter() {
let Some(template) = resolve_name(ctx, due.template) else {
continue;
};
if ctx
.get::<crate::components::SkeletonPose>(template)
.is_some()
{
template::spawn_skinned_from_template(
ctx,
template,
None,
due.transform,
due.lifetime,
|tmpl, model| record_skinned_claim(ops, slots, tmpl, model),
);
} else {
template::spawn_from_template(
ctx,
template,
None,
due.transform,
due.lifetime,
|src, model| record_clone_static(ops, slots, src, model),
);
}
}
}
}