use crate::simple_world::{EntityId, SimpleWorld};
use alloc::vec::Vec;
use soroban_sdk::{Bytes, Symbol};
pub type OnAddHook = fn(entity_id: EntityId, component_type: &Symbol, data: &Bytes);
pub type OnRemoveHook = fn(entity_id: EntityId, component_type: &Symbol);
pub struct HookRegistry {
add_hooks: Vec<(Symbol, OnAddHook)>,
remove_hooks: Vec<(Symbol, OnRemoveHook)>,
}
impl HookRegistry {
pub fn new() -> Self {
Self {
add_hooks: Vec::new(),
remove_hooks: Vec::new(),
}
}
pub fn on_add(&mut self, component_type: Symbol, hook: OnAddHook) {
self.add_hooks.push((component_type, hook));
}
pub fn on_remove(&mut self, component_type: Symbol, hook: OnRemoveHook) {
self.remove_hooks.push((component_type, hook));
}
pub fn fire_on_add(&self, entity_id: EntityId, component_type: &Symbol, data: &Bytes) {
for (ctype, hook) in &self.add_hooks {
if ctype == component_type {
hook(entity_id, component_type, data);
}
}
}
pub fn fire_on_remove(&self, entity_id: EntityId, component_type: &Symbol) {
for (ctype, hook) in &self.remove_hooks {
if ctype == component_type {
hook(entity_id, component_type);
}
}
}
pub fn add_hook_count(&self) -> usize {
self.add_hooks.len()
}
pub fn remove_hook_count(&self) -> usize {
self.remove_hooks.len()
}
}
impl Default for HookRegistry {
fn default() -> Self {
Self::new()
}
}
pub struct HookedWorld {
world: SimpleWorld,
hooks: HookRegistry,
}
impl HookedWorld {
pub fn new(world: SimpleWorld) -> Self {
Self {
world,
hooks: HookRegistry::new(),
}
}
pub fn with_hooks(world: SimpleWorld, hooks: HookRegistry) -> Self {
Self { world, hooks }
}
pub fn world(&self) -> &SimpleWorld {
&self.world
}
pub fn world_mut(&mut self) -> &mut SimpleWorld {
&mut self.world
}
pub fn hooks(&self) -> &HookRegistry {
&self.hooks
}
pub fn hooks_mut(&mut self) -> &mut HookRegistry {
&mut self.hooks
}
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) {
self.world
.add_component(entity_id, component_type.clone(), data.clone());
self.hooks.fire_on_add(entity_id, &component_type, &data);
}
pub fn remove_component(&mut self, entity_id: EntityId, component_type: &Symbol) -> bool {
self.hooks.fire_on_remove(entity_id, component_type);
self.world.remove_component(entity_id, component_type)
}
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) {
if let Some(types) = self.world.entity_components.get(entity_id) {
for i in 0..types.len() {
if let Some(t) = types.get(i) {
self.hooks.fire_on_remove(entity_id, &t);
}
}
}
self.world.despawn_entity(entity_id);
}
}
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::{symbol_short, Env};
fn noop_add_hook(_entity_id: EntityId, _component_type: &Symbol, _data: &Bytes) {}
fn noop_remove_hook(_entity_id: EntityId, _component_type: &Symbol) {}
#[test]
fn test_hook_registry_new() {
let registry = HookRegistry::new();
assert_eq!(registry.add_hook_count(), 0);
assert_eq!(registry.remove_hook_count(), 0);
}
#[test]
fn test_hook_registry_register() {
let mut registry = HookRegistry::new();
registry.on_add(symbol_short!("pos"), noop_add_hook);
registry.on_remove(symbol_short!("pos"), noop_remove_hook);
assert_eq!(registry.add_hook_count(), 1);
assert_eq!(registry.remove_hook_count(), 1);
}
#[test]
fn test_hook_registry_multiple_hooks() {
let mut registry = HookRegistry::new();
registry.on_add(symbol_short!("pos"), noop_add_hook);
registry.on_add(symbol_short!("vel"), noop_add_hook);
registry.on_remove(symbol_short!("pos"), noop_remove_hook);
assert_eq!(registry.add_hook_count(), 2);
assert_eq!(registry.remove_hook_count(), 1);
}
#[test]
fn test_hooked_world_add_component() {
let env = Env::default();
let world = SimpleWorld::new(&env);
let mut hooked = HookedWorld::new(world);
hooked
.hooks_mut()
.on_add(symbol_short!("pos"), noop_add_hook);
let e1 = hooked.spawn_entity();
let data = Bytes::from_array(&env, &[1, 2, 3, 4]);
hooked.add_component(e1, symbol_short!("pos"), data.clone());
assert!(hooked.has_component(e1, &symbol_short!("pos")));
assert_eq!(hooked.get_component(e1, &symbol_short!("pos")), Some(data));
}
#[test]
fn test_hooked_world_remove_component() {
let env = Env::default();
let world = SimpleWorld::new(&env);
let mut hooked = HookedWorld::new(world);
hooked
.hooks_mut()
.on_remove(symbol_short!("pos"), noop_remove_hook);
let e1 = hooked.spawn_entity();
let data = Bytes::from_array(&env, &[1, 2, 3, 4]);
hooked.add_component(e1, symbol_short!("pos"), data);
assert!(hooked.remove_component(e1, &symbol_short!("pos")));
assert!(!hooked.has_component(e1, &symbol_short!("pos")));
}
#[test]
fn test_hooked_world_despawn() {
let env = Env::default();
let world = SimpleWorld::new(&env);
let mut hooked = HookedWorld::new(world);
hooked
.hooks_mut()
.on_remove(symbol_short!("a"), noop_remove_hook);
let e1 = hooked.spawn_entity();
let data = Bytes::from_array(&env, &[1]);
hooked.add_component(e1, symbol_short!("a"), data.clone());
hooked.add_component(e1, symbol_short!("b"), data);
hooked.despawn_entity(e1);
assert!(!hooked.has_component(e1, &symbol_short!("a")));
assert!(!hooked.has_component(e1, &symbol_short!("b")));
}
#[test]
fn test_hooked_world_into_inner() {
let env = Env::default();
let world = SimpleWorld::new(&env);
let mut hooked = HookedWorld::new(world);
let e1 = hooked.spawn_entity();
let data = Bytes::from_array(&env, &[1]);
hooked.add_component(e1, symbol_short!("test"), data);
let inner = hooked.into_inner();
assert!(inner.has_component(e1, &symbol_short!("test")));
}
#[test]
fn test_hooked_world_with_hooks() {
let env = Env::default();
let world = SimpleWorld::new(&env);
let mut hooks = HookRegistry::new();
hooks.on_add(symbol_short!("pos"), noop_add_hook);
let hooked = HookedWorld::with_hooks(world, hooks);
assert_eq!(hooked.hooks().add_hook_count(), 1);
}
}