use std::{marker::PhantomData, sync::Arc};
use bevy_app::App;
use bevy_ecs::world::{Mut, World};
use parking_lot::Mutex;
use naia_bevy_shared::{
PropertyMutate, PropertyMutator, Replicate, ReplicatedResource, WorldMutType, WorldProxyMut,
};
use crate::server::ServerImpl;
#[derive(bevy_ecs::resource::Resource)]
pub struct SyncDirtyTracker<R: Replicate> {
pub(crate) inner: Arc<Mutex<Vec<u8>>>,
_phantom: PhantomData<R>,
}
impl<R: Replicate> Default for SyncDirtyTracker<R> {
fn default() -> Self {
Self {
inner: Arc::new(Mutex::new(Vec::new())),
_phantom: PhantomData,
}
}
}
impl<R: Replicate> SyncDirtyTracker<R> {
pub(crate) fn drain_unique(&self) -> Vec<u8> {
let mut g = self.inner.lock();
if g.is_empty() {
return Vec::new();
}
let mut out: Vec<u8> = g.drain(..).collect();
out.sort_unstable();
out.dedup();
out
}
}
pub(crate) struct SyncMutator<R: Replicate> {
inner: Arc<Mutex<Vec<u8>>>,
_phantom: PhantomData<R>,
}
impl<R: Replicate> Clone for SyncMutator<R> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
_phantom: PhantomData,
}
}
}
impl<R: Replicate> SyncMutator<R> {
pub(crate) fn new(tracker: &SyncDirtyTracker<R>) -> Self {
Self {
inner: Arc::clone(&tracker.inner),
_phantom: PhantomData,
}
}
}
impl<R: Replicate> PropertyMutate for SyncMutator<R> {
fn mutate(&mut self, property_index: u8) -> bool {
self.inner.lock().push(property_index);
true
}
}
type SyncHook = Box<dyn Fn(&mut World) + Send + Sync + 'static>;
#[derive(bevy_ecs::resource::Resource, Default)]
pub(crate) struct ResourceSyncDispatcher {
hooks: Vec<SyncHook>,
}
impl ResourceSyncDispatcher {
fn run_all(world: &mut World) {
let hooks: Vec<SyncHook> = world
.resource_mut::<ResourceSyncDispatcher>()
.hooks
.drain(..)
.collect();
for hook in &hooks {
hook(world);
}
world.resource_mut::<ResourceSyncDispatcher>().hooks = hooks;
}
}
fn ensure_dispatcher_system_installed(app: &mut App) {
if app
.world()
.get_resource::<ResourceSyncDispatcher>()
.is_none()
{
app.insert_resource(ResourceSyncDispatcher::default());
app.add_systems(bevy_app::Update, ResourceSyncDispatcher::run_all);
}
}
pub(crate) fn install_resource_sync_system<R: ReplicatedResource>(app: &mut App) {
ensure_dispatcher_system_installed(app);
if app
.world()
.get_resource::<ResourceSyncInstalled<R>>()
.is_some()
{
return;
}
app.insert_resource(ResourceSyncInstalled::<R> {
_phantom: PhantomData,
});
let hook: SyncHook = Box::new(sync_resource_outgoing::<R>);
app.world_mut()
.resource_mut::<ResourceSyncDispatcher>()
.hooks
.push(hook);
}
#[derive(bevy_ecs::resource::Resource)]
struct ResourceSyncInstalled<R: Replicate> {
_phantom: PhantomData<R>,
}
fn sync_resource_outgoing<R: ReplicatedResource>(world: &mut World) {
let dirty: Vec<u8> = match world.get_resource::<SyncDirtyTracker<R>>() {
Some(t) => t.drain_unique(),
None => return,
};
if dirty.is_empty() {
return;
}
world.resource_scope::<ServerImpl, _>(|world, server: Mut<ServerImpl>| {
let Some(entity) = server.resource_entity::<R>() else {
return;
};
let snapshot: Box<dyn Replicate> = match world.get_resource::<R>() {
Some(r) => r.copy_to_box(),
None => return,
};
let mut world_mut = world.proxy_mut();
let Some(mut entity_comp) = world_mut.component_mut::<R>(&entity) else {
return;
};
for &idx in &dirty {
entity_comp.mirror_single_field(idx, snapshot.as_ref());
}
});
}
pub(crate) fn wire_sync_mutator<R: Replicate>(
value: &mut R,
tracker: &SyncDirtyTracker<R>,
) {
let sync = SyncMutator::<R>::new(tracker);
let mutator = PropertyMutator::new(sync);
value.set_mutator(&mutator);
}
pub(crate) fn install_bevy_resource_mirror<R: ReplicatedResource>(
world: &mut World,
mut value: R,
) {
let tracker = world.get_resource::<SyncDirtyTracker<R>>().unwrap_or_else(|| {
panic!(
"naia replicate_resource: missing SyncDirtyTracker<{0}>. \
You must call `app.add_resource_events::<{0}>()` before \
`commands.replicate_resource(...)`.",
std::any::type_name::<R>()
)
});
wire_sync_mutator::<R>(&mut value, tracker);
world.insert_resource(value);
}