use crate::{
BufferAccessStorage, Buffered, ManageDisposal, ManageInput, MiscellaneousFailure,
OperationError, OperationResult, OperationRoster, OrBroken, ScopeStorage, UnhandledErrors,
};
use bevy_ecs::prelude::{Component, Entity, World};
use std::{collections::HashMap, sync::Arc};
use anyhow::anyhow;
use smallvec::SmallVec;
pub struct OperationCleanup<'a> {
pub source: Entity,
pub cleanup: Cleanup,
pub world: &'a mut World,
pub roster: &'a mut OperationRoster,
}
impl<'a> OperationCleanup<'a> {
pub fn new(
cleaner: Entity,
node: Entity,
session: Entity,
cleanup_id: Entity,
world: &'a mut World,
roster: &'a mut OperationRoster,
) -> Self {
let cleanup = Cleanup {
cleaner,
node,
session,
cleanup_id,
};
Self {
source: node,
cleanup,
world,
roster,
}
}
pub fn clean(&mut self) {
let Some(cleanup) = self.world.get::<OperationCleanupStorage>(self.source) else {
return;
};
let cleanup = cleanup.0;
if let Err(error) = cleanup(OperationCleanup {
source: self.source,
cleanup: self.cleanup,
world: self.world,
roster: self.roster,
}) {
self.world
.get_resource_or_insert_with(UnhandledErrors::default)
.operations
.push(error);
}
}
pub fn cleanup_inputs<T: 'static + Send + Sync>(&mut self) -> OperationResult {
self.world
.get_entity_mut(self.source)
.or_broken()?
.cleanup_inputs::<T>(self.cleanup.session);
Ok(())
}
pub fn cleanup_disposals(&mut self) -> OperationResult {
let mut source_mut = self.world.get_entity_mut(self.source).or_broken()?;
let scope = source_mut.get::<ScopeStorage>().or_broken()?.get();
if self.cleanup.cleaner == scope {
source_mut.clear_disposals(self.cleanup.session);
}
Ok(())
}
pub fn cleanup_buffer_access<B>(&mut self) -> OperationResult
where
B: Buffered + 'static + Send + Sync,
B::Key: 'static + Send + Sync,
{
let scope = self
.world
.get::<ScopeStorage>(self.source)
.or_broken()?
.get();
if self.cleanup.cleaner == scope {
self.world
.get_mut::<BufferAccessStorage<B>>(self.source)
.or_broken()?
.keys
.remove(&self.cleanup.session);
}
Ok(())
}
pub fn notify_cleaned(&mut self) -> OperationResult {
self.cleanup.notify_cleaned(self.world, self.roster)
}
pub fn delegate_to(mut self, source: Entity) -> Self {
self.source = source;
self
}
}
#[derive(Default, Component)]
pub struct CleanupContents {
awaiting_cleanup: HashMap<Entity, SmallVec<[Entity; 16]>>,
}
impl CleanupContents {
pub fn new() -> Self {
Self::default()
}
pub fn add_cleanup(&mut self, cleanup_id: Entity, nodes: SmallVec<[Entity; 16]>) {
self.awaiting_cleanup.insert(cleanup_id, nodes);
}
pub fn register_cleanup_of_node(&mut self, cleanup_id: Entity, node: Entity) -> bool {
let Some(nodes) = self.awaiting_cleanup.get_mut(&cleanup_id) else {
return false;
};
nodes.retain(|n| *n != node);
nodes.is_empty()
}
}
pub struct FinalizeCleanupRequest<'a> {
pub cleanup: Cleanup,
pub world: &'a mut World,
pub roster: &'a mut OperationRoster,
}
#[derive(Component)]
pub(crate) struct OperationCleanupStorage(pub(super) fn(OperationCleanup) -> OperationResult);
#[derive(Component, Clone, Copy)]
pub struct FinalizeCleanup(pub(crate) fn(FinalizeCleanupRequest) -> OperationResult);
impl FinalizeCleanup {
pub fn new(f: fn(FinalizeCleanupRequest) -> OperationResult) -> Self {
Self(f)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Cleanup {
pub cleaner: Entity,
pub node: Entity,
pub session: Entity,
pub cleanup_id: Entity,
}
impl Cleanup {
pub(crate) fn notify_cleaned(
&self,
world: &mut World,
roster: &mut OperationRoster,
) -> OperationResult {
let mut cleaner_mut = world.get_entity_mut(self.cleaner).or_broken()?;
let mut scope_contents = cleaner_mut.get_mut::<CleanupContents>().or_broken()?;
if scope_contents.register_cleanup_of_node(self.cleanup_id, self.node) {
roster.cleanup_finished(*self);
scope_contents.awaiting_cleanup.remove(&self.cleanup_id);
}
Ok(())
}
pub(crate) fn trigger(self, world: &mut World, roster: &mut OperationRoster) {
match world.get_mut::<CleanupContents>(self.cleaner) {
Some(mut contents) => {
contents.awaiting_cleanup.remove(&self.cleanup_id);
}
None => {
world
.get_resource_or_insert_with(UnhandledErrors::default)
.miscellaneous
.push(MiscellaneousFailure {
error: Arc::new(anyhow!("Failed to clear cleanup tracker: {self:?}")),
backtrace: Some(backtrace::Backtrace::new()),
});
}
}
let Some(FinalizeCleanup(f)) = world.get::<FinalizeCleanup>(self.cleaner).copied() else {
return;
};
if let Err(OperationError::Broken(backtrace)) = (f)(FinalizeCleanupRequest {
cleanup: self,
world,
roster,
}) {
world
.get_resource_or_insert_with(UnhandledErrors::default)
.miscellaneous
.push(MiscellaneousFailure {
error: Arc::new(anyhow!("Failed to finalize cleanup: {self:?}")),
backtrace,
})
}
}
}