From 90e5ff36e70f7c849493e092712f0b3048c9c4f4 Mon Sep 17 00:00:00 2001 From: Matt Low Date: Sat, 25 Jan 2020 13:54:55 +0400 Subject: [PATCH] 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 --- core/src/com/me/common/ecs/ComponentBag.java | 4 +- core/src/com/me/common/ecs/ComponentType.java | 2 +- core/src/com/me/common/ecs/Engine.java | 100 ++++++++++++++---- core/src/com/me/common/ecs/Entity.java | 14 ++- core/src/com/me/common/ecs/EntitySystem.java | 27 +++-- 5 files changed, 111 insertions(+), 36 deletions(-) diff --git a/core/src/com/me/common/ecs/ComponentBag.java b/core/src/com/me/common/ecs/ComponentBag.java index 88407e4..39b3c0f 100644 --- a/core/src/com/me/common/ecs/ComponentBag.java +++ b/core/src/com/me/common/ecs/ComponentBag.java @@ -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]; } diff --git a/core/src/com/me/common/ecs/ComponentType.java b/core/src/com/me/common/ecs/ComponentType.java index 6436fba..0fcabbd 100644 --- a/core/src/com/me/common/ecs/ComponentType.java +++ b/core/src/com/me/common/ecs/ComponentType.java @@ -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 component) { diff --git a/core/src/com/me/common/ecs/Engine.java b/core/src/com/me/common/ecs/Engine.java index cce1c9f..d24bc4f 100644 --- a/core/src/com/me/common/ecs/Engine.java +++ b/core/src/com/me/common/ecs/Engine.java @@ -7,6 +7,10 @@ import com.me.common.ecs.event.Listener; public class Engine { private Array entities; + private Array toActivate; + private Array toDeactivate; + private Array toRemove; + private ComponentBag[] components; private Array 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 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); - } - } - - } diff --git a/core/src/com/me/common/ecs/Entity.java b/core/src/com/me/common/ecs/Entity.java index 444014c..3645169 100644 --- a/core/src/com/me/common/ecs/Entity.java +++ b/core/src/com/me/common/ecs/Entity.java @@ -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 clazz) { + return ComponentType.getComponentType(clazz).isTypeInMask(componentBits); + } + public T getComponent(Class clazz) { return engine.getEntityComponent(this, clazz); } diff --git a/core/src/com/me/common/ecs/EntitySystem.java b/core/src/com/me/common/ecs/EntitySystem.java index 8a81d08..51f8ecc 100644 --- a/core/src/com/me/common/ecs/EntitySystem.java +++ b/core/src/com/me/common/ecs/EntitySystem.java @@ -1,25 +1,36 @@ package com.me.common.ecs; +import com.badlogic.gdx.utils.Array; + public abstract class EntitySystem { private long typeBits; + protected Array entities; + public EntitySystem(Class... 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 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; + } + }