use crate::component::ComponentStorage;
use crate::simple_world::{EntityId, SimpleWorld};
use alloc::vec::Vec;
use soroban_sdk::{Bytes, Symbol};
enum CommandKind {
Spawn,
Despawn,
AddComponent,
RemoveComponent,
}
struct CommandEntry {
kind: CommandKind,
entity_id: Option<EntityId>,
component_type: Option<Symbol>,
data: Option<Bytes>,
storage: ComponentStorage,
}
pub struct CommandQueue {
commands: Vec<CommandEntry>,
}
impl CommandQueue {
pub fn new() -> Self {
Self {
commands: Vec::new(),
}
}
pub fn spawn(&mut self) {
self.commands.push(CommandEntry {
kind: CommandKind::Spawn,
entity_id: None,
component_type: None,
data: None,
storage: ComponentStorage::Table,
});
}
pub fn despawn(&mut self, entity_id: EntityId) {
self.commands.push(CommandEntry {
kind: CommandKind::Despawn,
entity_id: Some(entity_id),
component_type: None,
data: None,
storage: ComponentStorage::Table,
});
}
pub fn add_component(&mut self, entity_id: EntityId, component_type: Symbol, data: Bytes) {
self.commands.push(CommandEntry {
kind: CommandKind::AddComponent,
entity_id: Some(entity_id),
component_type: Some(component_type),
data: Some(data),
storage: ComponentStorage::Table,
});
}
pub fn add_sparse_component(
&mut self,
entity_id: EntityId,
component_type: Symbol,
data: Bytes,
) {
self.commands.push(CommandEntry {
kind: CommandKind::AddComponent,
entity_id: Some(entity_id),
component_type: Some(component_type),
data: Some(data),
storage: ComponentStorage::Sparse,
});
}
pub fn remove_component(&mut self, entity_id: EntityId, component_type: Symbol) {
self.commands.push(CommandEntry {
kind: CommandKind::RemoveComponent,
entity_id: Some(entity_id),
component_type: Some(component_type),
data: None,
storage: ComponentStorage::Table,
});
}
pub fn apply(self, world: &mut SimpleWorld) -> Vec<EntityId> {
let mut spawned_ids = Vec::new();
for entry in self.commands {
match entry.kind {
CommandKind::Spawn => {
let id = world.spawn_entity();
spawned_ids.push(id);
}
CommandKind::Despawn => {
if let Some(entity_id) = entry.entity_id {
world.despawn_entity(entity_id);
}
}
CommandKind::AddComponent => {
if let (Some(entity_id), Some(component_type), Some(data)) =
(entry.entity_id, entry.component_type, entry.data)
{
world.add_component_with_storage(
entity_id,
component_type,
data,
entry.storage,
);
}
}
CommandKind::RemoveComponent => {
if let (Some(entity_id), Some(component_type)) =
(entry.entity_id, entry.component_type)
{
world.remove_component(entity_id, &component_type);
}
}
}
}
spawned_ids
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
pub fn len(&self) -> usize {
self.commands.len()
}
}
impl Default for CommandQueue {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::{symbol_short, Env};
#[test]
fn test_empty_queue() {
let queue = CommandQueue::new();
assert!(queue.is_empty());
assert_eq!(queue.len(), 0);
let env = Env::default();
let mut world = SimpleWorld::new(&env);
let spawned = queue.apply(&mut world);
assert!(spawned.is_empty());
}
#[test]
fn test_spawn_via_queue() {
let env = Env::default();
let mut world = SimpleWorld::new(&env);
let mut queue = CommandQueue::new();
queue.spawn();
queue.spawn();
assert_eq!(queue.len(), 2);
let spawned = queue.apply(&mut world);
assert_eq!(spawned.len(), 2);
assert_eq!(spawned[0], 1);
assert_eq!(spawned[1], 2);
}
#[test]
fn test_despawn_via_queue() {
let env = Env::default();
let mut world = SimpleWorld::new(&env);
let e1 = world.spawn_entity();
let data = Bytes::from_array(&env, &[1]);
world.add_component(e1, symbol_short!("pos"), data);
let mut queue = CommandQueue::new();
queue.despawn(e1);
queue.apply(&mut world);
assert!(!world.has_component(e1, &symbol_short!("pos")));
}
#[test]
fn test_add_component_via_queue() {
let env = Env::default();
let mut world = SimpleWorld::new(&env);
let e1 = world.spawn_entity();
let mut queue = CommandQueue::new();
let data = Bytes::from_array(&env, &[1, 2, 3]);
queue.add_component(e1, symbol_short!("pos"), data.clone());
queue.apply(&mut world);
assert!(world.has_component(e1, &symbol_short!("pos")));
assert_eq!(world.get_component(e1, &symbol_short!("pos")), Some(data));
}
#[test]
fn test_add_sparse_component_via_queue() {
let env = Env::default();
let mut world = SimpleWorld::new(&env);
let e1 = world.spawn_entity();
let mut queue = CommandQueue::new();
let data = Bytes::from_array(&env, &[0xAA]);
queue.add_sparse_component(e1, symbol_short!("tag"), data.clone());
queue.apply(&mut world);
assert!(world.has_component(e1, &symbol_short!("tag")));
assert_eq!(world.table_component_count(&symbol_short!("tag")), 0);
assert_eq!(world.component_count(&symbol_short!("tag")), 1);
}
#[test]
fn test_remove_component_via_queue() {
let env = Env::default();
let mut world = SimpleWorld::new(&env);
let e1 = world.spawn_entity();
let data = Bytes::from_array(&env, &[1]);
world.add_component(e1, symbol_short!("pos"), data);
let mut queue = CommandQueue::new();
queue.remove_component(e1, symbol_short!("pos"));
queue.apply(&mut world);
assert!(!world.has_component(e1, &symbol_short!("pos")));
}
#[test]
fn test_mixed_operations() {
let env = Env::default();
let mut world = SimpleWorld::new(&env);
let e1 = world.spawn_entity();
let data = Bytes::from_array(&env, &[1]);
world.add_component(e1, symbol_short!("old"), data);
let mut queue = CommandQueue::new();
queue.spawn();
let new_data = Bytes::from_array(&env, &[2, 3]);
queue.add_component(e1, symbol_short!("new"), new_data.clone());
queue.remove_component(e1, symbol_short!("old"));
let spawned = queue.apply(&mut world);
assert_eq!(spawned.len(), 1);
assert_eq!(spawned[0], 2); assert!(world.has_component(e1, &symbol_short!("new")));
assert!(!world.has_component(e1, &symbol_short!("old")));
}
#[test]
fn test_queue_len_tracking() {
let env = Env::default();
let mut queue = CommandQueue::new();
assert_eq!(queue.len(), 0);
assert!(queue.is_empty());
queue.spawn();
assert_eq!(queue.len(), 1);
assert!(!queue.is_empty());
let data = Bytes::from_array(&env, &[1]);
queue.add_component(1, symbol_short!("test"), data);
assert_eq!(queue.len(), 2);
queue.remove_component(1, symbol_short!("test"));
assert_eq!(queue.len(), 3);
queue.despawn(1);
assert_eq!(queue.len(), 4);
}
}