Make ECS hooks subscribe to changes

This commit is contained in:
JMS55 2023-12-12 16:22:54 -08:00
parent deb6ddb2fe
commit 99929cbc7f
3 changed files with 81 additions and 10 deletions

View file

@ -2,21 +2,65 @@ use crate::{
deferred_system::{new_deferred_system, DeferredSystem}, deferred_system::{new_deferred_system, DeferredSystem},
tick::EcsContext, tick::EcsContext,
}; };
use bevy::ecs::{ use bevy::{
query::{QueryState, ReadOnlyWorldQuery}, ecs::{
system::{IntoSystem, Query, Resource}, component::ComponentId,
world::{unsafe_world_cell::UnsafeWorldCell, World}, query::{QueryState, ReadOnlyWorldQuery},
system::{IntoSystem, Query, Resource},
world::{unsafe_world_cell::UnsafeWorldCell, World},
},
utils::{HashMap, HashSet},
};
use dioxus::{
core::{ScopeId, ScopeState},
hooks::use_on_destroy,
}; };
use dioxus::core::ScopeState;
// TODO: Hooks need to schedule future updates #[derive(Resource, Default)]
pub(crate) struct EcsSubscriptions {
pub resources: Box<HashMap<ComponentId, HashSet<ScopeId>>>,
pub world_and_queries: Box<HashSet<ScopeId>>,
}
pub fn use_world<'a>(cx: &'a ScopeState) -> &'a World { pub fn use_world<'a>(cx: &'a ScopeState) -> &'a World {
EcsContext::get_world(cx) let world = EcsContext::get_world(cx);
let scope_id = cx.scope_id();
let subscription_manager = *cx.use_hook(|| {
let subscription_manager = &mut world.resource_mut::<EcsSubscriptions>().world_and_queries;
subscription_manager.insert(scope_id);
Box::as_mut(subscription_manager) as *mut HashSet<ScopeId>
});
use_on_destroy(cx, move || {
unsafe { &mut *subscription_manager }.remove(&scope_id);
});
world
} }
pub fn use_resource<'a, T: Resource>(cx: &'a ScopeState) -> &'a T { pub fn use_resource<'a, T: Resource>(cx: &'a ScopeState) -> &'a T {
EcsContext::get_world(cx).resource() let world = EcsContext::get_world(cx);
let resource_id = world.components().resource_id::<T>().unwrap();
let scope_id = cx.scope_id();
let subscription_manager = *cx.use_hook(|| {
let subscription_manager = &mut world.resource_mut::<EcsSubscriptions>().resources;
subscription_manager
.entry(resource_id)
.or_default()
.insert(scope_id);
Box::as_mut(subscription_manager) as *mut HashMap<ComponentId, HashSet<ScopeId>>
});
use_on_destroy(cx, move || {
let subscription_manager = &mut unsafe { &mut *subscription_manager };
let resource_subscriptions = subscription_manager.get_mut(&resource_id).unwrap();
resource_subscriptions.remove(&scope_id);
if resource_subscriptions.is_empty() {
subscription_manager.remove(&resource_id);
}
});
world.resource()
} }
pub fn use_query<'a, Q>(cx: &'a ScopeState) -> DioxusUiQuery<'a, Q, ()> pub fn use_query<'a, Q>(cx: &'a ScopeState) -> DioxusUiQuery<'a, Q, ()>
@ -32,6 +76,17 @@ where
F: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery,
{ {
let world = EcsContext::get_world(cx); let world = EcsContext::get_world(cx);
let scope_id = cx.scope_id();
let subscription_manager = *cx.use_hook(|| {
let subscription_manager = &mut world.resource_mut::<EcsSubscriptions>().world_and_queries;
subscription_manager.insert(scope_id);
Box::as_mut(subscription_manager) as *mut HashSet<ScopeId>
});
use_on_destroy(cx, move || {
unsafe { &mut *subscription_manager }.remove(&scope_id);
});
DioxusUiQuery { DioxusUiQuery {
query_state: QueryState::new(world), query_state: QueryState::new(world),
world_cell: world.as_unsafe_world_cell(), world_cell: world.as_unsafe_world_cell(),

View file

@ -8,6 +8,7 @@ use self::{
apply_mutations::BevyTemplate, apply_mutations::BevyTemplate,
deferred_system::DeferredSystemRegistry, deferred_system::DeferredSystemRegistry,
events::EventReaders, events::EventReaders,
hooks::EcsSubscriptions,
tick::{tick_dioxus_ui, VirtualDomUnsafe}, tick::{tick_dioxus_ui, VirtualDomUnsafe},
}; };
use bevy::{ use bevy::{
@ -26,7 +27,8 @@ pub struct DioxusUiPlugin;
impl Plugin for DioxusUiPlugin { impl Plugin for DioxusUiPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.init_resource::<DeferredSystemRegistry>() app.init_resource::<EcsSubscriptions>()
.init_resource::<DeferredSystemRegistry>()
.init_resource::<EventReaders>() .init_resource::<EventReaders>()
.add_systems(Update, tick_dioxus_ui); .add_systems(Update, tick_dioxus_ui);
} }

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
apply_mutations::apply_mutations, deferred_system::DeferredSystemRegistry, apply_mutations::apply_mutations, deferred_system::DeferredSystemRegistry,
events::EventReaders, DioxusUiRoot, events::EventReaders, hooks::EcsSubscriptions, DioxusUiRoot,
}; };
use bevy::{ use bevy::{
ecs::{ ecs::{
@ -17,6 +17,7 @@ use std::{mem, rc::Rc, sync::Arc};
pub fn tick_dioxus_ui(world: &mut World) { pub fn tick_dioxus_ui(world: &mut World) {
let world_ptr: *mut World = world; let world_ptr: *mut World = world;
let world_cell = world.as_unsafe_world_cell(); let world_cell = world.as_unsafe_world_cell();
let ecs_subscriptions = unsafe { world_cell.get_resource::<EcsSubscriptions>().unwrap() };
let events = unsafe { let events = unsafe {
world_cell world_cell
.get_resource_mut::<EventReaders>() .get_resource_mut::<EventReaders>()
@ -42,6 +43,19 @@ pub fn tick_dioxus_ui(world: &mut World) {
} = &mut *dioxus_ui_root; } = &mut *dioxus_ui_root;
let virtual_dom = virtual_dom.get(); let virtual_dom = virtual_dom.get();
let schedule_update = virtual_dom.base_scope().schedule_update_any();
for scope_id in &*ecs_subscriptions.world_and_queries {
schedule_update(*scope_id);
}
for (resource_id, scope_ids) in &*ecs_subscriptions.resources {
// TODO: Only schedule updates if resource has changed
// if unsafe { world_cell.world() }.is_resource_changed() {
for scope_id in scope_ids {
schedule_update(*scope_id);
}
// }
}
virtual_dom virtual_dom
.base_scope() .base_scope()
.provide_context(EcsContext { world: world_ptr }); .provide_context(EcsContext { world: world_ptr });