diff --git a/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentArray.java b/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentArray.java index 0dd13f1..f68dae8 100644 --- a/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentArray.java +++ b/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentArray.java @@ -16,6 +16,7 @@ package nz.ac.massey.javaecs; import java.util.Map; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.ArrayList; diff --git a/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentManager.java b/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentManager.java index 4c6bd98..c501cfa 100644 --- a/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentManager.java +++ b/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentManager.java @@ -27,11 +27,9 @@ class ComponentManager{ * @param entity the entity to associate data to */ protected boolean addComponentToEntity(Type componentType, Object componentData, Entity entity){ - if (componentData == null){ - // In the case of null component data, insert the boolean false - // (preserves structure by associating data with a null component) + /*if (componentData == null){ return componentArrays.get(componentType).insertData(entity, false); - } + } */ return componentArrays.get(componentType).insertData(entity, componentData); } @@ -40,7 +38,7 @@ class ComponentManager{ * @param entity the entity that was destroyed. */ public void entityDestroyed(Entity entity, BitSet entityRegistrations){ - // HashMap lookups take time, use the known bitstates + // HashMap lookups take time, use the known bitstates to avoid int index = entityRegistrations.nextSetBit(0); while(index != -1){ if (!componentArrays.get(indexComponentType.get(index)).removeData(entity)){ @@ -48,11 +46,6 @@ class ComponentManager{ } index = entityRegistrations.nextSetBit(index+1); } - /*for (Type key : componentArrays.keySet()) { - if (!componentArrays.get(key).removeData(entity)){ - Engine.writeErr(key.getTypeName()); - } - }*/ } /** @@ -70,7 +63,7 @@ class ComponentManager{ * @param type the class type of the component * @return the index of the component type, or -1 if it isn't found */ - protected Integer getComponentIndex(Type type){ + public Integer getComponentIndex(Type type){ try{ return componentPosIndex.get(type); } @@ -84,7 +77,7 @@ class ComponentManager{ * @param index the index of the component * @return the class type of the index. `null` if not found */ - protected Type getComponentType(Integer index){ + public Type getComponentType(Integer index){ try{ return indexComponentType.get(index); } @@ -139,7 +132,9 @@ class ComponentManager{ } /** - * Removes the specified component from the entity + * Removes the specified component from the entity. + *
+ * Be sure to call SystemManager.entityRegistrationsChanged after calling this function * @param componentType the class type of the component to remove * @param entity the entity to remove the component from */ diff --git a/javaecs/src/main/java/nz/ac/massey/javaecs/Engine.java b/javaecs/src/main/java/nz/ac/massey/javaecs/Engine.java index 1c11b0b..0a7e5f4 100644 --- a/javaecs/src/main/java/nz/ac/massey/javaecs/Engine.java +++ b/javaecs/src/main/java/nz/ac/massey/javaecs/Engine.java @@ -8,13 +8,15 @@ package nz.ac.massey.javaecs; * * References: * Based on the implementation by Austin Morlan: - * https://code.austinmorlan.com/austin/ecs - 'A simple C++ Entity Component System' - released under MIT licence + * https://austinmorlan.com/posts/entity_component_system/ - 'A simple C++ Entity Component System' * */ import java.io.PrintStream; import java.lang.reflect.Type; import java.util.BitSet; +import java.util.NoSuchElementException; + /** * The ECS manager. *
@@ -29,11 +31,10 @@ public class Engine { protected EntityManager entityManager; protected ComponentManager componentManager; protected SystemManager systemManager; - /** - * Initialises the ECS with default values - *
- * Maximum 1024 enitites default - */ + + /*************************************** + ** Engine Constructors ** + ***************************************/ public Engine(){ entityManager = new EntityManager(); componentManager = new ComponentManager(); @@ -50,18 +51,6 @@ public class Engine { systemManager = new SystemManager(); } - /** - * /** - * Creates a new entity - * @return the index of the new entity - * @throws IndexOutOfBoundsException if there are no more entities available - */ - public Entity createEntity() throws IndexOutOfBoundsException{ - Entity newEntity = entityManager.addEntity(); - if (newEntity == null) throw new IndexOutOfBoundsException("Could not create a new entity"); - return newEntity; - } - /** * Attempts to resize the maximum number of entities * @param newSize the new maximum number of entities @@ -71,16 +60,60 @@ public class Engine { return entityManager.resize(newSize, systemManager, componentManager); } + + /*************************************** + ** Manage Entities ** + ***************************************/ + + /** + * Create a new Entity (dequeues the first element from unusedEntities) + * @return the next unused Entity, now set to used + * @throws NoSuchElementException if there are no more entities available + */ + public Entity createEntity() throws NoSuchElementException{ + try{ + Entity newEntity = entityManager.addEntity(); + return newEntity; + } + catch (NoSuchElementException e){ + // Catch the exception to log the error message + writeErr("No more available entities"); + // Throw again to pass up to the original calling function + throw e; + } + } + /** * Signals each manager to remove the specified entity * @param entity the entity to destroy */ public void destroyEntity(Entity entity){ + // Pass the registration list to the componentManager first, to avoid having to iterate through each component type componentManager.entityDestroyed(entity, entityManager.getRegistrations(entity)); entityManager.removeEntity(entity); systemManager.entityDestroyed(entity); } + /** + * Gets the set maximum number of entities + * @return an integer of the maximum number of entities + */ + public Integer getMaxEntities(){ + return entityManager.getMaxSize(); + } + + /** + * Gets the current number of entities + * @return the number of currently active entities + */ + public int getNumEntities(){ + return entityManager.getNumEntities(); + } + + /*************************************** + ** Manage Components ** + ***************************************/ + /** * Registers the specified name in the component manager * @param type the name to register. Should be the component class name or a suitable name for primitive types @@ -90,10 +123,6 @@ public class Engine { return componentManager.registerComponent(type); } - public Integer getComponentIndex(Type type){ - return componentManager.getComponentIndex(type); - } - /** * Adds an exisiting component to an exisiting entity * @param entity the entity to add the component to @@ -102,8 +131,11 @@ public class Engine { * @return true if the compnent was added to the entity */ public boolean addComponent(Entity entity, Type componentType, Object component){ - if (entityManager.registerComponent(componentManager.getComponentIndex(componentType), entity)) + // Get the old registrations of the entity so that we can update only the relevant systems + int componentIndex = componentManager.getComponentIndex(componentType); + if (entityManager.registerComponent(componentIndex, entity)) { + //systemManager.entityRegistrationAdded(entity, componentIndex); systemManager.entityRegistrationsChanged(entity, entityManager.getRegistrations(entity)); componentManager.addComponentToEntity(componentType, component, entity); return true; @@ -124,7 +156,7 @@ public class Engine { if (entityManager.unregisterComponent(componentManager.getComponentIndex(componentType), entity)) { componentManager.removeComponentFromEntity(componentType, entity); - systemManager.entityRegistrationsChanged(entity, entityManager.getRegistrations(entity)); + systemManager.entityRegistrationsChanged(entity, entityManager.getRegistrations(entity)); // entityRegistrationRemoved(entity, componentManager.getComponentIndex(componentType)); return true; } else return false; @@ -132,7 +164,7 @@ public class Engine { /** * Gets the actual data of the component associated to the entity. - * May require casting from Object to the known data type + * Requires casting from Object to the known data type * @param entity the entity to retrieve the data for * @param componentType the class type of the component * @return the component data Object associated with the entity @@ -142,14 +174,28 @@ public class Engine { } /** - * Registers the system to - * @param systemType - * @param action + * Gets the component index of the provided type + * @param type the type to get the index of + * @return the index of the component */ - public boolean registerSystem(Type systemType, ECSSystem action){ - return systemManager.registerSystem(systemType, action); + public Integer getComponentIndex(Type type){ + return componentManager.getComponentIndex(type); } + + /**************************************** + ** Manage Systems ** + ****************************************/ + + /** + * Registers the system to the SystemManager + * @param systemType the type of the system + * @param instance the instance of the system + */ + public boolean registerSystem(Type systemType, ECSSystem instance){ + return systemManager.registerSystem(systemType, instance); + } + /** * Sets the specified system's signature to the provided signature * @param system the class name of the system to set the signature of @@ -158,16 +204,16 @@ public class Engine { public void setSystemSignature(Type system, BitSet signature){ systemManager.setRegistrationSignature(system, signature); } - public Integer getMaxEntities(){ - return entityManager.getMaxSize(); - } - // Encapsulate syserr writes so they may be redirected out of the lib + + /**************************************** + ** Helper Functions & Accessors ** + ****************************************/ /** * Writes an error message to the set PrintStream *
- * Default System.err + * Default is System.err * @param message the message to write */ static void writeErr(String message){ @@ -200,19 +246,27 @@ public class Engine { return errorStream; } + /** + * Gets a the current component manager + * @return the current active component manager + */ public ComponentManager getComponentManager() { return componentManager; } + /** + * Gets a the current enitity manager + * @return the current active enitity manager + */ public EntityManager getEntityManager() { return entityManager; } + /** + * Gets a the current systems manager + * @return the current active systems manager + */ public SystemManager getSystemManager() { return systemManager; } - - public int getNumEntities(){ - return entityManager.getNumEntities(); - } } \ No newline at end of file diff --git a/javaecs/src/main/java/nz/ac/massey/javaecs/EntityManager.java b/javaecs/src/main/java/nz/ac/massey/javaecs/EntityManager.java index 9e5fdfc..4923578 100644 --- a/javaecs/src/main/java/nz/ac/massey/javaecs/EntityManager.java +++ b/javaecs/src/main/java/nz/ac/massey/javaecs/EntityManager.java @@ -11,6 +11,7 @@ package nz.ac.massey.javaecs; import java.util.BitSet; import java.util.LinkedList; import java.util.List; +import java.util.NoSuchElementException; import java.util.Queue; import java.util.ArrayList; @@ -55,13 +56,10 @@ class EntityManager{ /** * Creates a new entity - * @return the index of the new entity, or -1 if there is no more available entities + * @return the index of the new entity + * @throws NoSuchElementException an exception if there are no more unused entities */ - protected Entity addEntity(){ - if (unusedEntities.size() == 0){ - Engine.writeErr("No available space to create a new entity"); - return null; - } + protected Entity addEntity() throws NoSuchElementException{ Entity result = unusedEntities.remove(); entityRegistrations.set(result.getValue(), new BitSet()); return result; diff --git a/javaecs/src/main/java/nz/ac/massey/javaecs/SystemManager.java b/javaecs/src/main/java/nz/ac/massey/javaecs/SystemManager.java index 7b919ba..9b794e4 100644 --- a/javaecs/src/main/java/nz/ac/massey/javaecs/SystemManager.java +++ b/javaecs/src/main/java/nz/ac/massey/javaecs/SystemManager.java @@ -24,7 +24,7 @@ class SystemManager{ * @param entity the destroyed entity */ protected void entityDestroyed(Entity entity){ - // Unlike components, this isn't simply indexed; makes more sense just to search the systems + // Unlike components, this isn't simply indexed. for (Type key : systems.keySet()) { systems.get(key).entities.remove(entity); } @@ -32,21 +32,24 @@ class SystemManager{ /** * Signals the SystemManager that an entity had its registrations changed, so - * evaluate if the entity is still relevant to each system + * evaluate if the entity is still relevant to each system. This is not lightweight, + * use entityRegistrationAdded and entityRegistrationRemoved if adding/removing a + * single component * @param entity the entity that was modified * @param entityRegistrations the new registrations of the entity */ - protected void entityRegistrationsChanged(Entity entity, BitSet entityRegistrations){ + public void entityRegistrationsChanged(Entity entity, BitSet entityRegistrations){ for (Type key : systems.keySet()) { // Check if the signature is null if (!entityRegistrations.equals(null)){ BitSet srcCpy = (BitSet)entityRegistrations.clone(); - srcCpy.and(systems.get(key).registrationSet); - if (srcCpy.equals(systems.get(key).registrationSet)){ // Bitwise check if the entity is subscribed to this system - systems.get(key).entities.add(entity); + ECSSystem sys = systems.get(key); + srcCpy.and(sys.registrationSet); + if (srcCpy.equals(sys.registrationSet)){ // Bitwise check if the entity is subscribed to this system + sys.entities.add(entity); } else{ - systems.get(key).entities.remove(entity); + sys.entities.remove(entity); } } } diff --git a/javaecs/src/test/java/nz/ac/massey/javaecs/AppTest.java b/javaecs/src/test/java/nz/ac/massey/javaecs/AppTest.java index 19f7a8d..2df4f15 100644 --- a/javaecs/src/test/java/nz/ac/massey/javaecs/AppTest.java +++ b/javaecs/src/test/java/nz/ac/massey/javaecs/AppTest.java @@ -11,6 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.BitSet; +import java.util.NoSuchElementException; import org.junit.jupiter.api.BeforeEach; @@ -67,7 +68,7 @@ class AppTest { void testAssignMoreThanMax(){ for (int i = 0; i < gameEngine.getMaxEntities() + 5; i++) { if (i >= gameEngine.getMaxEntities()){ - assertThrows(IndexOutOfBoundsException.class, () -> { + assertThrows(NoSuchElementException.class, () -> { gameEngine.createEntity(); }); }