| name | zay-es-debug |
| description | Debug Entity-Component-System issues including EntitySet problems, memory leaks, and component queries. Use when troubleshooting ECS bugs or entity processing issues. |
Debugging ECS Issues
Common Entity-Component-System problems and solutions from official Zay-ES patterns.
Official Rules of Thumb
From the Zay-ES wiki:
- Components are data only - no logic in components
- One canonical writer per component type. Multiple systems must not independently write the same component to the same entity. When multiple sources contribute deltas (e.g. damage from different weapons, prize-pickup cap bumps), use the ADR 0001 Change-entity shape: emitters create a short-lived entity carrying
ChangeTarget(target, source) + a typed *Change payload; a single canonical writer drains those entities and applies the fold. Concrete example: EnergyChange / EnergyStatsChange drained by EnergySystem / EnergyStatsSystem. See .claude/rules/replacement-as-mutation.md and docs/adr/0001-ecs-component-model.md.
- Immutable components help threading - allows systems to run without synchronization
EntitySet Not Updating
Symptom: Entities added but not showing in set
Checks:
- Is
applyChanges() called before iterating?
for (Entity e : entities) { }
entities.applyChanges();
for (Entity e : entities) { }
- Are you querying correct components?
entities = ed.getEntities(Component1.class, Component2.class);
- Is the component actually set?
MyComponent c = ed.getComponent(entityId, MyComponent.class);
System.out.println("Component: " + c);
The Visualization Pattern (from Wiki)
The standard pattern for syncing entities to visual spatials:
public class VisualAppState extends AbstractAppState {
private EntityData ed;
private EntitySet entities;
private final Map<EntityId, Spatial> models = new HashMap<>();
@Override
public void initialize(AppStateManager stateManager, Application app) {
ed = getEntityData();
entities = ed.getEntities(Position.class, Model.class);
}
@Override
public void cleanup() {
entities.release();
entities = null;
}
@Override
public void update(float tpf) {
if (entities.applyChanges()) {
removeModels(entities.getRemovedEntities());
addModels(entities.getAddedEntities());
updateModels(entities.getChangedEntities());
}
}
private void removeModels(Set<Entity> removed) {
for (Entity e : removed) {
Spatial s = models.remove(e.getId());
s.removeFromParent();
}
}
private void addModels(Set<Entity> added) {
for (Entity e : added) {
Spatial s = createVisual(e);
models.put(e.getId(), s);
updateModelSpatial(e, s);
rootNode.attachChild(s);
}
}
private void updateModels(Set<Entity> changed) {
for (Entity e : changed) {
Spatial s = models.get(e.getId());
updateModelSpatial(e, s);
}
}
}
Memory Leaks
Symptom: Growing memory, eventual OOM
Cause: EntitySets not released
@Override
protected void terminate() {
}
@Override
protected void terminate() {
entities.release();
entities = null;
}
Component Filter Issues
import com.simsilica.es.Filters;
EntitySet ships = ed.getEntities(
Filters.fieldEquals(Model.class, "name", Model.SHIP),
Model.class,
Position.class
);
ComponentFilter<ShapeInfo> filter =
FieldFilter.create(ShapeInfo.class, "shapeName", ShapeNames.SHIP);
EntitySet filtered = ed.getEntities(filter, ShapeInfo.class, Position.class);
Concurrent Modification
Symptom: ConcurrentModificationException or missed updates
Cause: Modifying entities during iteration
for (Entity e : entities) {
ed.removeEntity(e.getId());
}
List<EntityId> toRemove = new ArrayList<>();
for (Entity e : entities) {
toRemove.add(e.getId());
}
for (EntityId id : toRemove) {
ed.removeEntity(id);
}
entities.stream().forEach(e -> {
ed.removeEntity(e.getId());
});
The Decay Pattern (from Wiki)
Time-based entity removal:
public class DecaySystem extends AbstractGameSystem {
private EntitySet decays;
@Override
protected void initialize() {
decays = ed.getEntities(Decay.class);
}
@Override
protected void terminate() {
decays.release();
decays = null;
}
@Override
public void update(SimTime time) {
decays.applyChanges();
for (Entity e : decays) {
Decay decay = e.get(Decay.class);
if (decay.getPercent() >= 1.0) {
ed.removeEntity(e.getId());
}
}
}
}
Entity Not Found
Entity e = ed.getEntity(entityId, Component1.class, Component2.class);
if (e == null) {
}
if (ed.getComponent(id, SomeComponent.class) != null) {
}
Setting Multiple Components
EntityId entity = ed.createEntity();
ed.setComponents(entity,
new Position(new Vector3f(0, 0, 0)),
new Model(Model.SHIP),
new CollisionShape(1.0f),
new Attack(10)
);
Debugging Commands
public void debugEntities() {
EntitySet all = ed.getEntities(MyComponent.class);
all.applyChanges();
for (Entity e : all) {
System.out.println(e.getId() + ": " + e.get(MyComponent.class));
}
all.release();
}