use crate::{
cx::Cx,
tracking_scope::{TrackingScope, TrackingScopeTracing},
};
use bevy::{
ecs::{system::SystemState, world::DeferredWorld},
hierarchy::{Children, HierarchyQueryExt, Parent},
log::warn,
prelude::{Added, Component, Entity, Query, With, World},
utils::hashbrown::HashSet,
};
use impl_trait_for_tuples::*;
use std::{
any::Any,
sync::{Arc, Mutex},
};
#[cfg(feature = "verbose")]
use bevy::log::info;
pub trait View: Sync + Send + 'static {
type State: Send + Sync;
fn nodes(&self, world: &World, state: &Self::State, out: &mut Vec<Entity>);
fn build(&self, cx: &mut Cx) -> Self::State;
fn rebuild(&self, cx: &mut Cx, state: &mut Self::State) -> bool;
#[allow(unused)]
fn attach_children(&self, world: &mut World, state: &mut Self::State) -> bool {
false
}
fn raze(&self, world: &mut DeferredWorld, state: &mut Self::State);
fn to_root(self) -> (ViewStateCell<Self>, ViewThunk, ViewRoot)
where
Self: Sized,
{
let holder = ViewStateCell::new(self);
let thunk = holder.create_thunk();
(holder, thunk, ViewRoot)
}
fn view_type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<Self>()
}
}
#[derive(Component)]
pub struct OutputChanged;
pub struct ViewState<V: View> {
pub(crate) view: V,
pub(crate) state: Option<V::State>,
}
impl<V: View> ViewState<V> {
fn rebuild(&mut self, cx: &mut Cx) -> bool {
if let Some(state) = self.state.as_mut() {
self.view.rebuild(cx, state)
} else {
let state = self.view.build(cx);
self.state = Some(state);
true
}
}
fn raze(&mut self, world: &mut DeferredWorld) {
if let Some(state) = self.state.as_mut() {
self.view.raze(world, state);
}
}
fn attach_children(&mut self, world: &mut World) -> bool {
if let Some(state) = self.state.as_mut() {
self.view.attach_children(world, state)
} else {
false
}
}
}
#[derive(Component)]
pub struct ViewStateCell<V: View>(pub Arc<Mutex<ViewState<V>>>);
impl<V: View> ViewStateCell<V> {
pub fn new(view: V) -> Self {
Self(Arc::new(Mutex::new(ViewState { view, state: None })))
}
pub fn create_thunk(&self) -> ViewThunk {
ViewThunk(&ViewAdapter::<V> {
marker: std::marker::PhantomData,
})
}
}
pub struct ViewAdapter<V: View> {
marker: std::marker::PhantomData<V>,
}
pub trait AnyViewAdapter: Sync + Send + 'static {
fn nodes(&self, world: &mut World, entity: Entity, out: &mut Vec<Entity>);
fn rebuild(&self, world: &mut World, entity: Entity, scope: &mut TrackingScope) -> bool;
fn raze(&self, world: &mut DeferredWorld, entity: Entity);
fn attach_children(&self, world: &mut World, entity: Entity) -> bool;
}
impl<V: View> AnyViewAdapter for ViewAdapter<V> {
fn nodes(&self, world: &mut World, entity: Entity, out: &mut Vec<Entity>) {
if let Some(view_cell) = world.entity(entity).get::<ViewStateCell<V>>() {
let vstate = view_cell.0.lock().unwrap();
match &vstate.state {
Some(state) => vstate.view.nodes(world, state, out),
None => {}
}
}
}
fn rebuild(&self, world: &mut World, entity: Entity, scope: &mut TrackingScope) -> bool {
let mut cx = Cx::new(world, entity, scope);
if let Some(view_cell) = cx
.world_mut()
.entity_mut(entity)
.get_mut::<ViewStateCell<V>>()
{
let inner = view_cell.0.clone();
let mut vstate = inner.lock().unwrap();
vstate.rebuild(&mut cx)
} else {
false
}
}
fn raze(&self, world: &mut DeferredWorld, entity: Entity) {
if let Some(vsh) = world.entity(entity).get::<ViewStateCell<V>>() {
let inner = vsh.0.clone();
inner.lock().unwrap().raze(world);
}
}
fn attach_children(&self, world: &mut World, entity: Entity) -> bool {
if let Some(view_cell) = world.entity(entity).get::<ViewStateCell<V>>() {
let vs = view_cell.0.clone();
let mut inner = vs.lock().unwrap();
inner.attach_children(world)
} else {
false
}
}
}
#[derive(Component)]
pub struct ViewThunk(pub(crate) &'static dyn AnyViewAdapter);
impl ViewThunk {
pub fn nodes(&self, world: &mut World, entity: Entity, out: &mut Vec<Entity>) {
self.0.nodes(world, entity, out);
}
pub fn rebuild(&self, world: &mut World, entity: Entity, scope: &mut TrackingScope) -> bool {
self.0.rebuild(world, entity, scope)
}
pub fn raze(&self, world: &mut DeferredWorld, entity: Entity) {
self.0.raze(world, entity)
}
pub fn attach_children(&self, world: &mut World, entity: Entity) -> bool {
self.0.attach_children(world, entity)
}
}
#[derive(Component)]
pub struct ViewRoot;
impl View for () {
type State = ();
fn nodes(&self, _world: &World, _state: &Self::State, _out: &mut Vec<Entity>) {}
fn build(&self, _cx: &mut Cx) -> Self::State {}
fn rebuild(&self, _cx: &mut Cx, _state: &mut Self::State) -> bool {
false
}
fn raze(&self, _world: &mut DeferredWorld, _state: &mut Self::State) {}
}
impl<V: View> View for (V,) {
type State = V::State;
fn nodes(&self, world: &World, state: &Self::State, out: &mut Vec<Entity>) {
self.0.nodes(world, state, out);
}
fn build(&self, cx: &mut Cx) -> Self::State {
self.0.build(cx)
}
fn rebuild(&self, cx: &mut Cx, state: &mut Self::State) -> bool {
self.0.rebuild(cx, state)
}
fn raze(&self, world: &mut DeferredWorld, state: &mut Self::State) {
self.0.raze(world, state)
}
}
#[impl_for_tuples(2, 32)]
#[tuple_types_custom_trait_bound(View)]
impl View for Tuple {
for_tuples!( type State = ( #( Tuple::State ),* ); );
#[rustfmt::skip]
fn nodes(&self, world: &World, state: &Self::State, out: &mut Vec<Entity>) {
for_tuples!(#( self.Tuple.nodes(world, &state.Tuple, out); )*);
}
fn build(&self, cx: &mut Cx) -> Self::State {
for_tuples!((#( self.Tuple.build(cx) ),*))
}
fn rebuild(&self, cx: &mut Cx, state: &mut Self::State) -> bool {
let mut changed = false;
for_tuples!(#( changed |= self.Tuple.rebuild(cx, &mut state.Tuple); )*);
changed
}
fn raze(&self, world: &mut DeferredWorld, state: &mut Self::State) {
for_tuples!(#( self.Tuple.raze(world, &mut state.Tuple); )*)
}
fn attach_children(&self, world: &mut World, state: &mut Self::State) -> bool {
let mut changed = false;
for_tuples!(#( changed |= self.Tuple.attach_children(world, &mut state.Tuple); )*);
changed
}
}
impl<V: View> View for Option<V> {
type State = Option<V::State>;
fn nodes(&self, world: &World, state: &Self::State, out: &mut Vec<Entity>) {
if let (Some(view), Some(state)) = (self, state) {
view.nodes(world, state, out)
}
}
fn build(&self, cx: &mut Cx) -> Self::State {
self.as_ref().map(|view| view.build(cx))
}
fn rebuild(&self, cx: &mut Cx, state: &mut Self::State) -> bool {
match (self, state) {
(Some(view), Some(state)) => view.rebuild(cx, state),
(None, None) => false,
_ => panic!("Option<View>::rebuild(): state is out of sync"),
}
}
fn attach_children(&self, world: &mut World, state: &mut Self::State) -> bool {
match (self, state) {
(Some(view), Some(state)) => view.attach_children(world, state),
(None, None) => false,
_ => panic!("Option<View>::attach_children(): state is out of sync"),
}
}
fn raze(&self, world: &mut DeferredWorld, state: &mut Self::State) {
match (self, state) {
(Some(view), Some(state)) => view.raze(world, state),
(None, None) => {}
_ => panic!("Option<View>::raze(): state is out of sync"),
}
}
fn view_type_id(&self) -> std::any::TypeId {
match self {
Some(view) => view.view_type_id(),
None => std::any::TypeId::of::<Option<V>>(),
}
}
}
pub(crate) type BoxedState = Box<dyn Any + Send + Sync>;
pub trait AnyView: Sync + Send + 'static {
fn nodes(&self, world: &World, state: &BoxedState, out: &mut Vec<Entity>);
fn build(&self, cx: &mut Cx) -> BoxedState;
fn rebuild(&self, cx: &mut Cx, state: &mut BoxedState) -> bool;
#[allow(unused)]
fn attach_children(&self, world: &mut World, state: &mut BoxedState) -> bool;
fn raze(&self, world: &mut DeferredWorld, state: &mut BoxedState);
fn view_type_id(&self) -> std::any::TypeId;
}
impl<V: View> AnyView for V {
fn nodes(&self, world: &World, state: &BoxedState, out: &mut Vec<Entity>) {
if let Some(state) = state.downcast_ref::<V::State>() {
View::nodes(self, world, state, out)
}
}
fn build(&self, cx: &mut Cx) -> BoxedState {
Box::new(View::build(self, cx))
}
fn rebuild(&self, cx: &mut Cx, state: &mut BoxedState) -> bool {
match state.downcast_mut::<V::State>() {
Some(state) => View::rebuild(self, cx, state),
None => panic!(
"View state type mismatch in rebuild(): type={}",
std::any::type_name::<V>()
),
}
}
fn attach_children(&self, world: &mut World, state: &mut BoxedState) -> bool {
View::attach_children(self, world, state.downcast_mut::<V::State>().unwrap())
}
fn raze(&self, world: &mut DeferredWorld, state: &mut BoxedState) {
if let Some(state) = state.downcast_mut::<V::State>() {
View::raze(self, world, state);
} else {
warn!(
"Failed to downcast state in raze(): type={}",
std::any::type_name::<V>()
);
}
}
fn view_type_id(&self) -> std::any::TypeId {
View::view_type_id(self)
}
}
pub(crate) fn build_views(world: &mut World) {
let mut roots = world.query_filtered::<(Entity, &ViewThunk), Added<ViewRoot>>();
let roots_copy: Vec<Entity> = roots.iter(world).map(|(e, _)| e).collect();
let tick = world.change_tick();
for root_entity in roots_copy.iter() {
let Ok((_, root)) = roots.get(world, *root_entity) else {
continue;
};
let mut scope = TrackingScope::new(tick);
root.0.rebuild(world, *root_entity, &mut scope);
world.entity_mut(*root_entity).insert(scope);
}
}
const MAX_DIVERGENCE_CT: usize = 32;
pub(crate) fn reaction_control_system(world: &mut World) {
let is_tracing = world.get_resource_mut::<TrackingScopeTracing>().is_some();
let mut all_reactions: Vec<Entity> = Vec::new();
let mut iteration_ct: usize = 0;
let mut divergence_ct: usize = 0;
let mut prev_change_ct: usize = 0;
loop {
let this_run = if iteration_ct > 0 {
world.increment_change_tick()
} else {
world.change_tick()
};
let mut st: SystemState<(
Query<Entity, With<ViewRoot>>,
Query<&Children>,
Query<(Entity, &TrackingScope)>,
)> = SystemState::new(world);
let (roots, children, scopes) = st.get(world);
let roots = roots.iter().collect::<Vec<_>>();
let mut changed: Vec<Entity> = Vec::with_capacity(64);
for root in roots {
children.iter_descendants(root).for_each(|child| {
if let Ok(scope) = scopes.get(child) {
if scope.1.dependencies_changed(world, this_run) {
changed.push(child);
}
}
});
}
if changed.is_empty() {
break;
}
if is_tracing {
all_reactions.extend(changed.clone());
}
run_cleanups(world, &changed);
let mut scopes = world.query::<(Entity, &mut TrackingScope, &ViewThunk)>();
for scope_entity in changed.iter() {
let Ok((_, mut scope, view_cell)) = scopes.get_mut(world, *scope_entity) else {
continue;
};
let mut next_scope = TrackingScope::new(this_run);
next_scope.take_hooks(scope.as_mut());
let output_changed = view_cell.0.rebuild(world, *scope_entity, &mut next_scope);
if output_changed {
#[cfg(feature = "verbose")]
info!("View output changed: {}", *scope_entity);
world.entity_mut(*scope_entity).insert(OutputChanged);
}
let (_, mut scope, _) = scopes.get_mut(world, *scope_entity).unwrap();
scope.take_deps(&mut next_scope);
scope.tick = this_run;
}
iteration_ct += 1;
let change_ct = changed.len();
if change_ct >= prev_change_ct {
divergence_ct += 1;
if divergence_ct > MAX_DIVERGENCE_CT {
panic!("Reactions failed to converge, num changes: {}", change_ct);
}
}
prev_change_ct = change_ct;
}
if let Some(mut tracing) = world.get_resource_mut::<TrackingScopeTracing>() {
std::mem::swap(&mut tracing.0, &mut all_reactions);
}
}
fn run_cleanups(world: &mut World, changed: &[Entity]) {
let mut deferred = DeferredWorld::from(world);
for scope_entity in changed.iter() {
let Some(mut scope) = deferred.get_mut::<TrackingScope>(*scope_entity) else {
continue;
};
let mut cleanups = std::mem::take(&mut scope.cleanups);
for cleanup_fn in cleanups.drain(..) {
cleanup_fn(&mut deferred);
}
}
}
pub(crate) fn reattach_children(world: &mut World) {
let mut changed_views = Vec::<Entity>::new();
let mut work_queue = HashSet::<Entity>::new();
let mut changed_views_query = world.query_filtered::<Entity, With<OutputChanged>>();
for view_entity in changed_views_query.iter(world) {
changed_views.push(view_entity);
if let Some(parent) = world.entity(view_entity).get::<Parent>() {
work_queue.insert(parent.get());
}
}
for view_entity in changed_views.drain(..) {
world.entity_mut(view_entity).remove::<OutputChanged>();
}
while !work_queue.is_empty() {
let entity = *work_queue.iter().next().unwrap();
work_queue.remove(&entity);
if let Some(thunk) = world.entity(entity).get::<ViewThunk>() {
if thunk.0.attach_children(world, entity) {
if let Some(parent) = world.entity(entity).get::<Parent>() {
work_queue.insert(parent.get());
}
}
}
if work_queue.is_empty() {
break;
}
}
}
pub(crate) fn cleanup_view_roots(world: &mut World) {
world
.register_component_hooks::<ViewRoot>()
.on_remove(|mut world, entity, _component| {
let thunk = world.get_mut::<ViewThunk>(entity).unwrap();
thunk.0.raze(&mut world, entity);
});
}