From 6ae11ef2d372e13300fc2a053b6c0f511bb1322e Mon Sep 17 00:00:00 2001 From: Matt Low Date: Sat, 1 Feb 2020 15:55:54 +0400 Subject: [PATCH] ECS refactoring (breaks game code) - Remove requirement of an Engine instance in ComponentMapper instances, as the Engine from the entity passed to get() can be used. Allows for static initialization of ComponentMappers. - Store instances of ComponentMapper in a static Map within the ComponentMapper class itself, rather that inside of Engine. - Name bit/bits consistently. Some cases of bits were renamed to bit, such as ComponentType getBits() is now getBit(). Change usage of Mask to just Bits for consistency. - Modify access modifiers and field finality where appropriate - Add comments to public methods in Engine --- core/src/com/me/common/ecs/BaseSystem.java | 2 +- core/src/com/me/common/ecs/ComponentBag.java | 19 ++-- .../com/me/common/ecs/ComponentMapper.java | 29 ++++-- core/src/com/me/common/ecs/ComponentType.java | 58 ++++------- core/src/com/me/common/ecs/Engine.java | 98 +++++++++++-------- core/src/com/me/common/ecs/Entity.java | 84 ++++++++-------- core/src/com/me/common/ecs/EntityManager.java | 23 +++-- core/src/com/me/common/ecs/EntitySystem.java | 17 ++-- .../com/me/common/ecs/ListenerRegistry.java | 9 +- .../common/ecs/event/RegisteredListener.java | 9 +- 10 files changed, 175 insertions(+), 173 deletions(-) diff --git a/core/src/com/me/common/ecs/BaseSystem.java b/core/src/com/me/common/ecs/BaseSystem.java index d9589a3..21edcbe 100644 --- a/core/src/com/me/common/ecs/BaseSystem.java +++ b/core/src/com/me/common/ecs/BaseSystem.java @@ -2,7 +2,7 @@ package com.me.common.ecs; public abstract class BaseSystem { - protected Engine engine; + protected final Engine engine; public BaseSystem(Engine engine) { this.engine = engine; diff --git a/core/src/com/me/common/ecs/ComponentBag.java b/core/src/com/me/common/ecs/ComponentBag.java index 4e635b9..7f0bf01 100644 --- a/core/src/com/me/common/ecs/ComponentBag.java +++ b/core/src/com/me/common/ecs/ComponentBag.java @@ -1,38 +1,33 @@ package com.me.common.ecs; -public class ComponentBag { +class ComponentBag { private Component[] items; private int size; - public ComponentBag() { + ComponentBag() { this.items = new Component[16]; this.size = items.length; } - public Component get(int index) { + Component get(int index) { if (index >= size) { return null; } return items[index]; } - public boolean contains(int index) { - return index < size && items[index] != null; - } - - public void insert(int index, Component item) { + void insert(int index, Component item) { if (index >= size) { grow((int) (index * 1.5f)); } items[index] = item; } - public void remove(int index) { - if (index >= size) { - throw new IndexOutOfBoundsException("index must be < size"); + void remove(int index) { + if (index < size) { + items[index] = null; } - items[index] = null; } private void grow(int newSize) { diff --git a/core/src/com/me/common/ecs/ComponentMapper.java b/core/src/com/me/common/ecs/ComponentMapper.java index 09efc6f..e8fe76c 100644 --- a/core/src/com/me/common/ecs/ComponentMapper.java +++ b/core/src/com/me/common/ecs/ComponentMapper.java @@ -1,23 +1,36 @@ package com.me.common.ecs; -public class ComponentMapper { +import java.util.HashMap; +import java.util.Map; - private Engine engine; - private ComponentType type; - private Class typeClass; +public final class ComponentMapper { - public ComponentMapper(Engine engine, Class typeClass) { - this.engine = engine; + private static final Map, ComponentMapper> mappers = new HashMap<>(); + + private final ComponentType type; + private final Class typeClass; + + private ComponentMapper(Class typeClass) { this.type = ComponentType.getComponentType(typeClass); this.typeClass = typeClass; } public T get(Entity entity) { - return typeClass.cast(engine.getEntityComponent(entity, type)); + return typeClass.cast(entity.getComponent(type)); } public boolean has(Entity entity) { - return type.isTypeInMask(entity.componentBits); + return type.isInBits(entity.componentBits); + } + + @SuppressWarnings("unchecked") + public static ComponentMapper getFor(Class typeClass) { + ComponentMapper mapper = mappers.get(typeClass); + if (mapper == null) { + mapper = new ComponentMapper<>(typeClass); + mappers.put(typeClass, mapper); + } + return mapper; } } diff --git a/core/src/com/me/common/ecs/ComponentType.java b/core/src/com/me/common/ecs/ComponentType.java index c14c8c2..34775d7 100644 --- a/core/src/com/me/common/ecs/ComponentType.java +++ b/core/src/com/me/common/ecs/ComponentType.java @@ -6,64 +6,44 @@ import java.util.Map; final class ComponentType { private static final Map, ComponentType> types = new HashMap<>(); - private static final ComponentType[] typeById = new ComponentType[Long.SIZE]; private static long nextBit = 1l; private static int nextId = 0; - protected long bits; - protected int id; + final long bit; + final int id; private ComponentType() { - this.bits = nextBit; + this.bit = nextBit; this.id = nextId++; - this.nextBit <<= 1; + nextBit <<= 1; } - protected long getBits() { + boolean isInBits(long bits) { + return (bits & bit) == bit; + } + + @SafeVarargs + static long getBitsFor(Class... components) { + long bits = 0l; + for (Class clazz : components) { + bits |= getComponentType(clazz).bit; + } return bits; } - protected int getId() { - return id; - } - - protected static long getMaskBits(Class... components) { - long mask = 0l; - for (Class clazz : components) { - mask |= getTypeBits(clazz); - } - return mask; - } - - protected static long getTypeBits(Class component) { - return getComponentType(component).bits; - } - - protected static int getTypeId(Class component) { - return getComponentType(component).id; - } - - protected static ComponentType getById(int id) { - return typeById[id]; - } - - protected boolean isTypeInMask(long mask) { - return (bits & mask) == bits; - } - - protected static void registerComponentType(Class component) { + static ComponentType registerComponentType(Class component) { ComponentType type = types.get(component); if (type != null) { throw new IllegalArgumentException(component.getName() + " has already been registered."); } type = new ComponentType(); types.put(component, type); - typeById[type.id] = type; + return type; } - protected static ComponentType getComponentType(Class component) { + static ComponentType getComponentType(Class component) { ComponentType type = types.get(component); if (type == null) { throw new IllegalArgumentException(component.getName() + " has not been registered."); @@ -71,8 +51,4 @@ final class ComponentType { return type; } - protected static int getRegisteredComponentTypeCount() { - return types.size(); - } - } diff --git a/core/src/com/me/common/ecs/Engine.java b/core/src/com/me/common/ecs/Engine.java index feb082a..6f3f5e8 100644 --- a/core/src/com/me/common/ecs/Engine.java +++ b/core/src/com/me/common/ecs/Engine.java @@ -4,46 +4,51 @@ import com.badlogic.gdx.utils.Array; import com.me.common.ecs.event.Event; import com.me.common.ecs.event.Listener; -import java.util.HashMap; -import java.util.Map; - public class Engine { - private ComponentBag[] components; - protected Array systems; + private final EntityManager entityManager; + private final ComponentBag[] components; + private final Array systems; - private EntityManager entityManager; - private ListenerRegistry listenerRegistry; - - private Map, ComponentMapper> componentMappers; + private final ListenerRegistry listenerRegistry; public Engine() { this.entityManager = new EntityManager(this); - + this.components = new ComponentBag[Long.SIZE]; this.systems = new Array<>(); + this.listenerRegistry = new ListenerRegistry(); - this.componentMappers = new HashMap<>(); } + /** + * Register a Component class. + * @param clazz + */ public void registerComponentClass(Class clazz) { - ComponentType.registerComponentType(clazz); + ComponentType type = ComponentType.registerComponentType(clazz); + components[type.id] = new ComponentBag(); } + /** + * Register a BaseSystem. + * @param system + */ public void registerSystem(BaseSystem system) { - this.systems.add(system); + systems.add(system); } + /** + * Register an event Listener. + * @param listener + */ 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++) { - components[i] = new ComponentBag(); - } - } - + /** + * Process all systems + * @param dt the time (in seconds) passed since the last update + */ public void update(float dt) { entityManager.update(); @@ -54,61 +59,70 @@ public class Engine { } } + /** + * @return all entities known by the engine - both those active and inactive. + */ public Array getEntities() { - return entityManager.entities; + return entityManager.getEntities(); } - public int getEntityCount() { - return entityManager.entities.size; + /** + * @return all currently registered systems. + */ + public Array getSystems() { + return systems; } + /** + * @return a new Entity object which can have fresh components added to it + */ public Entity createEntity() { return entityManager.create(); } + /** + * Calls the given event, dispatching it to all registered Listeners that have an EventHandler + * for this particular event. + * @param event the event to call + */ public void callEvent(Event event) { listenerRegistry.callEvent(event); } - protected void refreshEntity(Entity entity) { + void refreshEntity(Entity entity) { entityManager.queueRefresh(entity); } - protected void removeAllEntityComponents(int entityId) { - for (int i = 0; i < components.length; i++) { - components[i].insert(entityId, null); + void removeAllEntityComponents(int entityId) { + for (ComponentBag bag : components) { + if (bag == null) { + // The rest of the array should also be null given that we never nullify + // an index after it has been set, and they are set sequentially. So, break. + break; + } + bag.remove(entityId); } } - protected void addEntityComponent(Entity entity, Component component) { + void addEntityComponent(Entity entity, Component component) { ComponentType type = ComponentType.getComponentType(component.getClass()); components[type.id].insert(entity.id, component); entity.addComponentType(type); } - protected void removeEntityComponent(Entity entity, Component component) { + void removeEntityComponent(Entity entity, Component component) { ComponentType type = ComponentType.getComponentType(component.getClass()); components[type.id].remove(entity.id); entity.removeComponentType(type); } - protected T getEntityComponent(Entity entity, Class clazz) { - return clazz.cast(components[ComponentType.getTypeId(clazz)].get(entity.id)); + T getEntityComponent(Entity entity, Class clazz) { + return clazz.cast(getEntityComponent(entity, ComponentType.getComponentType(clazz))); } - protected Component getEntityComponent(Entity entity, ComponentType type) { + Component getEntityComponent(Entity entity, ComponentType type) { return components[type.id].get(entity.id); } - @SuppressWarnings("unchecked") - public ComponentMapper getComponentMapper(Class typeClass) { - ComponentMapper mapper = componentMappers.get(typeClass); - if (mapper == null) { - mapper = new ComponentMapper<>(this, typeClass); - componentMappers.put(typeClass, mapper); - } - return mapper; - } - } diff --git a/core/src/com/me/common/ecs/Entity.java b/core/src/com/me/common/ecs/Entity.java index 5345ffd..316c00b 100644 --- a/core/src/com/me/common/ecs/Entity.java +++ b/core/src/com/me/common/ecs/Entity.java @@ -5,15 +5,15 @@ public final class Entity { private static int nextId = 0; private static long nextUniqueId = 0; - private Engine engine; - protected final int id; + private final Engine engine; + final int id; - protected long componentBits; - protected long systemEnabledBits; + long componentBits; + long systemEnabledBits; + boolean pendingRefresh; - protected boolean active; - protected boolean removed; - protected boolean pendingRefresh; + boolean active; + boolean removed; private long uniqueId; @@ -30,6 +30,10 @@ public final class Entity { return uniqueId; } + public Engine getEngine() { + return engine; + } + public boolean isActive() { return active; } @@ -60,18 +64,6 @@ public final class Entity { } } - 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); - } - public void addComponent(Component component) { engine.addEntityComponent(this, component); refresh(); @@ -82,39 +74,49 @@ public final class Entity { refresh(); } - protected void addComponentType(ComponentType type) { - componentBits |= type.bits; - } - - protected void removeComponentType(ComponentType type) { - componentBits &= ~type.bits; - } - - protected void addSystemEnabledBit(long bit) { - systemEnabledBits |= bit; - } - - protected void removeSystemEnabledBit(long bit) { - systemEnabledBits &= ~bit; - } - - public void refresh() { + private void refresh() { if (!pendingRefresh) { engine.refreshEntity(this); pendingRefresh = true; } } - protected void reset() { + public boolean hasComponent(Class clazz) { + return ComponentType.getComponentType(clazz).isInBits(componentBits); + } + + public T getComponent(Class clazz) { + return engine.getEntityComponent(this, clazz); + } + + // Internal getComponent method accessed only by ComponentMapper + Component getComponent(ComponentType type) { + return engine.getEntityComponent(this, type); + } + + void addComponentType(ComponentType type) { + componentBits |= type.bit; + } + + void removeComponentType(ComponentType type) { + componentBits &= ~type.bit; + } + + void addSystemEnabledBit(long systemBit) { + systemEnabledBits |= systemBit; + } + + void removeSystemEnabledBit(long systemBit) { + systemEnabledBits &= ~systemBit; + } + + void reset() { componentBits = 0; systemEnabledBits = 0; active = false; removed = false; pendingRefresh = false; - } - - protected void updateUniqueId() { - this.uniqueId = nextUniqueId++; + uniqueId = nextUniqueId++; } public String toString() { diff --git a/core/src/com/me/common/ecs/EntityManager.java b/core/src/com/me/common/ecs/EntityManager.java index bbf15d0..bf45d3a 100644 --- a/core/src/com/me/common/ecs/EntityManager.java +++ b/core/src/com/me/common/ecs/EntityManager.java @@ -4,11 +4,11 @@ import com.badlogic.gdx.utils.Array; final class EntityManager { - private Engine engine; + private final Engine engine; - protected Array entities; - protected Array removedEntities; - protected Array toRefresh; + private final Array entities; + private final Array removedEntities; + private final Array toRefresh; EntityManager(Engine engine) { this.engine = engine; @@ -18,7 +18,11 @@ final class EntityManager { this.toRefresh = new Array<>(false, 16); } - public Entity create() { + Array getEntities() { + return entities; + } + + Entity create() { Entity entity; if (!removedEntities.isEmpty()) { entity = removedEntities.removeIndex(0); @@ -26,12 +30,11 @@ final class EntityManager { entity = new Entity(engine); } entity.reset(); - entity.updateUniqueId(); entities.add(entity); return entity; } - public void update() { + void update() { if (toRefresh.isEmpty()) { return; } @@ -44,12 +47,12 @@ final class EntityManager { toRefresh.clear(); } - public void queueRefresh(Entity entity) { + void queueRefresh(Entity entity) { toRefresh.add(entity); } private void refreshEntity(Entity entity) { - for (BaseSystem system : engine.systems) { + for (BaseSystem system : engine.getSystems()) { if (!(system instanceof EntitySystem)) { continue; } @@ -57,7 +60,7 @@ final class EntityManager { ((EntitySystem) system).refresh(entity); } - if (entity.removed) { + if (entity.isRemoved()) { engine.removeAllEntityComponents(entity.id); entities.removeValue(entity, true); removedEntities.add(entity); diff --git a/core/src/com/me/common/ecs/EntitySystem.java b/core/src/com/me/common/ecs/EntitySystem.java index 5c71ccf..83facee 100644 --- a/core/src/com/me/common/ecs/EntitySystem.java +++ b/core/src/com/me/common/ecs/EntitySystem.java @@ -10,15 +10,16 @@ public abstract class EntitySystem extends BaseSystem { private static final Map, Long> systemBits = new HashMap<>(); private static long nextBit = 1l; - private long systemBit; - private long typeBits; + private final long systemBit; + private final long componentBits; - protected Array entities; + private final Array entities; + @SafeVarargs public EntitySystem(Engine engine, Class... components) { super(engine); this.systemBit = getBitFor(getClass()); - this.typeBits = ComponentType.getMaskBits(components); + this.componentBits = ComponentType.getBitsFor(components); this.entities = new Array<>(true, 16, Entity.class); } @@ -37,13 +38,13 @@ public abstract class EntitySystem extends BaseSystem { public abstract void processEntity(Entity entity, float dt); - protected void refresh(Entity entity) { + void refresh(Entity entity) { boolean enabled = (entity.systemEnabledBits & systemBit) == systemBit; - boolean interested = (entity.componentBits & typeBits) == typeBits; + boolean interested = (entity.componentBits & componentBits) == componentBits; if (interested && !enabled && entity.active && !entity.removed) { add(entity); - } else if (enabled && (!interested || !entity.active || entity.removed)) { + } else if (enabled && (!interested || !entity.active) || entity.removed) { remove(entity); } } @@ -59,7 +60,7 @@ public abstract class EntitySystem extends BaseSystem { } - static long getBitFor(Class es) { + private static long getBitFor(Class es) { Long bits = systemBits.get(es); if (bits == null) { bits = nextBit; diff --git a/core/src/com/me/common/ecs/ListenerRegistry.java b/core/src/com/me/common/ecs/ListenerRegistry.java index 9b42340..981eab5 100644 --- a/core/src/com/me/common/ecs/ListenerRegistry.java +++ b/core/src/com/me/common/ecs/ListenerRegistry.java @@ -11,15 +11,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class ListenerRegistry { +class ListenerRegistry { private final Map, List> registeredListeners; - protected ListenerRegistry() { + ListenerRegistry() { this.registeredListeners = new HashMap<>(); } - protected void registerListener(Listener listener) { + void registerListener(Listener listener) { for (Method method : listener.getClass().getMethods()) { EventHandler eh = method.getAnnotation(EventHandler.class); if (eh == null) { @@ -37,10 +37,9 @@ public class ListenerRegistry { List executors = getRegisteredListeners(eventClass); executors.add(new RegisteredListener(listener, method, eventClass, eh)); } - } - protected void callEvent(Event event) { + void callEvent(Event event) { List listeners = getRegisteredListeners(event.getClass()); if (listeners.isEmpty()) { return; diff --git a/core/src/com/me/common/ecs/event/RegisteredListener.java b/core/src/com/me/common/ecs/event/RegisteredListener.java index 73ca218..cf79d4a 100644 --- a/core/src/com/me/common/ecs/event/RegisteredListener.java +++ b/core/src/com/me/common/ecs/event/RegisteredListener.java @@ -4,11 +4,10 @@ import java.lang.reflect.Method; public class RegisteredListener { - private Listener listener; - private Method method; - private Class eventType; - - public EventHandler eh; + private final Listener listener; + private final Method method; + private final Class eventType; + private final EventHandler eh; public RegisteredListener(Listener listener, Method method, Class eventType, EventHandler eh) { this.listener = listener;