///|
struct World {
mut entity_id_gen : UInt
entities : @set.Set[EntityId]
component_ids : Map[EntityId, Array[ComponentId]]
component_stores : Map[ComponentId, @hashmap.HashMap[EntityId, Component]]
resources : Map[ResourceId, Resource]
systems : Array[ScheduledSystem]
mut startup_complete : Bool
}
///|
pub fn World::World() -> Self {
{
entity_id_gen: 0,
entities: Set([]),
component_ids: {},
component_stores: {},
resources: {},
systems: [],
startup_complete: false,
}
}
///|
pub fn World::spawn(self : Self) -> EntityId {
let entity = self.reserve_entity_id()
self.spawn_reserved(entity)
entity
}
///|
fn World::reserve_entity_id(self : Self) -> EntityId {
let entity = EntityId::EntityId(self.entity_id_gen)
self.entity_id_gen += 1
entity
}
///|
fn World::spawn_reserved(self : Self, entity : EntityId) -> Unit {
if entity.value() >= self.entity_id_gen {
self.entity_id_gen = entity.value() + 1
}
guard !self.is_alive(entity) else { return }
self.entities.add(entity)
self.component_ids[entity] = []
}
///|
pub fn[B : Bundle] World::spawn_with(self : Self, bundle : B) -> EntityId {
let entity = self.spawn()
bundle.insert_into(self, entity)
entity
}
///|
pub fn World::is_alive(self : Self, entity : EntityId) -> Bool {
self.entities.contains(entity)
}
///|
pub fn World::despawn(self : Self, entity : EntityId) -> Bool {
guard self.is_alive(entity) else { return false }
match self.component_ids.get(entity) {
Some(ids) =>
ids.each(fn(id) { self.remove_component_from_store(entity, id) })
None => ()
}
self.entities.remove(entity)
self.component_ids.remove(entity)
true
}
///|
pub fn World::despawn_checked(
self : Self,
entity : EntityId,
) -> Unit raise EcsError {
guard self.despawn(entity) else { raise EntityMissing(entity) }
}
///|
fn World::entity_component_ids(
self : Self,
entity : EntityId,
) -> Array[ComponentId]? {
self.component_ids.get(entity)
}
///|
fn World::component_store(
self : Self,
id : ComponentId,
) -> @hashmap.HashMap[EntityId, Component]? {
self.component_stores.get(id)
}
///|
fn World::component_store_len(self : Self, id : ComponentId) -> Int {
match self.component_store(id) {
Some(store) => store.length()
None => 0
}
}
///|
fn World::insert_component_into_store(
self : Self,
entity : EntityId,
id : ComponentId,
component : Component,
) -> Unit {
let store = self.component_stores.get_or_init(id, fn() { HashMap([]) })
store[entity] = component
}
///|
fn World::remove_component_from_store(
self : Self,
entity : EntityId,
id : ComponentId,
) -> Unit {
match self.component_stores.get(id) {
Some(store) => {
store.remove(entity)
if store.is_empty() {
self.component_stores.remove(id)
}
}
None => ()
}
}
///|
pub fn[C : ComponentValue] World::insert_component(
self : Self,
entity : EntityId,
component : C,
) -> Unit raise NotSpawned {
self.insert_component_value(
entity,
C::component_id(),
C::to_component(component),
)
}
///|
fn World::insert_component_value(
self : Self,
entity : EntityId,
id : ComponentId,
component : Component,
) -> Unit raise NotSpawned {
guard self.is_alive(entity) else { raise NotSpawned(entity) }
let ids = self.component_ids.get_or_init(entity, fn() {
Array::new(capacity=4)
})
if !ids.contains(id) {
ids.push(id)
}
self.insert_component_into_store(entity, id, component)
}
///|
pub fn[C : ComponentValue] World::insert_component_checked(
self : Self,
entity : EntityId,
component : C,
) -> Unit raise EcsError {
guard self.is_alive(entity) else { raise EntityMissing(entity) }
let id = C::component_id()
let value = C::to_component(component)
let ids = self.component_ids.get_or_init(entity, fn() {
Array::new(capacity=4)
})
if !ids.contains(id) {
ids.push(id)
}
self.insert_component_into_store(entity, id, value)
}
///|
pub fn[C : ComponentValue] World::has_component(
self : Self,
entity : EntityId,
_hint? : C? = None,
) -> Bool {
let component : C? = self.get_component(entity)
component is Some(_)
}
///|
pub fn[C : ComponentValue] World::get_component(
self : Self,
entity : EntityId,
) -> C? {
match self.component_store(C::component_id()) {
Some(store) =>
match store.get(entity) {
Some(component) => C::from_component(component)
None => None
}
None => None
}
}
///|
pub fn[C : ComponentValue] World::get_component_checked(
self : Self,
entity : EntityId,
) -> C raise EcsError {
let id = C::component_id()
guard self.is_alive(entity) else { raise EntityMissing(entity) }
match self.component_store(id) {
Some(store) =>
match store.get(entity) {
Some(component) =>
match C::from_component(component) {
Some(value) => value
None => raise ComponentVariantMismatch(entity, id)
}
None => raise ComponentMissing(entity, id)
}
None => raise ComponentMissing(entity, id)
}
}
///|
pub fn[C : ComponentValue] World::require_component(
self : Self,
entity : EntityId,
) -> C {
match self.get_component(entity) {
Some(component) => component
None => abort("component is missing on entity \{entity.value()}")
}
}
///|
pub fn[C : ComponentValue] World::remove_component(
self : Self,
entity : EntityId,
_hint? : C? = None,
) -> Unit {
self.remove_component_value(entity, C::component_id())
}
///|
pub fn[C : ComponentValue] World::remove_component_checked(
self : Self,
entity : EntityId,
_hint? : C? = None,
) -> Unit raise EcsError {
let id = C::component_id()
guard self.is_alive(entity) else { raise EntityMissing(entity) }
match self.component_store(id) {
Some(store) =>
match store.get(entity) {
Some(component) =>
match C::from_component(component) {
Some(_) => self.remove_component_value(entity, id)
None => raise ComponentVariantMismatch(entity, id)
}
None => raise ComponentMissing(entity, id)
}
None => raise ComponentMissing(entity, id)
}
}
///|
fn World::remove_component_value(
self : Self,
entity : EntityId,
id : ComponentId,
) -> Unit {
match self.entity_component_ids(entity) {
Some(ids) => {
match ids.search(id) {
Some(index) => ignore(ids.remove(index))
None => ()
}
self.remove_component_from_store(entity, id)
}
None => ()
}
}
///|
pub fn[R : ResourceValue] World::set_resource(
self : Self,
resource : R,
) -> Unit {
self.resources[R::resource_id()] = R::to_resource(resource)
}
///|
pub fn[R : ResourceValue] World::has_resource(
self : Self,
_hint? : R? = None,
) -> Bool {
let resource : R? = self.try_get_resource()
resource is Some(_)
}
///|
pub fn[R : ResourceValue] World::get_resource(self : Self) -> R {
match self.try_get_resource() {
Some(resource) => resource
None => abort("resource is missing")
}
}
///|
pub fn[R : ResourceValue] World::get_resource_checked(
self : Self,
) -> R raise EcsError {
let id = R::resource_id()
match self.resources.get(id) {
Some(resource) =>
match R::from_resource(resource) {
Some(value) => value
None => raise ResourceVariantMismatch(id)
}
None => raise ResourceMissing(id)
}
}
///|
pub fn[R : ResourceValue] World::try_get_resource(self : Self) -> R? {
match self.resources.get(R::resource_id()) {
Some(resource) => R::from_resource(resource)
None => None
}
}
///|
pub fn[R : ResourceValue] World::remove_resource(
self : Self,
_hint? : R? = None,
) -> Unit {
self.resources.remove(R::resource_id())
}
///|
pub fn[R : ResourceValue] World::remove_resource_checked(
self : Self,
_hint? : R? = None,
) -> Unit raise EcsError {
let id = R::resource_id()
match self.resources.get(id) {
Some(resource) =>
match R::from_resource(resource) {
Some(_) => self.resources.remove(id)
None => raise ResourceVariantMismatch(id)
}
None => raise ResourceMissing(id)
}
}
///|
pub fn World::entity_ops(self : Self, entity : EntityId) -> EntityOps {
{ world: self, entity }
}
///|
pub fn World::add_system(self : Self, system : System) -> Unit {
self.add_system_to_stage(Update, system)
}
///|
pub fn World::add_system_fn(
self : Self,
func : (World) -> Unit,
name? : String = "system",
) -> Unit {
self.add_system(System(func, name~))
}
///|
pub fn World::add_command_system(self : Self, system : CommandSystem) -> Unit {
self.add_command_system_to_stage(Update, system)
}
///|
pub fn World::add_command_system_fn(
self : Self,
func : (World, Commands) -> Unit,
name? : String = "system",
) -> Unit {
self.add_command_system(CommandSystem(func, name~))
}
///|
pub fn World::step(self : Self) -> Unit raise NotSpawned {
if !self.startup_complete {
self.run_stage(Startup)
self.startup_complete = true
}
self.run_stage(Update)
self.run_stage(Cleanup)
}