diff --git a/CHANGELOG.md b/CHANGELOG.md index a37bf3e..5b8a90a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## Changelog ### Ashley 1.8.0 - +* **Bug fix**: Allow add and remove system during iteration, operations are compute at end of update loop Issue #310. * **Bug fix**: Poolable Component returned to their ComponentPools even if EntityPool is full. Issue #302. * **Update**: Uses libgdx 1.10.0. Commit afa68fc165119a2c79c1709c642e6b620a973ecc. diff --git a/ashley/src/com/badlogic/ashley/core/Engine.java b/ashley/src/com/badlogic/ashley/core/Engine.java index fdd019d..bda3804 100644 --- a/ashley/src/com/badlogic/ashley/core/Engine.java +++ b/ashley/src/com/badlogic/ashley/core/Engine.java @@ -139,21 +139,21 @@ public ImmutableArray getEntities() { * the new one will replace the old one. */ public void addSystem(EntitySystem system){ - systemManager.addSystem(system); + systemManager.addSystem(system, updating); } /** * Removes the {@link EntitySystem} from this Engine. */ public void removeSystem(EntitySystem system){ - systemManager.removeSystem(system); + systemManager.removeSystem(system, updating); } /** * Removes all systems from this Engine. */ public void removeAllSystems(){ - systemManager.removeAllSystems(); + systemManager.removeAllSystems(updating); } /** @@ -248,6 +248,7 @@ public void update(float deltaTime){ } finally { updating = false; + systemManager.processPendingOperations(); } } diff --git a/ashley/src/com/badlogic/ashley/core/SystemManager.java b/ashley/src/com/badlogic/ashley/core/SystemManager.java index 9517b25..46686a9 100644 --- a/ashley/src/com/badlogic/ashley/core/SystemManager.java +++ b/ashley/src/com/badlogic/ashley/core/SystemManager.java @@ -1,69 +1,125 @@ package com.badlogic.ashley.core; -import java.util.Comparator; - -import com.badlogic.ashley.signals.Listener; -import com.badlogic.ashley.signals.Signal; import com.badlogic.ashley.utils.ImmutableArray; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.Pool; + +import java.util.Comparator; class SystemManager { - private SystemComparator systemComparator = new SystemComparator(); - private Array systems = new Array(true, 16); - private ImmutableArray immutableSystems = new ImmutableArray(systems); - private ObjectMap, EntitySystem> systemsByClass = new ObjectMap, EntitySystem>(); - private SystemListener listener; - - public SystemManager(SystemListener listener) { - this.listener = listener; - } - - public void addSystem(EntitySystem system){ - Class systemType = system.getClass(); - EntitySystem oldSystem = getSystem(systemType); - - if (oldSystem != null) { - removeSystem(oldSystem); - } - - systems.add(system); - systemsByClass.put(systemType, system); - systems.sort(systemComparator); - listener.systemAdded(system); - } - - public void removeSystem(EntitySystem system){ - if(systems.removeValue(system, true)) { - systemsByClass.remove(system.getClass()); - listener.systemRemoved(system); - } - } - - public void removeAllSystems() { - while(systems.size > 0) { - removeSystem(systems.first()); - } - } - - @SuppressWarnings("unchecked") - public T getSystem(Class systemType) { - return (T) systemsByClass.get(systemType); - } - - public ImmutableArray getSystems() { - return immutableSystems; - } - - private static class SystemComparator implements Comparator{ - @Override - public int compare(EntitySystem a, EntitySystem b) { - return a.priority > b.priority ? 1 : (a.priority == b.priority) ? 0 : -1; - } - } - - interface SystemListener { - void systemAdded(EntitySystem system); - void systemRemoved(EntitySystem system); - } + private SystemComparator systemComparator = new SystemComparator(); + private Array systems = new Array(true, 16); + private ImmutableArray immutableSystems = new ImmutableArray(systems); + private ObjectMap, EntitySystem> systemsByClass = new ObjectMap, EntitySystem>(); + private SystemListener listener; + + private Array pendingOperations = new Array<>(); + private SystemManager.SystemOperationPool systemOperationPool = new SystemManager.SystemOperationPool(); + + public SystemManager(SystemListener listener) { + this.listener = listener; + } + + public void addSystem(EntitySystem system, boolean delayed) { + if (delayed) { + SystemOperation operation = systemOperationPool.obtain(); + operation.type = SystemOperation.Type.Add; + operation.system = system; + pendingOperations.add(operation); + } else { + Class systemType = system.getClass(); + EntitySystem oldSystem = getSystem(systemType); + + if (oldSystem != null) { + removeSystem(oldSystem, delayed); + } + + systems.add(system); + systemsByClass.put(systemType, system); + systems.sort(systemComparator); + listener.systemAdded(system); + } + } + + public void removeSystem(EntitySystem system, boolean delayed) { + if (delayed) { + SystemOperation operation = systemOperationPool.obtain(); + operation.type = SystemOperation.Type.Remove; + operation.system = system; + } else { + if (systems.removeValue(system, true)) { + systemsByClass.remove(system.getClass()); + listener.systemRemoved(system); + } + } + } + + public void removeAllSystems(boolean delayed) { + while (systems.size > 0) { + removeSystem(systems.first(), delayed); + } + } + + public void processPendingOperations() { + for (int i = 0; i < pendingOperations.size; ++i) { + SystemOperation operation = pendingOperations.get(i); + switch(operation.type) { + case Add: + addSystem(operation.system, false); + break; + case Remove: + removeSystem(operation.system, false); + break; + default: + throw new AssertionError("Unexpected EntityOperation type"); + } + systemOperationPool.free(operation); + } + pendingOperations.clear(); + } + + @SuppressWarnings("unchecked") + public T getSystem(Class systemType) { + return (T) systemsByClass.get(systemType); + } + + public ImmutableArray getSystems() { + return immutableSystems; + } + + private static class SystemComparator implements Comparator { + @Override + public int compare(EntitySystem a, EntitySystem b) { + return a.priority > b.priority ? 1 : (a.priority == b.priority) ? 0 : -1; + } + } + + interface SystemListener { + void systemAdded(EntitySystem system); + + void systemRemoved(EntitySystem system); + } + + + private static class SystemOperation implements Pool.Poolable { + public enum Type { + Add, Remove; + } + + public Type type; + public EntitySystem system; + + @Override + public void reset() { + system = null; + } + } + + private static class SystemOperationPool extends Pool { + @Override + protected SystemManager.SystemOperation newObject() { + return new SystemManager.SystemOperation(); + } + } } diff --git a/ashley/tests/com/badlogic/ashley/core/AddRemoveSystemDuringIterationTest.java b/ashley/tests/com/badlogic/ashley/core/AddRemoveSystemDuringIterationTest.java new file mode 100644 index 0000000..26f60a2 --- /dev/null +++ b/ashley/tests/com/badlogic/ashley/core/AddRemoveSystemDuringIterationTest.java @@ -0,0 +1,112 @@ +package com.badlogic.ashley.core; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 3 systems add at engine start with priority 1, 5, 10 + * System 5 add system with priority 4 during first iteration by system 5 + * + */ +public class AddRemoveSystemDuringIterationTest { + Engine engine; + List systemCallOrder; //updateIndex -> list by priority + boolean firstUpdateDone = false; + + private void addSystemUpdateCall(EntitySystem entitySystem) { + systemCallOrder.add(entitySystem.priority); + } + + @Before + public void setUp() { + engine = new Engine(); + systemCallOrder = new ArrayList<>(); + firstUpdateDone = false; + } + + @Test + public void addSystemDuringIterationTest() { + EntitySystem system1 = new NamedSystem(1) { + }; + EntitySystem system10 = new NamedSystem(10) { + }; + final EntitySystem system4 = new NamedSystem(4) { //System added during update by system 5 + }; + EntitySystem system5 = new NamedSystem(5) { + + @Override + public void update(float deltaTime) { + super.update(deltaTime); + if (!firstUpdateDone) { + getEngine().addSystem(system4); + firstUpdateDone = true; + } + } + }; + + engine.addSystem(system1); + engine.addSystem(system10); + engine.addSystem(system5); + + engine.update(1); //system 5 add system 4 during iteration + + List expected = Arrays.asList(1, 5, 10); //system 4 should not be called + Assert.assertArrayEquals(expected.toArray(), systemCallOrder.toArray()); + + systemCallOrder.clear(); + engine.update(1); + expected = Arrays.asList(1, 4, 5, 10); //second update ok + Assert.assertArrayEquals(expected.toArray(), systemCallOrder.toArray()); + } + @Test + public void removeSystemDuringIterationTest() { + engine = new Engine(); + EntitySystem system1 = new NamedSystem(1) { + }; + EntitySystem system10 = new NamedSystem(10) { + }; + final EntitySystem system8 = new NamedSystem(8) { //System remove during update by system 5 + }; + EntitySystem system5 = new NamedSystem(5) { + + @Override + public void update(float deltaTime) { + super.update(deltaTime); + if (!firstUpdateDone) { + getEngine().removeSystem(system8); + firstUpdateDone = true; + } + } + }; + + engine.addSystem(system1); + engine.addSystem(system10); + engine.addSystem(system5); + + engine.update(1); //system 5 remove system 8 during iteration + + List expected = Arrays.asList(1, 5, 10); //system 8 should not be called + Assert.assertArrayEquals(expected.toArray(), systemCallOrder.toArray()); + + systemCallOrder.clear(); + engine.update(1); + expected = Arrays.asList(1, 5, 10); //second update ok + Assert.assertArrayEquals(expected.toArray(), systemCallOrder.toArray()); + } + private class NamedSystem extends EntitySystem { + + public NamedSystem(int priority) { + super(priority); + } + + @Override + public void update(float deltaTime) { + addSystemUpdateCall(this); + } + } +} diff --git a/ashley/tests/com/badlogic/ashley/core/SystemManagerTests.java b/ashley/tests/com/badlogic/ashley/core/SystemManagerTests.java index dd77cfa..d362220 100644 --- a/ashley/tests/com/badlogic/ashley/core/SystemManagerTests.java +++ b/ashley/tests/com/badlogic/ashley/core/SystemManagerTests.java @@ -94,25 +94,25 @@ public void addAndRemoveSystem () { assertNull(manager.getSystem(EntitySystemMockA.class)); assertNull(manager.getSystem(EntitySystemMockB.class)); - manager.addSystem(systemA); - manager.addSystem(systemB); + manager.addSystem(systemA, false); + manager.addSystem(systemB, false); assertNotNull(manager.getSystem(EntitySystemMockA.class)); assertNotNull(manager.getSystem(EntitySystemMockB.class)); assertEquals(1, systemA.addedCalls); assertEquals(1, systemB.addedCalls); - manager.removeSystem(systemA); - manager.removeSystem(systemB); + manager.removeSystem(systemA, false); + manager.removeSystem(systemB, false); assertNull(manager.getSystem(EntitySystemMockA.class)); assertNull(manager.getSystem(EntitySystemMockB.class)); assertEquals(1, systemA.removedCalls); assertEquals(1, systemB.removedCalls); - manager.addSystem(systemA); - manager.addSystem(systemB); - manager.removeAllSystems(); + manager.addSystem(systemA, false); + manager.addSystem(systemB, false); + manager.removeAllSystems(false); assertNull(manager.getSystem(EntitySystemMockA.class)); assertNull(manager.getSystem(EntitySystemMockB.class)); @@ -129,14 +129,14 @@ public void getSystems () { assertEquals(0, manager.getSystems().size()); - manager.addSystem(systemA); - manager.addSystem(systemB); + manager.addSystem(systemA, false); + manager.addSystem(systemB, false); assertEquals(2, manager.getSystems().size()); assertEquals(2, systemSpy.addedCount); - manager.removeSystem(systemA); - manager.removeSystem(systemB); + manager.removeSystem(systemA, false); + manager.removeSystem(systemB, false); assertEquals(0, manager.getSystems().size()); assertEquals(2, systemSpy.addedCount); @@ -152,13 +152,13 @@ public void addTwoSystemsOfSameClass () { assertEquals(0, manager.getSystems().size()); - manager.addSystem(system1); + manager.addSystem(system1, false); assertEquals(1, manager.getSystems().size()); assertEquals(system1, manager.getSystem(EntitySystemMockA.class)); assertEquals(1, systemSpy.addedCount); - manager.addSystem(system2); + manager.addSystem(system2, false); assertEquals(1, manager.getSystems().size()); assertEquals(system2, manager.getSystem(EntitySystemMockA.class)); @@ -178,8 +178,8 @@ public void systemUpdateOrder () { system1.priority = 2; system2.priority = 1; - manager.addSystem(system1); - manager.addSystem(system2); + manager.addSystem(system1, false); + manager.addSystem(system2, false); ImmutableArray systems = manager.getSystems(); assertEquals(system2, systems.get(0));