ECS
February 7, 2026 ยท View on GitHub
A very simple and friendly entity-component system, loosely based on Unity3D's model of GameObjects and MonoBehaviours.
If you prefer high-performance over simplicity, use an external Julia ECS package such as Overseer.jl.
Overview
- A
Worldis a list ofEntity(plus accelerated lookup structures). - An
Entityis a list ofAbstractComponent. - A component has its own data and behavior, mostly in the form of a "TICK()" event that is invoked by the world.
You can update an entire world with tick_world(world, delta_seconds::Float32). You can destroy all entities (and reset other fields) with reset_world(world), which is useful if your components are managing resources and you want to make sure all of them are destructed properly.
A world stores timing data for easy access: delta_seconds, elapsed_seconds, and time_scale. You may set time_scale to change the speed of your world.
To enable extra error-checking around your use of the ECS, enable the module's debug asserts flag by executing BplusTools.ECS.bp_ecs_asserts_enabled() = true while loading your game's code. Just don't do that in release!
Entity/component management
You can add and remove both entities and components:
add_entity(world)::Entityandremove_entity(world, entity)add_component(entity, T, constructor_args...; kw_constructor_args...)::T- All the extra arguments get passed into the component's constructor (refer to
CONSTRUCT()in the@componentmacro)
- All the extra arguments get passed into the component's constructor (refer to
remove_component(entity, c::AbstractComponent)
You can query the components on an Entity or entire World:
has_component(entity_or_world, T)::Boolget_component(entity, T)::Optional{T}- Throws an error if there is more than one on the entity.
get_component(world, T)::Optional{Tuple{T, Entity}}- Throws an error if there is more than one in the world.
- Also returns the entity owning the component.
get_components(entity, T)::[iterator over T]- Returns all components of the given type within the given entity.
get_components(world, T)::[iterator over Tuple{T, Entity}]- Returns all components of the given type within the entire world, paired with their owning entities.
count_components(entity_or_world, T)::Intget_component_types(T)::Tuple{Vararg{Type}}- Returns a list of the type T, its parent type, grandparent type, etc. up to but not including the root type
AbstractComponent.
- Returns a list of the type T, its parent type, grandparent type, etc. up to but not including the root type
@component
You can define component types with the @component macro. Components have a OOP structure to them; abstract parent components can add fields, "promises" (a.k.a. abstract functions) and "configurables" (a.k.a. virtual functions) to their children. Components have a default constructor where each field is provided in order (parent fields before child ones), but you can override this behavior.
Refer to the @component doc-string for a very detailed explanation, with code samples, of the full features and syntax.
Internal interface
If you wish, you can entirely ignore @component and manually implement your own component type through standard Julia syntax.
Simply define a mutable struct inheriting fromAbstractComponent (or a child of it), and implement the interface described in ECS/interface.jl.
Your custom type cannot be a parent of another @component, unless you implement all the interface functions at the top of ECS/macros.jl -- and at that point just use the macro.
Component printing
By default, when printing a component defined with @component, fields that reference an Entity World or AbstractComponent are shortened for brevity.
You can customize how a specific field of your @component displays itself, in print(), by overloading component_field_print(component, Val{:my_field}, field_value, io::IO).