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) {
if (index > size) {
throw new IndexOutOfBoundsException("index > size");
if (index >= size) {
return null;
}
return items[index];
}

View File

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

View File

@ -7,6 +7,10 @@ import com.me.common.ecs.event.Listener;
public class Engine {
private Array<Entity> entities;
private Array<Entity> toActivate;
private Array<Entity> toDeactivate;
private Array<Entity> toRemove;
private ComponentBag[] components;
private Array<EntitySystem> systems;
@ -14,6 +18,10 @@ public class Engine {
public Engine() {
this.entities = new Array<>();
this.toActivate = new Array<>();
this.toDeactivate = new Array<>();
this.toRemove = new Array<>();
this.systems = new Array<>();
this.listenerRegistry = new ListenerRegistry();
}
@ -26,6 +34,10 @@ public class Engine {
this.systems.add(system);
}
public void registerListener(Listener listener) {
listenerRegistry.registerListener(listener);
}
public void ready() {
this.components = new ComponentBag[ComponentType.getRegisteredComponentTypeCount()];
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) {
activatePending();
deactivatePending();
removePending();
for (EntitySystem system : systems) {
system.preProcessing();
updateSystem(system, dt);
system.processEntities(dt);
system.postProcessing();
}
}
@ -47,13 +71,59 @@ public class Engine {
return entity;
}
public void removeEntity(Entity entity) {
removeAllEntityComponents(entity.getId());
entities.removeValue(entity, true);
protected void removeEntity(Entity entity) {
entity.deactivate();
toRemove.add(entity);
}
public void registerListener(Listener listener) {
listenerRegistry.registerListener(listener);
private void removePending() {
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) {
@ -72,6 +142,7 @@ public class Engine {
entity.addComponentType(type);
}
protected void removeEntityComponent(Entity entity, Component component) {
ComponentType type = ComponentType.getComponentType(component.getClass());
components[type.getId()].remove(entity.getId());
@ -83,21 +154,4 @@ public class Engine {
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() {
this.active = true;
active = true;
engine.activateEntity(this);
}
public void deactivate() {
this.active = false;
active = false;
engine.deactivateEntity(this);
}
public void remove() {
engine.removeEntity(this);
}
public Engine getEngine() {
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) {
return engine.getEntityComponent(this, clazz);
}

View File

@ -1,25 +1,36 @@
package com.me.common.ecs;
import com.badlogic.gdx.utils.Array;
public abstract class EntitySystem {
private long typeBits;
protected Array<Entity> entities;
public EntitySystem(Class<? extends Component>... components) {
typeBits = ComponentType.getMaskBits(components);
}
/**
* @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;
entities = new Array<>();
}
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 void postProcessing() {}
public boolean interestedIn(Entity entity) {
return (entity.getComponentBits() & typeBits) == typeBits;
}
}