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:
parent
cc53757a37
commit
6ae11ef2d3
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -1,23 +1,36 @@
|
||||
package com.me.common.ecs;
|
||||
|
||||
public class ComponentMapper<T extends Component> {
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
private Engine engine;
|
||||
private ComponentType type;
|
||||
private Class<T> typeClass;
|
||||
public final class ComponentMapper<T extends Component> {
|
||||
|
||||
public ComponentMapper(Engine engine, Class<T> typeClass) {
|
||||
this.engine = engine;
|
||||
private static final Map<Class<? extends Component>, ComponentMapper> mappers = new HashMap<>();
|
||||
|
||||
private final ComponentType type;
|
||||
private final Class<T> typeClass;
|
||||
|
||||
private ComponentMapper(Class<T> 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 <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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,64 +6,44 @@ import java.util.Map;
|
||||
final class ComponentType {
|
||||
|
||||
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 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<? extends Component>... components) {
|
||||
long bits = 0l;
|
||||
for (Class<? extends Component> clazz : components) {
|
||||
bits |= getComponentType(clazz).bit;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
protected int getId() {
|
||||
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) {
|
||||
static ComponentType registerComponentType(Class<? extends Component> 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<? extends Component> component) {
|
||||
static ComponentType getComponentType(Class<? extends Component> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<BaseSystem> systems;
|
||||
private final EntityManager entityManager;
|
||||
private final ComponentBag[] components;
|
||||
private final Array<BaseSystem> systems;
|
||||
|
||||
private EntityManager entityManager;
|
||||
private ListenerRegistry listenerRegistry;
|
||||
|
||||
private Map<Class<? extends Component>, 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<? 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) {
|
||||
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<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() {
|
||||
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 extends Component> T getEntityComponent(Entity entity, Class<T> clazz) {
|
||||
return clazz.cast(components[ComponentType.getTypeId(clazz)].get(entity.id));
|
||||
<T extends Component> T getEntityComponent(Entity entity, Class<T> 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 <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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<? 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) {
|
||||
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<? 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;
|
||||
systemEnabledBits = 0;
|
||||
active = false;
|
||||
removed = false;
|
||||
pendingRefresh = false;
|
||||
}
|
||||
|
||||
protected void updateUniqueId() {
|
||||
this.uniqueId = nextUniqueId++;
|
||||
uniqueId = nextUniqueId++;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
@ -4,11 +4,11 @@ import com.badlogic.gdx.utils.Array;
|
||||
|
||||
final class EntityManager {
|
||||
|
||||
private Engine engine;
|
||||
private final Engine engine;
|
||||
|
||||
protected Array<Entity> entities;
|
||||
protected Array<Entity> removedEntities;
|
||||
protected Array<Entity> toRefresh;
|
||||
private final Array<Entity> entities;
|
||||
private final Array<Entity> removedEntities;
|
||||
private final Array<Entity> toRefresh;
|
||||
|
||||
EntityManager(Engine engine) {
|
||||
this.engine = engine;
|
||||
@ -18,7 +18,11 @@ final class EntityManager {
|
||||
this.toRefresh = new Array<>(false, 16);
|
||||
}
|
||||
|
||||
public Entity create() {
|
||||
Array<Entity> 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);
|
||||
|
@ -10,15 +10,16 @@ public abstract class EntitySystem extends BaseSystem {
|
||||
private static final Map<Class<? extends EntitySystem>, 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<Entity> entities;
|
||||
private final Array<Entity> entities;
|
||||
|
||||
@SafeVarargs
|
||||
public EntitySystem(Engine engine, Class<? extends Component>... 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<? extends EntitySystem> es) {
|
||||
private static long getBitFor(Class<? extends EntitySystem> es) {
|
||||
Long bits = systemBits.get(es);
|
||||
if (bits == null) {
|
||||
bits = nextBit;
|
||||
|
@ -11,15 +11,15 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ListenerRegistry {
|
||||
class ListenerRegistry {
|
||||
|
||||
private final Map<Class<? extends Event>, List<RegisteredListener>> 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<RegisteredListener> executors = getRegisteredListeners(eventClass);
|
||||
executors.add(new RegisteredListener(listener, method, eventClass, eh));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void callEvent(Event event) {
|
||||
void callEvent(Event event) {
|
||||
List<RegisteredListener> listeners = getRegisteredListeners(event.getClass());
|
||||
if (listeners.isEmpty()) {
|
||||
return;
|
||||
|
@ -4,11 +4,10 @@ import java.lang.reflect.Method;
|
||||
|
||||
public class RegisteredListener {
|
||||
|
||||
private Listener listener;
|
||||
private Method method;
|
||||
private Class<? extends Event> eventType;
|
||||
|
||||
public EventHandler eh;
|
||||
private final Listener listener;
|
||||
private final Method method;
|
||||
private final Class<? extends Event> eventType;
|
||||
private final EventHandler eh;
|
||||
|
||||
public RegisteredListener(Listener listener, Method method, Class<? extends Event> eventType, EventHandler eh) {
|
||||
this.listener = listener;
|
||||
|
Loading…
Reference in New Issue
Block a user