use crate::simple_world::{EntityId, SimpleWorld};
use alloc::vec::Vec;
use soroban_sdk::{Bytes, Env, Symbol};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ComponentEventKind {
Added,
Removed,
}
#[derive(Debug, Clone)]
pub struct ComponentEvent {
pub entity_id: EntityId,
pub component_type: Symbol,
pub kind: ComponentEventKind,
}
pub type ObserverFn = fn(event: &ComponentEvent, world: &SimpleWorld, env: &Env);
pub struct ObserverRegistry {
observers: Vec<(Symbol, ComponentEventKind, ObserverFn)>,
}
impl ObserverRegistry {
pub fn new() -> Self {
Self {
observers: Vec::new(),
}
}
pub fn on_add(&mut self, component_type: Symbol, observer: ObserverFn) {
self.observers
.push((component_type, ComponentEventKind::Added, observer));
}
pub fn on_remove(&mut self, component_type: Symbol, observer: ObserverFn) {
self.observers
.push((component_type, ComponentEventKind::Removed, observer));
}
pub fn fire(&self, event: &ComponentEvent, world: &SimpleWorld, env: &Env) {
for (ctype, kind, observer) in &self.observers {
if ctype == &event.component_type && kind == &event.kind {
observer(event, world, env);
}
}
}
pub fn observer_count(&self) -> usize {
self.observers.len()
}
}
impl Default for ObserverRegistry {
fn default() -> Self {
Self::new()
}
}
pub struct ObservedWorld {
world: SimpleWorld,
observers: ObserverRegistry,
}
impl ObservedWorld {
pub fn new(world: SimpleWorld) -> Self {
Self {
world,
observers: ObserverRegistry::new(),
}
}
pub fn with_observers(world: SimpleWorld, observers: ObserverRegistry) -> Self {
Self { world, observers }
}
pub fn world(&self) -> &SimpleWorld {
&self.world
}
pub fn world_mut(&mut self) -> &mut SimpleWorld {
&mut self.world
}
pub fn observers(&self) -> &ObserverRegistry {
&self.observers
}
pub fn observers_mut(&mut self) -> &mut ObserverRegistry {
&mut self.observers
}
pub fn into_inner(self) -> SimpleWorld {
self.world
}
pub fn spawn_entity(&mut self) -> EntityId {
self.world.spawn_entity()
}
pub fn add_component(
&mut self,
entity_id: EntityId,
component_type: Symbol,
data: Bytes,
env: &Env,
) {
self.world
.add_component(entity_id, component_type.clone(), data);
let event = ComponentEvent {
entity_id,
component_type,
kind: ComponentEventKind::Added,
};
self.observers.fire(&event, &self.world, env);
}
pub fn remove_component(
&mut self,
entity_id: EntityId,
component_type: &Symbol,
env: &Env,
) -> bool {
let removed = self.world.remove_component(entity_id, component_type);
if removed {
let event = ComponentEvent {
entity_id,
component_type: component_type.clone(),
kind: ComponentEventKind::Removed,
};
self.observers.fire(&event, &self.world, env);
}
removed
}
pub fn get_component(&self, entity_id: EntityId, component_type: &Symbol) -> Option<Bytes> {
self.world.get_component(entity_id, component_type)
}
pub fn has_component(&self, entity_id: EntityId, component_type: &Symbol) -> bool {
self.world.has_component(entity_id, component_type)
}
pub fn despawn_entity(&mut self, entity_id: EntityId, env: &Env) {
if let Some(types) = self.world.entity_components.get(entity_id) {
for i in 0..types.len() {
if let Some(t) = types.get(i) {
let event = ComponentEvent {
entity_id,
component_type: t,
kind: ComponentEventKind::Removed,
};
self.observers.fire(&event, &self.world, env);
}
}
}
self.world.despawn_entity(entity_id);
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::sync::atomic::{AtomicU32, Ordering};
use soroban_sdk::symbol_short;
static ADD_COUNT: AtomicU32 = AtomicU32::new(0);
static REMOVE_COUNT: AtomicU32 = AtomicU32::new(0);
fn counting_add_observer(_event: &ComponentEvent, _world: &SimpleWorld, _env: &Env) {
ADD_COUNT.fetch_add(1, Ordering::Relaxed);
}
fn counting_remove_observer(_event: &ComponentEvent, _world: &SimpleWorld, _env: &Env) {
REMOVE_COUNT.fetch_add(1, Ordering::Relaxed);
}
fn noop_observer(_event: &ComponentEvent, _world: &SimpleWorld, _env: &Env) {}
#[test]
fn test_registry_new() {
let registry = ObserverRegistry::new();
assert_eq!(registry.observer_count(), 0);
}
#[test]
fn test_registry_register() {
let _env = Env::default();
let mut registry = ObserverRegistry::new();
registry.on_add(symbol_short!("pos"), noop_observer);
registry.on_remove(symbol_short!("pos"), noop_observer);
assert_eq!(registry.observer_count(), 2);
}
#[test]
fn test_observer_fires_on_add() {
let env = Env::default();
ADD_COUNT.store(0, Ordering::Relaxed);
let world = SimpleWorld::new(&env);
let mut observed = ObservedWorld::new(world);
observed
.observers_mut()
.on_add(symbol_short!("pos"), counting_add_observer);
let e1 = observed.spawn_entity();
let data = Bytes::from_array(&env, &[1, 2, 3]);
observed.add_component(e1, symbol_short!("pos"), data, &env);
assert_eq!(ADD_COUNT.load(Ordering::Relaxed), 1);
assert!(observed.has_component(e1, &symbol_short!("pos")));
}
#[test]
fn test_observer_fires_on_remove() {
let env = Env::default();
REMOVE_COUNT.store(0, Ordering::Relaxed);
let world = SimpleWorld::new(&env);
let mut observed = ObservedWorld::new(world);
observed
.observers_mut()
.on_remove(symbol_short!("pos"), counting_remove_observer);
let e1 = observed.spawn_entity();
let data = Bytes::from_array(&env, &[1]);
observed.add_component(e1, symbol_short!("pos"), data, &env);
assert!(observed.remove_component(e1, &symbol_short!("pos"), &env));
assert_eq!(REMOVE_COUNT.load(Ordering::Relaxed), 1);
}
#[test]
fn test_observer_not_fired_for_wrong_type() {
let env = Env::default();
ADD_COUNT.store(0, Ordering::Relaxed);
let world = SimpleWorld::new(&env);
let mut observed = ObservedWorld::new(world);
observed
.observers_mut()
.on_add(symbol_short!("vel"), counting_add_observer);
let e1 = observed.spawn_entity();
let data = Bytes::from_array(&env, &[1]);
observed.add_component(e1, symbol_short!("pos"), data, &env);
assert_eq!(ADD_COUNT.load(Ordering::Relaxed), 0);
}
#[test]
fn test_observed_world_despawn() {
let env = Env::default();
let before = REMOVE_COUNT.load(Ordering::Relaxed);
let world = SimpleWorld::new(&env);
let mut observed = ObservedWorld::new(world);
observed
.observers_mut()
.on_remove(symbol_short!("a"), counting_remove_observer);
observed
.observers_mut()
.on_remove(symbol_short!("b"), counting_remove_observer);
let e1 = observed.spawn_entity();
let data = Bytes::from_array(&env, &[1]);
observed.add_component(e1, symbol_short!("a"), data.clone(), &env);
observed.add_component(e1, symbol_short!("b"), data, &env);
observed.despawn_entity(e1, &env);
let after = REMOVE_COUNT.load(Ordering::Relaxed);
assert_eq!(after - before, 2);
}
#[test]
fn test_observed_world_into_inner() {
let env = Env::default();
let world = SimpleWorld::new(&env);
let mut observed = ObservedWorld::new(world);
let e1 = observed.spawn_entity();
let data = Bytes::from_array(&env, &[1]);
observed.add_component(e1, symbol_short!("test"), data, &env);
let inner = observed.into_inner();
assert!(inner.has_component(e1, &symbol_short!("test")));
}
#[test]
fn test_with_observers_constructor() {
let env = Env::default();
let world = SimpleWorld::new(&env);
let mut observers = ObserverRegistry::new();
observers.on_add(symbol_short!("pos"), noop_observer);
let observed = ObservedWorld::with_observers(world, observers);
assert_eq!(observed.observers().observer_count(), 1);
}
}