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
This commit is contained in:
Matt Low 2020-02-01 15:55:54 +04:00
parent cc53757a37
commit 6ae11ef2d3
10 changed files with 175 additions and 173 deletions

View File

@ -2,7 +2,7 @@ package com.me.common.ecs;
public abstract class BaseSystem { public abstract class BaseSystem {
protected Engine engine; protected final Engine engine;
public BaseSystem(Engine engine) { public BaseSystem(Engine engine) {
this.engine = engine; this.engine = engine;

View File

@ -1,38 +1,33 @@
package com.me.common.ecs; package com.me.common.ecs;
public class ComponentBag { class ComponentBag {
private Component[] items; private Component[] items;
private int size; private int size;
public ComponentBag() { ComponentBag() {
this.items = new Component[16]; this.items = new Component[16];
this.size = items.length; this.size = items.length;
} }
public Component get(int index) { Component get(int index) {
if (index >= size) { if (index >= size) {
return null; return null;
} }
return items[index]; return items[index];
} }
public boolean contains(int index) { void insert(int index, Component item) {
return index < size && items[index] != null;
}
public void insert(int index, Component item) {
if (index >= size) { if (index >= size) {
grow((int) (index * 1.5f)); grow((int) (index * 1.5f));
} }
items[index] = item; items[index] = item;
} }
public void remove(int index) { void remove(int index) {
if (index >= size) { if (index < size) {
throw new IndexOutOfBoundsException("index must be < size"); items[index] = null;
} }
items[index] = null;
} }
private void grow(int newSize) { private void grow(int newSize) {

View File

@ -1,23 +1,36 @@
package com.me.common.ecs; package com.me.common.ecs;
public class ComponentMapper<T extends Component> { import java.util.HashMap;
import java.util.Map;
private Engine engine; public final class ComponentMapper<T extends Component> {
private ComponentType type;
private Class<T> typeClass;
public ComponentMapper(Engine engine, Class<T> typeClass) { private static final Map<Class<? extends Component>, ComponentMapper> mappers = new HashMap<>();
this.engine = engine;
private final ComponentType type;
private final Class<T> typeClass;
private ComponentMapper(Class<T> typeClass) {
this.type = ComponentType.getComponentType(typeClass); this.type = ComponentType.getComponentType(typeClass);
this.typeClass = typeClass; this.typeClass = typeClass;
} }
public T get(Entity entity) { public T get(Entity entity) {
return typeClass.cast(engine.getEntityComponent(entity, type)); return typeClass.cast(entity.getComponent(type));
} }
public boolean has(Entity entity) { public boolean has(Entity entity) {
return type.isTypeInMask(entity.componentBits); return type.isInBits(entity.componentBits);
}
@SuppressWarnings("unchecked")
public static <T extends Component> ComponentMapper<T> getFor(Class<T> typeClass) {
ComponentMapper<T> mapper = mappers.get(typeClass);
if (mapper == null) {
mapper = new ComponentMapper<>(typeClass);
mappers.put(typeClass, mapper);
}
return mapper;
} }
} }

View File

@ -6,64 +6,44 @@ import java.util.Map;
final class ComponentType { final class ComponentType {
private static final Map<Class<? extends Component>, ComponentType> types = new HashMap<>(); private static final Map<Class<? extends Component>, ComponentType> types = new HashMap<>();
private static final ComponentType[] typeById = new ComponentType[Long.SIZE];
private static long nextBit = 1l; private static long nextBit = 1l;
private static int nextId = 0; private static int nextId = 0;
protected long bits; final long bit;
protected int id; final int id;
private ComponentType() { private ComponentType() {
this.bits = nextBit; this.bit = nextBit;
this.id = nextId++; this.id = nextId++;
this.nextBit <<= 1; nextBit <<= 1;
} }
protected long getBits() { boolean isInBits(long bits) {
return (bits & bit) == bit;
}
@SafeVarargs
static long getBitsFor(Class<? extends Component>... components) {
long bits = 0l;
for (Class<? extends Component> clazz : components) {
bits |= getComponentType(clazz).bit;
}
return bits; return bits;
} }
protected int getId() { static ComponentType registerComponentType(Class<? extends Component> component) {
return id;
}
protected static long getMaskBits(Class<? extends Component>... components) {
long mask = 0l;
for (Class<? extends Component> clazz : components) {
mask |= getTypeBits(clazz);
}
return mask;
}
protected static long getTypeBits(Class<? extends Component> component) {
return getComponentType(component).bits;
}
protected static int getTypeId(Class<? extends Component> 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<? extends Component> component) {
ComponentType type = types.get(component); ComponentType type = types.get(component);
if (type != null) { if (type != null) {
throw new IllegalArgumentException(component.getName() + " has already been registered."); throw new IllegalArgumentException(component.getName() + " has already been registered.");
} }
type = new ComponentType(); type = new ComponentType();
types.put(component, type); types.put(component, type);
typeById[type.id] = type; return type;
} }
protected static ComponentType getComponentType(Class<? extends Component> component) { static ComponentType getComponentType(Class<? extends Component> component) {
ComponentType type = types.get(component); ComponentType type = types.get(component);
if (type == null) { if (type == null) {
throw new IllegalArgumentException(component.getName() + " has not been registered."); throw new IllegalArgumentException(component.getName() + " has not been registered.");
@ -71,8 +51,4 @@ final class ComponentType {
return type; return type;
} }
protected static int getRegisteredComponentTypeCount() {
return types.size();
}
} }

View File

@ -4,46 +4,51 @@ import com.badlogic.gdx.utils.Array;
import com.me.common.ecs.event.Event; import com.me.common.ecs.event.Event;
import com.me.common.ecs.event.Listener; import com.me.common.ecs.event.Listener;
import java.util.HashMap;
import java.util.Map;
public class Engine { public class Engine {
private ComponentBag[] components; private final EntityManager entityManager;
protected Array<BaseSystem> systems; private final ComponentBag[] components;
private final Array<BaseSystem> systems;
private EntityManager entityManager; private final ListenerRegistry listenerRegistry;
private ListenerRegistry listenerRegistry;
private Map<Class<? extends Component>, ComponentMapper> componentMappers;
public Engine() { public Engine() {
this.entityManager = new EntityManager(this); this.entityManager = new EntityManager(this);
this.components = new ComponentBag[Long.SIZE];
this.systems = new Array<>(); this.systems = new Array<>();
this.listenerRegistry = new ListenerRegistry(); this.listenerRegistry = new ListenerRegistry();
this.componentMappers = new HashMap<>();
} }
/**
* Register a Component class.
* @param clazz
*/
public void registerComponentClass(Class<? extends Component> clazz) { public void registerComponentClass(Class<? extends Component> clazz) {
ComponentType.registerComponentType(clazz); ComponentType type = ComponentType.registerComponentType(clazz);
components[type.id] = new ComponentBag();
} }
/**
* Register a BaseSystem.
* @param system
*/
public void registerSystem(BaseSystem system) { public void registerSystem(BaseSystem system) {
this.systems.add(system); systems.add(system);
} }
/**
* Register an event Listener.
* @param listener
*/
public void registerListener(Listener listener) { public void registerListener(Listener listener) {
listenerRegistry.registerListener(listener); listenerRegistry.registerListener(listener);
} }
public void ready() { /**
this.components = new ComponentBag[ComponentType.getRegisteredComponentTypeCount()]; * Process all systems
for (int i = 0; i < components.length; i++) { * @param dt the time (in seconds) passed since the last update
components[i] = new ComponentBag(); */
}
}
public void update(float dt) { public void update(float dt) {
entityManager.update(); entityManager.update();
@ -54,61 +59,70 @@ public class Engine {
} }
} }
/**
* @return all entities known by the engine - both those active and inactive.
*/
public Array<Entity> getEntities() { public Array<Entity> getEntities() {
return entityManager.entities; return entityManager.getEntities();
} }
public int getEntityCount() { /**
return entityManager.entities.size; * @return all currently registered systems.
*/
public Array<BaseSystem> getSystems() {
return systems;
} }
/**
* @return a new Entity object which can have fresh components added to it
*/
public Entity createEntity() { public Entity createEntity() {
return entityManager.create(); 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) { public void callEvent(Event event) {
listenerRegistry.callEvent(event); listenerRegistry.callEvent(event);
} }
protected void refreshEntity(Entity entity) { void refreshEntity(Entity entity) {
entityManager.queueRefresh(entity); entityManager.queueRefresh(entity);
} }
protected void removeAllEntityComponents(int entityId) { void removeAllEntityComponents(int entityId) {
for (int i = 0; i < components.length; i++) { for (ComponentBag bag : components) {
components[i].insert(entityId, null); 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()); ComponentType type = ComponentType.getComponentType(component.getClass());
components[type.id].insert(entity.id, component); components[type.id].insert(entity.id, component);
entity.addComponentType(type); entity.addComponentType(type);
} }
protected void removeEntityComponent(Entity entity, Component component) { void removeEntityComponent(Entity entity, Component component) {
ComponentType type = ComponentType.getComponentType(component.getClass()); ComponentType type = ComponentType.getComponentType(component.getClass());
components[type.id].remove(entity.id); components[type.id].remove(entity.id);
entity.removeComponentType(type); entity.removeComponentType(type);
} }
protected <T extends Component> T getEntityComponent(Entity entity, Class<T> clazz) { <T extends Component> T getEntityComponent(Entity entity, Class<T> clazz) {
return clazz.cast(components[ComponentType.getTypeId(clazz)].get(entity.id)); 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); return components[type.id].get(entity.id);
} }
@SuppressWarnings("unchecked")
public <T extends Component> ComponentMapper<T> getComponentMapper(Class<T> typeClass) {
ComponentMapper<T> mapper = componentMappers.get(typeClass);
if (mapper == null) {
mapper = new ComponentMapper<>(this, typeClass);
componentMappers.put(typeClass, mapper);
}
return mapper;
}
} }

View File

@ -5,15 +5,15 @@ public final class Entity {
private static int nextId = 0; private static int nextId = 0;
private static long nextUniqueId = 0; private static long nextUniqueId = 0;
private Engine engine; private final Engine engine;
protected final int id; final int id;
protected long componentBits; long componentBits;
protected long systemEnabledBits; long systemEnabledBits;
boolean pendingRefresh;
protected boolean active; boolean active;
protected boolean removed; boolean removed;
protected boolean pendingRefresh;
private long uniqueId; private long uniqueId;
@ -30,6 +30,10 @@ public final class Entity {
return uniqueId; return uniqueId;
} }
public Engine getEngine() {
return engine;
}
public boolean isActive() { public boolean isActive() {
return active; return active;
} }
@ -60,18 +64,6 @@ public final class Entity {
} }
} }
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);
}
public void addComponent(Component component) { public void addComponent(Component component) {
engine.addEntityComponent(this, component); engine.addEntityComponent(this, component);
refresh(); refresh();
@ -82,39 +74,49 @@ public final class Entity {
refresh(); refresh();
} }
protected void addComponentType(ComponentType type) { private void refresh() {
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() {
if (!pendingRefresh) { if (!pendingRefresh) {
engine.refreshEntity(this); engine.refreshEntity(this);
pendingRefresh = true; pendingRefresh = true;
} }
} }
protected void reset() { public boolean hasComponent(Class<? extends Component> clazz) {
return ComponentType.getComponentType(clazz).isInBits(componentBits);
}
public <T extends Component> T getComponent(Class<T> 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; componentBits = 0;
systemEnabledBits = 0; systemEnabledBits = 0;
active = false; active = false;
removed = false; removed = false;
pendingRefresh = false; pendingRefresh = false;
} uniqueId = nextUniqueId++;
protected void updateUniqueId() {
this.uniqueId = nextUniqueId++;
} }
public String toString() { public String toString() {

View File

@ -4,11 +4,11 @@ import com.badlogic.gdx.utils.Array;
final class EntityManager { final class EntityManager {
private Engine engine; private final Engine engine;
protected Array<Entity> entities; private final Array<Entity> entities;
protected Array<Entity> removedEntities; private final Array<Entity> removedEntities;
protected Array<Entity> toRefresh; private final Array<Entity> toRefresh;
EntityManager(Engine engine) { EntityManager(Engine engine) {
this.engine = engine; this.engine = engine;
@ -18,7 +18,11 @@ final class EntityManager {
this.toRefresh = new Array<>(false, 16); this.toRefresh = new Array<>(false, 16);
} }
public Entity create() { Array<Entity> getEntities() {
return entities;
}
Entity create() {
Entity entity; Entity entity;
if (!removedEntities.isEmpty()) { if (!removedEntities.isEmpty()) {
entity = removedEntities.removeIndex(0); entity = removedEntities.removeIndex(0);
@ -26,12 +30,11 @@ final class EntityManager {
entity = new Entity(engine); entity = new Entity(engine);
} }
entity.reset(); entity.reset();
entity.updateUniqueId();
entities.add(entity); entities.add(entity);
return entity; return entity;
} }
public void update() { void update() {
if (toRefresh.isEmpty()) { if (toRefresh.isEmpty()) {
return; return;
} }
@ -44,12 +47,12 @@ final class EntityManager {
toRefresh.clear(); toRefresh.clear();
} }
public void queueRefresh(Entity entity) { void queueRefresh(Entity entity) {
toRefresh.add(entity); toRefresh.add(entity);
} }
private void refreshEntity(Entity entity) { private void refreshEntity(Entity entity) {
for (BaseSystem system : engine.systems) { for (BaseSystem system : engine.getSystems()) {
if (!(system instanceof EntitySystem)) { if (!(system instanceof EntitySystem)) {
continue; continue;
} }
@ -57,7 +60,7 @@ final class EntityManager {
((EntitySystem) system).refresh(entity); ((EntitySystem) system).refresh(entity);
} }
if (entity.removed) { if (entity.isRemoved()) {
engine.removeAllEntityComponents(entity.id); engine.removeAllEntityComponents(entity.id);
entities.removeValue(entity, true); entities.removeValue(entity, true);
removedEntities.add(entity); removedEntities.add(entity);

View File

@ -10,15 +10,16 @@ public abstract class EntitySystem extends BaseSystem {
private static final Map<Class<? extends EntitySystem>, Long> systemBits = new HashMap<>(); private static final Map<Class<? extends EntitySystem>, Long> systemBits = new HashMap<>();
private static long nextBit = 1l; private static long nextBit = 1l;
private long systemBit; private final long systemBit;
private long typeBits; private final long componentBits;
protected Array<Entity> entities; private final Array<Entity> entities;
@SafeVarargs
public EntitySystem(Engine engine, Class<? extends Component>... components) { public EntitySystem(Engine engine, Class<? extends Component>... components) {
super(engine); super(engine);
this.systemBit = getBitFor(getClass()); this.systemBit = getBitFor(getClass());
this.typeBits = ComponentType.getMaskBits(components); this.componentBits = ComponentType.getBitsFor(components);
this.entities = new Array<>(true, 16, Entity.class); 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); public abstract void processEntity(Entity entity, float dt);
protected void refresh(Entity entity) { void refresh(Entity entity) {
boolean enabled = (entity.systemEnabledBits & systemBit) == systemBit; 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) { if (interested && !enabled && entity.active && !entity.removed) {
add(entity); add(entity);
} else if (enabled && (!interested || !entity.active || entity.removed)) { } else if (enabled && (!interested || !entity.active) || entity.removed) {
remove(entity); remove(entity);
} }
} }
@ -59,7 +60,7 @@ public abstract class EntitySystem extends BaseSystem {
} }
static long getBitFor(Class<? extends EntitySystem> es) { private static long getBitFor(Class<? extends EntitySystem> es) {
Long bits = systemBits.get(es); Long bits = systemBits.get(es);
if (bits == null) { if (bits == null) {
bits = nextBit; bits = nextBit;

View File

@ -11,15 +11,15 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class ListenerRegistry { class ListenerRegistry {
private final Map<Class<? extends Event>, List<RegisteredListener>> registeredListeners; private final Map<Class<? extends Event>, List<RegisteredListener>> registeredListeners;
protected ListenerRegistry() { ListenerRegistry() {
this.registeredListeners = new HashMap<>(); this.registeredListeners = new HashMap<>();
} }
protected void registerListener(Listener listener) { void registerListener(Listener listener) {
for (Method method : listener.getClass().getMethods()) { for (Method method : listener.getClass().getMethods()) {
EventHandler eh = method.getAnnotation(EventHandler.class); EventHandler eh = method.getAnnotation(EventHandler.class);
if (eh == null) { if (eh == null) {
@ -37,10 +37,9 @@ public class ListenerRegistry {
List<RegisteredListener> executors = getRegisteredListeners(eventClass); List<RegisteredListener> executors = getRegisteredListeners(eventClass);
executors.add(new RegisteredListener(listener, method, eventClass, eh)); executors.add(new RegisteredListener(listener, method, eventClass, eh));
} }
} }
protected void callEvent(Event event) { void callEvent(Event event) {
List<RegisteredListener> listeners = getRegisteredListeners(event.getClass()); List<RegisteredListener> listeners = getRegisteredListeners(event.getClass());
if (listeners.isEmpty()) { if (listeners.isEmpty()) {
return; return;

View File

@ -4,11 +4,10 @@ import java.lang.reflect.Method;
public class RegisteredListener { public class RegisteredListener {
private Listener listener; private final Listener listener;
private Method method; private final Method method;
private Class<? extends Event> eventType; private final Class<? extends Event> eventType;
private final EventHandler eh;
public EventHandler eh;
public RegisteredListener(Listener listener, Method method, Class<? extends Event> eventType, EventHandler eh) { public RegisteredListener(Listener listener, Method method, Class<? extends Event> eventType, EventHandler eh) {
this.listener = listener; this.listener = listener;