Skip to content

Entity Component System

Package: com.hypixel.hytale.component

Hytale uses an archetype-based Entity Component System for game entity and chunk data management. The ECS is generic over an ECS_TYPE parameter — the two concrete store types are:

  • EntityStore — For game entities (players, NPCs, mobs, items, projectiles)
  • ChunkStore — For chunk-level data

Components are data classes that implement Component<ECS_TYPE>. They hold per-entity data and are stored in archetype chunks for cache-friendly iteration.

An Archetype is an immutable set of ComponentTypes that defines an entity’s component composition. Entities with the same archetype are stored together in the same ArchetypeChunk. Adding or removing a component changes the entity’s archetype and may move it to a different chunk.

A Store<ECS_TYPE> is the main entity container. It manages entity storage in archetype chunks, system registration, ticking, event dispatch, and command buffer pooling. Each World has its own EntityStore and ChunkStore.

A Ref<ECS_TYPE> is a typed pointer to an entity within a Store. Contains a volatile index that can be invalidated when the entity is removed. Always check isValid() before use.

A Holder<ECS_TYPE> is an entity data container — a blueprint holding an Archetype and array of Components. Used for entity transfer between stores, serialization, and spawning.

public class MyPlugin extends PluginBase {
private static ComponentType<EntityStore, HealthComponent> HEALTH_TYPE;
@Override
protected void setup() {
// Register a serializable component
HEALTH_TYPE = getEntityStoreRegistry().registerComponent(
HealthComponent.class, "myplugin:health", HealthComponent.CODEC);
// Register a ticking system
getEntityStoreRegistry().registerSystem(new HealthRegenSystem());
// Register an entity event handler
getEntityStoreRegistry().registerSystem(new OnDamageSystem());
}
}
// Ticking system example
public class HealthRegenSystem extends EntityTickingSystem<EntityStore> {
@Override
public Query<EntityStore> getQuery() {
return MyPlugin.HEALTH_TYPE;
}
@Override
public void tick(float dt, int index, ArchetypeChunk<EntityStore> chunk,
Store<EntityStore> store, CommandBuffer<EntityStore> commandBuffer) {
HealthComponent health = chunk.getComponent(index, MyPlugin.HEALTH_TYPE);
health.regenerate(dt);
}
}

PluginBase.getEntityStoreRegistry() returns a ComponentRegistryProxy<EntityStore> with these capabilities:

MethodDescription
registerComponent(Class, String id, BuilderCodec)Register serializable component
registerComponent(Class, Supplier)Register transient component
registerResource(Class, String id, BuilderCodec)Register serializable resource
registerResource(Class, Supplier)Register transient resource
registerSystem(ISystem)Register a system (ticking, event, lifecycle)
registerEntityEventType(Class)Register custom entity event type
registerWorldEventType(Class)Register custom world event type
registerSystemType(Class)Register system type
registerSystemGroup()Register system group

All registrations are automatically unregistered when the plugin shuts down.

Components are accessed through Store<ECS_TYPE> (which implements ComponentAccessor<ECS_TYPE>):

MethodDescription
getComponent(Ref, ComponentType) → TGet component data for an entity
addComponent(Ref, ComponentType, Component)Add component to entity
removeComponent(Ref, ComponentType)Remove component from entity
addEntity(Holder, AddReason) → RefAdd entity to store
removeEntity(Ref, Holder, RemoveReason) → HolderRemove entity
getResource(ResourceType) → ResourceGet store-level resource
invoke(Ref, EcsEvent)Dispatch entity-level ECS event
invoke(EcsEvent)Dispatch store-level ECS event

Resources implement Resource<ECS_TYPE> and are singleton-like per-store data (not per-entity). Used for shared state like spatial indices.

Systems cannot directly mutate the store during iteration. Instead, they enqueue mutations into a CommandBuffer which is applied after the system finishes:

// Inside a system's tick() or handle() method:
commandBuffer.addEntity(holder, AddReason.SPAWNED);
commandBuffer.removeEntity(ref, RemoveReason.DESTROYED);
commandBuffer.addComponent(ref, componentType, component);
commandBuffer.removeComponent(ref, componentType);

See Systems for the full system type hierarchy.