From f9152e61d30907000bbc483de6fa0afb61c16c31 Mon Sep 17 00:00:00 2001 From: Anton Gustafsson Date: Tue, 9 Jun 2015 12:38:29 +0200 Subject: [PATCH] Implement entity listener priority --- .../src/com/badlogic/ashley/core/Engine.java | 224 ++++++++++-------- .../ashley/core/EntityListenerTests.java | 90 +++++++ build.gradle | 2 + 3 files changed, 211 insertions(+), 105 deletions(-) diff --git a/ashley/src/com/badlogic/ashley/core/Engine.java b/ashley/src/com/badlogic/ashley/core/Engine.java index a8bd2130..ef7f6224 100644 --- a/ashley/src/com/badlogic/ashley/core/Engine.java +++ b/ashley/src/com/badlogic/ashley/core/Engine.java @@ -22,9 +22,9 @@ import com.badlogic.ashley.signals.Signal; import com.badlogic.ashley.utils.ImmutableArray; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Bits; import com.badlogic.gdx.utils.LongMap; import com.badlogic.gdx.utils.ObjectMap; -import com.badlogic.gdx.utils.ObjectMap.Entry; import com.badlogic.gdx.utils.Pool; import com.badlogic.gdx.utils.SnapshotArray; @@ -45,7 +45,8 @@ * @author Stefan Bachmann */ public class Engine { - private static SystemComparator comparator = new SystemComparator(); + private static SystemComparator systemComparator = new SystemComparator(); + private static Family empty = Family.all().get(); private Array entities; private ImmutableArray immutableEntities; @@ -61,8 +62,9 @@ public class Engine { private ObjectMap> families; private ObjectMap> immutableFamilies; - private SnapshotArray entityListeners; - private ObjectMap> familyListeners; + private SnapshotArray entityListeners; + private ObjectMap entityListenerMasks; + private final Listener componentAdded; private final Listener componentRemoved; @@ -88,8 +90,8 @@ public Engine(){ systemsByClass = new ObjectMap, EntitySystem>(); families = new ObjectMap>(); immutableFamilies = new ObjectMap>(); - entityListeners = new SnapshotArray(false, 16); - familyListeners = new ObjectMap>(); + entityListeners = new SnapshotArray(true, 16); + entityListenerMasks = new ObjectMap(); componentAdded = new ComponentListener(this); componentRemoved = new ComponentListener(this); @@ -183,7 +185,7 @@ public void addSystem(EntitySystem system){ systemsByClass.put(systemType, system); system.addedToEngineInternal(this); - systems.sort(comparator); + systems.sort(systemComparator); } } @@ -224,8 +226,17 @@ public ImmutableArray getEntitiesFor(Family family){ * * The listener will be notified every time an entity is added/removed to/from the engine. */ - public void addEntityListener(EntityListener listener) { - entityListeners.add(listener); + public void addEntityListener (EntityListener listener) { + addEntityListener(empty, 0, listener); + } + + /** + * Adds an {@link EntityListener}. The listener will be notified every time an entity is added/removed + * to/from the engine. The priority determines in which order the entity listeners will be called. Lower + * value means it will get executed first. + */ + public void addEntityListener (int priority, EntityListener listener) { + addEntityListener(empty, priority, listener); } /** @@ -234,25 +245,66 @@ public void addEntityListener(EntityListener listener) { * The listener will be notified every time an entity is added/removed to/from the given family. */ public void addEntityListener(Family family, EntityListener listener) { + addEntityListener(family, 0, listener); + } + + /** + * Adds an {@link EntityListener} for a specific {@link Family}. The listener will be notified every time an entity is + * added/removed to/from the given family. The priority determines in which order the entity listeners will be called. Lower + * value means it will get executed first. + */ + public void addEntityListener (Family family, int priority, EntityListener listener) { registerFamily(family); - SnapshotArray listeners = familyListeners.get(family); - if (listeners == null) { - listeners = new SnapshotArray(false, 16); - familyListeners.put(family, listeners); + int insertionIndex = 0; + while (insertionIndex < entityListeners.size) { + if (entityListeners.get(insertionIndex).priority <= priority) { + insertionIndex++; + } else { + break; + } + } + + // Shift up bitmasks by one step + for (Bits mask : entityListenerMasks.values()) { + for (int k = mask.length(); k > insertionIndex; k--) { + if (mask.get(k - 1)) { + mask.set(k); + } else { + mask.clear(k); + } + } + mask.clear(insertionIndex); } - listeners.add(listener); + entityListenerMasks.get(family).set(insertionIndex); + + EntityListenerData entityListenerData = new EntityListenerData(); + entityListenerData.listener = listener; + entityListenerData.priority = priority; + entityListeners.insert(insertionIndex, entityListenerData); } /** * Removes an {@link EntityListener} */ - public void removeEntityListener(EntityListener listener) { - entityListeners.removeValue(listener, true); + public void removeEntityListener (EntityListener listener) { + for (int i = 0; i < entityListeners.size; i++) { + EntityListenerData entityListenerData = entityListeners.get(i); + if (entityListenerData.listener == listener) { + // Shift down bitmasks by one step + for (Bits mask : entityListenerMasks.values()) { + for (int k = i, n = mask.length(); k < n; k++) { + if (mask.get(k + 1)) { + mask.set(k); + } else { + mask.clear(k); + } + } + } - for (SnapshotArray familyListenerArray : familyListeners.values()) { - familyListenerArray.removeValue(listener, true); + entityListeners.removeIndex(i--); + } } } @@ -279,29 +331,48 @@ public void update(float deltaTime){ updating = false; } - private void updateFamilyMembership(Entity entity){ - for (Entry> entry : families.entries()) { - Family family = entry.key; - Array familyEntities = entry.value; - int familyIndex = family.getIndex(); - - - boolean belongsToFamily = entity.getFamilyBits().get(familyIndex); - boolean matches = family.matches(entity); + private void updateFamilyMembership (Entity entity, boolean removing) { + // Find families that the entity was added to/removed from, and fill + // the bitmasks with corresponding listener bits. + Bits addListenerBits = new Bits(); + Bits removeListenerBits = new Bits(); + + for (Family family : entityListenerMasks.keys()) { + final int familyIndex = family.getIndex(); + final Bits entityFamilyBits = entity.getFamilyBits(); + + boolean belongsToFamily = entityFamilyBits.get(familyIndex); + boolean matches = family.matches(entity) && !removing; + + if (belongsToFamily != matches) { + final Bits listenersMask = entityListenerMasks.get(family); + final Array familyEntities = families.get(family); + if (matches) { + addListenerBits.or(listenersMask); + familyEntities.add(entity); + entityFamilyBits.set(familyIndex); + } else { + removeListenerBits.or(listenersMask); + familyEntities.removeValue(entity, true); + entityFamilyBits.clear(familyIndex); + } + } + } - if (!belongsToFamily && matches) { - familyEntities.add(entity); - entity.getFamilyBits().set(familyIndex); + // Notify listeners; set bits match indices of listeners + notifying = true; + Object[] items = entityListeners.begin(); - notifyFamilyListenersAdd(family, entity); - } - else if (belongsToFamily && !matches) { - familyEntities.removeValue(entity, true); - entity.getFamilyBits().clear(familyIndex); + for (int i = removeListenerBits.nextSetBit(0); i >= 0; i = removeListenerBits.nextSetBit(i + 1)) { + ((EntityListenerData)items[i]).listener.entityRemoved(entity); + } - notifyFamilyListenersRemove(family, entity); - } + for (int i = addListenerBits.nextSetBit(0); i >= 0; i = addListenerBits.nextSetBit(i + 1)) { + ((EntityListenerData)items[i]).listener.entityAdded(entity); } + + entityListeners.end(); + notifying = false; } protected void removeEntityInternal(Entity entity) { @@ -314,32 +385,11 @@ protected void removeEntityInternal(Entity entity) { removed = true; } - if(!entity.getFamilyBits().isEmpty()){ - for (Entry> entry : families.entries()) { - Family family = entry.key; - Array familyEntities = entry.value; - - if(family.matches(entity)){ - familyEntities.removeValue(entity, true); - entity.getFamilyBits().clear(family.getIndex()); - notifyFamilyListenersRemove(family, entity); - } - } - } + updateFamilyMembership(entity, true); entity.componentAdded.remove(componentAdded); entity.componentRemoved.remove(componentRemoved); entity.componentOperationHandler = null; - - notifying = true; - Object[] items = entityListeners.begin(); - for (int i = 0, n = entityListeners.size; i < n; i++) { - EntityListener listener = (EntityListener)items[i]; - listener.entityRemoved(entity); - } - entityListeners.end(); - notifying = false; - entity.uuid = 0L; } @@ -347,50 +397,11 @@ protected void addEntityInternal(Entity entity) { entities.add(entity); entitiesById.put(entity.getId(), entity); - updateFamilyMembership(entity); + updateFamilyMembership(entity, false); entity.componentAdded.add(componentAdded); entity.componentRemoved.add(componentRemoved); entity.componentOperationHandler = componentOperationHandler; - - notifying = true; - Object[] items = entityListeners.begin(); - for (int i = 0, n = entityListeners.size; i < n; i++) { - EntityListener listener = (EntityListener)items[i]; - listener.entityAdded(entity); - } - entityListeners.end(); - notifying = false; - } - - private void notifyFamilyListenersAdd(Family family, Entity entity) { - SnapshotArray listeners = familyListeners.get(family); - - if (listeners != null) { - notifying = true; - Object[] items = listeners.begin(); - for (int i = 0, n = listeners.size; i < n; i++) { - EntityListener listener = (EntityListener)items[i]; - listener.entityAdded(entity); - } - listeners.end(); - notifying = false; - } - } - - private void notifyFamilyListenersRemove(Family family, Entity entity) { - SnapshotArray listeners = familyListeners.get(family); - - if (listeners != null) { - notifying = true; - Object[] items = listeners.begin(); - for (int i = 0, n = listeners.size; i < n; i++) { - EntityListener listener = (EntityListener)items[i]; - listener.entityRemoved(entity); - } - listeners.end(); - notifying = false; - } } private ImmutableArray registerFamily(Family family) { @@ -401,12 +412,10 @@ private ImmutableArray registerFamily(Family family) { immutableEntitiesInFamily = new ImmutableArray(familyEntities); families.put(family, familyEntities); immutableFamilies.put(family, immutableEntitiesInFamily); + entityListenerMasks.put(family, new Bits()); - for(Entity e : this.entities){ - if(family.matches(e)) { - familyEntities.add(e); - e.getFamilyBits().set(family.getIndex()); - } + for (Entity entity : this.entities){ + updateFamilyMembership(entity, false); } } @@ -460,7 +469,7 @@ public ComponentListener(Engine engine) { @Override public void receive(Signal signal, Entity object) { - engine.updateFamilyMembership(object); + engine.updateFamilyMembership(object, false); } } @@ -533,6 +542,11 @@ protected ComponentOperation newObject() { } } + private static class EntityListenerData { + public EntityListener listener; + public int priority; + } + private static class SystemComparator implements Comparator{ @Override public int compare(EntitySystem a, EntitySystem b) { diff --git a/ashley/tests/com/badlogic/ashley/core/EntityListenerTests.java b/ashley/tests/com/badlogic/ashley/core/EntityListenerTests.java index d958cc82..915c73f1 100644 --- a/ashley/tests/com/badlogic/ashley/core/EntityListenerTests.java +++ b/ashley/tests/com/badlogic/ashley/core/EntityListenerTests.java @@ -1,7 +1,10 @@ package com.badlogic.ashley.core; +import static org.mockito.Mockito.*; + import org.junit.Test; +import org.mockito.InOrder; public class EntityListenerTests { @@ -101,4 +104,91 @@ public void entityAdded (Entity entity) { public class PositionComponent implements Component { } + + @Test + public void entityListenerPriority () { + EntityListener a = mock(EntityListener.class); + EntityListener b = mock(EntityListener.class); + EntityListener c = mock(EntityListener.class); + InOrder inOrder = inOrder(a, b, c); + + Entity entity = new Entity(); + Engine engine = new Engine(); + engine.addEntityListener(-3, b); + engine.addEntityListener(c); + engine.addEntityListener(-4, a); + inOrder.verifyNoMoreInteractions(); + + engine.addEntity(entity); + inOrder.verify(a).entityAdded(entity); + inOrder.verify(b).entityAdded(entity); + inOrder.verify(c).entityAdded(entity); + inOrder.verifyNoMoreInteractions(); + + engine.removeEntity(entity); + inOrder.verify(a).entityRemoved(entity); + inOrder.verify(b).entityRemoved(entity); + inOrder.verify(c).entityRemoved(entity); + inOrder.verifyNoMoreInteractions(); + + engine.removeEntityListener(b); + inOrder.verifyNoMoreInteractions(); + + engine.addEntity(entity); + inOrder.verify(a).entityAdded(entity); + inOrder.verify(c).entityAdded(entity); + inOrder.verifyNoMoreInteractions(); + + engine.addEntityListener(4, b); + inOrder.verifyNoMoreInteractions(); + + engine.removeEntity(entity); + inOrder.verify(a).entityRemoved(entity); + inOrder.verify(c).entityRemoved(entity); + inOrder.verify(b).entityRemoved(entity); + inOrder.verifyNoMoreInteractions(); + } + + private static class ComponentA implements Component { + } + + private static class ComponentB implements Component { + } + + @Test + public void familyListenerPriority () { + EntityListener a = mock(EntityListener.class); + EntityListener b = mock(EntityListener.class); + InOrder inOrder = inOrder(a, b); + + Engine engine = new Engine(); + engine.addEntityListener(Family.all(ComponentB.class).get(), -2, b); + engine.addEntityListener(Family.all(ComponentA.class).get(), -3, a); + inOrder.verifyNoMoreInteractions(); + + Entity entity = new Entity(); + entity.add(new ComponentA()); + entity.add(new ComponentB()); + + engine.addEntity(entity); + inOrder.verify(a).entityAdded(entity); + inOrder.verify(b).entityAdded(entity); + inOrder.verifyNoMoreInteractions(); + + entity.remove(ComponentB.class); + inOrder.verify(b).entityRemoved(entity); + inOrder.verifyNoMoreInteractions(); + + entity.remove(ComponentA.class); + inOrder.verify(a).entityRemoved(entity); + inOrder.verifyNoMoreInteractions(); + + entity.add(new ComponentA()); + inOrder.verify(a).entityAdded(entity); + inOrder.verifyNoMoreInteractions(); + + entity.add(new ComponentB()); + inOrder.verify(b).entityAdded(entity); + inOrder.verifyNoMoreInteractions(); + } } diff --git a/build.gradle b/build.gradle index 16822224..83e29414 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ ext { projectGroup = "ashley" gdxVersion = "1.6.2" jUnitVersion = "4.12" + mockitoVersion = "1.10.19" } /** needed to disable Java 8 doclint which throws errors **/ @@ -33,6 +34,7 @@ project(":ashley") { dependencies { compile "com.badlogicgames.gdx:gdx:$gdxVersion" testCompile "junit:junit:$jUnitVersion" + testCompile "org.mockito:mockito-core:$mockitoVersion" } }