Move entity processing loop to systems

Store cache of interested entities in each system.

Perform entity activation, deactivation and removal outside of the update
loop.

Instead of throwing an exception when trying to get a Component from
ComponentBag that is out of range, just return null. This is to allow
systems to try getting a Component it isn't registered for.

Fixed ComponentType.isTypeInMask
This commit is contained in:
Matt Low 2020-01-25 13:54:55 +04:00
parent d8b82c4fee
commit 90e5ff36e7
5 changed files with 111 additions and 36 deletions

View File

@ -11,8 +11,8 @@ public class ComponentBag {
} }
public Component get(int index) { public Component get(int index) {
if (index > size) { if (index >= size) {
throw new IndexOutOfBoundsException("index > size"); return null;
} }
return items[index]; return items[index];
} }

View File

@ -50,7 +50,7 @@ public class ComponentType {
} }
protected boolean isTypeInMask(long mask) { protected boolean isTypeInMask(long mask) {
return (bits & mask) == mask; return (bits & mask) == bits;
} }
protected static void registerComponentType(Class<? extends Component> component) { protected static void registerComponentType(Class<? extends Component> component) {

View File

@ -7,6 +7,10 @@ import com.me.common.ecs.event.Listener;
public class Engine { public class Engine {
private Array<Entity> entities; private Array<Entity> entities;
private Array<Entity> toActivate;
private Array<Entity> toDeactivate;
private Array<Entity> toRemove;
private ComponentBag[] components; private ComponentBag[] components;
private Array<EntitySystem> systems; private Array<EntitySystem> systems;
@ -14,6 +18,10 @@ public class Engine {
public Engine() { public Engine() {
this.entities = new Array<>(); this.entities = new Array<>();
this.toActivate = new Array<>();
this.toDeactivate = new Array<>();
this.toRemove = new Array<>();
this.systems = new Array<>(); this.systems = new Array<>();
this.listenerRegistry = new ListenerRegistry(); this.listenerRegistry = new ListenerRegistry();
} }
@ -26,6 +34,10 @@ public class Engine {
this.systems.add(system); this.systems.add(system);
} }
public void registerListener(Listener listener) {
listenerRegistry.registerListener(listener);
}
public void ready() { public void ready() {
this.components = new ComponentBag[ComponentType.getRegisteredComponentTypeCount()]; this.components = new ComponentBag[ComponentType.getRegisteredComponentTypeCount()];
for (int i = 0; i < components.length; i++) { for (int i = 0; i < components.length; i++) {
@ -33,10 +45,22 @@ public class Engine {
} }
} }
public Array<Entity> getEntities() {
return entities;
}
public int getEntityCount() {
return entities.size;
}
public void update(float dt) { public void update(float dt) {
activatePending();
deactivatePending();
removePending();
for (EntitySystem system : systems) { for (EntitySystem system : systems) {
system.preProcessing(); system.preProcessing();
updateSystem(system, dt); system.processEntities(dt);
system.postProcessing(); system.postProcessing();
} }
} }
@ -47,13 +71,59 @@ public class Engine {
return entity; return entity;
} }
public void removeEntity(Entity entity) { protected void removeEntity(Entity entity) {
removeAllEntityComponents(entity.getId()); entity.deactivate();
entities.removeValue(entity, true); toRemove.add(entity);
} }
public void registerListener(Listener listener) { private void removePending() {
listenerRegistry.registerListener(listener); if (toRemove.isEmpty()) {
return;
}
for (Entity entity : toRemove) {
removeAllEntityComponents(entity.getId());
entities.removeValue(entity, true);
}
toRemove.clear();
}
protected void activateEntity(Entity entity) {
toActivate.add(entity);
}
private void activatePending() {
if (toActivate.isEmpty()) {
return;
}
for (Entity entity : toActivate) {
for (EntitySystem system : systems) {
if (system.interestedIn(entity)) {
system.entities.add(entity);
}
}
}
toActivate.clear();
}
protected void deactivateEntity(Entity entity) {
toDeactivate.add(entity);
}
private void deactivatePending() {
if (toDeactivate.isEmpty()) {
return;
}
for (Entity entity : toDeactivate) {
for (EntitySystem system : systems) {
system.entities.removeValue(entity, true);
}
}
toDeactivate.clear();
} }
public void callEvent(Event event) { public void callEvent(Event event) {
@ -72,6 +142,7 @@ public class Engine {
entity.addComponentType(type); entity.addComponentType(type);
} }
protected void removeEntityComponent(Entity entity, Component component) { protected void removeEntityComponent(Entity entity, Component component) {
ComponentType type = ComponentType.getComponentType(component.getClass()); ComponentType type = ComponentType.getComponentType(component.getClass());
components[type.getId()].remove(entity.getId()); components[type.getId()].remove(entity.getId());
@ -83,21 +154,4 @@ public class Engine {
return clazz.cast(components[type.getId()].get(entity.getId())); return clazz.cast(components[type.getId()].get(entity.getId()));
} }
protected void updateSystem(EntitySystem system, float dt) {
for (Entity entity : entities) {
if (!entity.isActive()) {
continue;
}
// Check if this system is interested in this entity
if ((entity.getComponentBits() & system.getTypeMask()) != system.getTypeMask()) {
continue;
}
// If so, process the entity
system.processEntity(entity, dt);
}
}
} }

View File

@ -26,17 +26,27 @@ public final class Entity {
} }
public void activate() { public void activate() {
this.active = true; active = true;
engine.activateEntity(this);
} }
public void deactivate() { public void deactivate() {
this.active = false; active = false;
engine.deactivateEntity(this);
}
public void remove() {
engine.removeEntity(this);
} }
public Engine getEngine() { public Engine getEngine() {
return this.engine; return this.engine;
} }
public boolean hasComponent(Class<? extends Component> clazz) {
return ComponentType.getComponentType(clazz).isTypeInMask(componentBits);
}
public <T extends Component> T getComponent(Class<T> clazz) { public <T extends Component> T getComponent(Class<T> clazz) {
return engine.getEntityComponent(this, clazz); return engine.getEntityComponent(this, clazz);
} }

View File

@ -1,25 +1,36 @@
package com.me.common.ecs; package com.me.common.ecs;
import com.badlogic.gdx.utils.Array;
public abstract class EntitySystem { public abstract class EntitySystem {
private long typeBits; private long typeBits;
protected Array<Entity> entities;
public EntitySystem(Class<? extends Component>... components) { public EntitySystem(Class<? extends Component>... components) {
typeBits = ComponentType.getMaskBits(components); typeBits = ComponentType.getMaskBits(components);
} entities = new Array<>();
/**
* @return the type mask for this system. Only entities containing all component types specified
* by the mask will be processed by it.
*/
public long getTypeMask() {
return typeBits;
} }
public void preProcessing() {} public void preProcessing() {}
public Array<Entity> getEntities() {
return entities;
}
public void processEntities(float dt) {
for (int i = 0, n = getEntities().size; i < n; i++) {
processEntity(entities.get(i), dt);
}
}
public abstract void processEntity(Entity entity, float dt); public abstract void processEntity(Entity entity, float dt);
public void postProcessing() {} public void postProcessing() {}
public boolean interestedIn(Entity entity) {
return (entity.getComponentBits() & typeBits) == typeBits;
}
} }