diff --git a/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentArray.java b/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentArray.java deleted file mode 100644 index 710b80c..0000000 --- a/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentArray.java +++ /dev/null @@ -1,121 +0,0 @@ -package nz.ac.massey.javaecs; -/** - * Component Array - * Defines the data structure that component data is stored under. - * Has a list of defined objects, and two associative maps that link - * the position of the data with the entity number. - * - * Therefore, every entity in entityComponentDataMap is valid, so - * no additional sorting is required - * - * - * Contributors: - * Brychan Dempsey - brychand@hotmail.com - * - */ - -import java.util.Map; -import java.util.HashMap; -import java.util.List; -import java.util.ArrayList; - -/** - * Stores component data in a packed interal list, and provides methods for accessing this data - */ -class ComponentArray{ - // The object data array - private List componentArray; - // The mappings between data and entity - private Map entityComponentDataMap; - private Map componentDataEntityMap; - - public ComponentArray(int initialSize){ - componentArray = new ArrayList<>(initialSize); - entityComponentDataMap = new HashMap<>(initialSize); - componentDataEntityMap = new HashMap<>(initialSize); - } - /** - * Gets the data Object associated with the entity. - * @param entity the entity to find data for - * @return the Object if the data exists, else null - */ - protected Object getData(Entity entity){ - try{ - return componentArray.get(entityComponentDataMap.get(entity)); - } - catch (NullPointerException ex){ - Engine.writeErr("Attempted to retrieve non-existent data for unassigned entity: " + entity.getValue()); - return null; - } - catch (IndexOutOfBoundsException e){ - Engine.writeErr("Index out-of-bounds for entity: " + entity.getValue()); - return null; - } - } - - /** - * Inserts the provided component and associates it with the entity. - * @param entity the entity to associate data to - * @param component the component data - * @return true if successful, false if the entity is already subscribed to the component - */ - protected boolean insertData(Entity entity, Object component){ - if (entityComponentDataMap.containsKey(entity)){ - Engine.writeErr("Entity is already subscribed to the component"); - return false; - } - // Put data at the end of the componentArray - int index = componentArray.size(); - entityComponentDataMap.put(entity, index); - componentDataEntityMap.put(index, entity); - componentArray.add(component); - return true; - } - - /** - * Moves component data to another entity. (Copies, deletes and inserts data) - * @param sourceEntity - * @param destinationEntity - * @return 0 if successful, -1 if the object is null, -2 if a NullPointerException occurred, and -3 if inserting data failed - */ - protected int moveData(Entity sourceEntity, Entity destinationEntity){ - try{ - Object data = entityComponentDataMap.get(sourceEntity); - if (data == null) return -1; - else if (insertData(destinationEntity, data)) - { - removeData(sourceEntity); - return 0; - } - else return -3; - } - catch (NullPointerException e){ - return -2; - } - } - - /** - * Removes the data associated with the entity. - * @param entity the entity to remove the data from - * @return true if the data was removed. If the data isn't found then returns false - */ - protected boolean removeData(Entity entity){ - if (!entityComponentDataMap.containsKey(entity)){ - Engine.writeErr("Attempted to remove non-existent entity: " + entity.getValue()); - return false; - } - // Get the componentData index of the entity - int removedComponentDataIndex = entityComponentDataMap.get(entity); - // Replace the removed component with the last component in the array - componentArray.set(removedComponentDataIndex, componentArray.get(componentArray.size() -1)); - // update the data positions in the map - Entity lastEntity = componentDataEntityMap.get(componentArray.size()-1); - entityComponentDataMap.replace(lastEntity, removedComponentDataIndex); - componentDataEntityMap.replace(removedComponentDataIndex, lastEntity); - // Finally, remomve the last elements - entityComponentDataMap.remove(entity); - componentDataEntityMap.remove(componentArray.size() -1); - componentArray.remove(componentArray.size() -1); - return true; - } -} \ No newline at end of file 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 fb56e88..169e4a7 100644 --- a/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentManager.java +++ b/javaecs/src/main/java/nz/ac/massey/javaecs/ComponentManager.java @@ -17,9 +17,11 @@ import java.util.HashMap; * Manages the addition, sorting and retrieving of components and component data */ class ComponentManager{ - private Map componentArrays = new HashMap<>(); - private Map componentPosIndex = new HashMap<>(); + private Map> componentArrays = new HashMap<>(); + // Need to be able to map bit indices and component types private Map indexComponentType = new HashMap<>(); + private Map componentTypeIndex = new HashMap<>(); + // /** * Adds the specified component to the provided entity. @@ -30,7 +32,8 @@ class ComponentManager{ * @param entity the entity to associate data to */ protected boolean addComponentToEntity(Type componentType, Object componentData, Entity entity){ - return componentArrays.get(componentType).insertData(entity, componentData); + componentArrays.get(componentType).put(entity, componentData); + return true; } /** @@ -41,9 +44,7 @@ class ComponentManager{ // 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)){ - Engine.writeErr(indexComponentType.get(index).getTypeName()); - } + componentArrays.get(indexComponentType.get(index)).remove(entity); index = entityRegistrations.nextSetBit(index+1); } } @@ -55,7 +56,7 @@ class ComponentManager{ * @return the Object data found, or null if it was not found */ public Object getComponent(Type componentType, Entity entity){ - return componentArrays.get(componentType).getData(entity); + return componentArrays.get(componentType).get(entity); } /** @@ -65,7 +66,7 @@ class ComponentManager{ */ public Integer getComponentIndex(Type type){ try{ - return componentPosIndex.get(type); + return componentTypeIndex.get(type); } catch (NullPointerException e){ return -1; @@ -94,10 +95,10 @@ class ComponentManager{ * @return true if the component was moved successfully, else false */ public boolean moveComponentData(Entity sourceEntity, Entity destinationEntity, Type component){ - if (componentArrays.get(component).moveData(sourceEntity, destinationEntity) == 0){ - return true; - } - else return false; + Object data = componentArrays.get(component).get(sourceEntity); + componentArrays.get(component).put(destinationEntity, data); + componentArrays.get(component).remove(sourceEntity); + return true; } /** @@ -109,9 +110,8 @@ class ComponentManager{ public void moveAllComponentData(Entity sourceEntity, Entity destinationEntity, BitSet sourceRegistrations){ int result = sourceRegistrations.nextSetBit(0); while (result != -1){ - Type key = indexComponentType.get(result); - componentArrays.get(key).moveData(sourceEntity, destinationEntity); - result = sourceRegistrations.nextSetBit(result+1); + moveComponentData(sourceEntity, destinationEntity, indexComponentType.get(result)); + result = sourceRegistrations.nextSetBit(result +1); } } @@ -135,9 +135,9 @@ class ComponentManager{ Engine.writeErr("Component " + type.getTypeName() + " is already registered"); return false; } - componentArrays.put(type, new ComponentArray(arraySize)); - indexComponentType.put(componentPosIndex.size(), type); - componentPosIndex.put(type, componentPosIndex.size()); + componentArrays.put(type, new HashMap<>()); + indexComponentType.put(componentTypeIndex.size(), type); + componentTypeIndex.put(type, componentTypeIndex.size()); return true; } @@ -149,6 +149,7 @@ class ComponentManager{ * @param entity the entity to remove the component from */ public boolean removeComponentFromEntity(Type componentType, Entity entity){ - return componentArrays.get(componentType).removeData(entity); + componentArrays.get(componentType).remove(entity); + return true; } } \ No newline at end of file diff --git a/javaecs/src/main/java/nz/ac/massey/javaecs/ECSSystem.java b/javaecs/src/main/java/nz/ac/massey/javaecs/ECSSystem.java index 773ef00..29e0e7f 100644 --- a/javaecs/src/main/java/nz/ac/massey/javaecs/ECSSystem.java +++ b/javaecs/src/main/java/nz/ac/massey/javaecs/ECSSystem.java @@ -16,24 +16,45 @@ import java.util.Set; import java.util.BitSet; import java.util.HashSet; +/** + * Abstract class that all systems should inherit from

+ * Defines four required components: + *

    + *
  1. The list of entities that have all the components required for this system
  2. + *
  3. The BitSet of component registrations required for this system
  4. + *
  5. The init() function, where logic that needs to be run once is performed
  6. + *
  7. The update(dt) function, where logic that needs to be run regularly is performed. dt is the delta time in millseconds
  8. + *
+ * Additionally, the object constructor should define the actual values of the BitSet.

+ * Additional functions can be implemented as required. + */ public abstract class ECSSystem{ protected Set entities = new HashSet<>(); protected BitSet registrationSet; + public ECSSystem(){ + registrationSet = new BitSet(); + } + public BitSet getRegistrationSet(){ return registrationSet; } /** * Functionality that should be run only once. - * Implement additional parameterised init() functions as required. + * Implement additional parameterised init() functions as required. + *

+ * This is distinct from the constructor as this may be run as required any time between + * the construction and update(dt). It is not intended to set the actual bits of registrationSet + * in this function; that should be performed in the constructor. + * @throws Exception can return exceptions to the main program if an issue occurred. */ - public abstract void init(); + public abstract void init() throws Exception; /** * Functionality that is expected to be called regularly * Intended as a template only; may be superficially implemented. - * Implement additional parameterised update() functions as required. + * Implement additional parameterised update() functions as required. * @param dt delta-time; the change in time in milliseconds since this function was last run */ public abstract void update(double dt); diff --git a/javaecs/src/main/java/nz/ac/massey/javaecs/Entity.java b/javaecs/src/main/java/nz/ac/massey/javaecs/Entity.java index d77f48c..e2439a2 100644 --- a/javaecs/src/main/java/nz/ac/massey/javaecs/Entity.java +++ b/javaecs/src/main/java/nz/ac/massey/javaecs/Entity.java @@ -5,7 +5,7 @@ import java.lang.reflect.Type; /** * Entity class. * Whilst an entity is just an Integer, and using Integer values - * would be more performant (no function call, primitive arrays & etc.), it is encapsulated to help + * would be more performant (no function call, primitive arrays and etc.), it is encapsulated to help * distinguish between entities and Integers. Also, in this form it may be extended to also include things * like names, grouping, specific ordering etc */ @@ -56,12 +56,11 @@ public class Entity { /** * Adds the provided component to this entity. *

- * This function calls addComponent() in the engine, so + * This function calls Engine.addComponent(Entity, Type, Object), so * that should be used instead. * @deprecated This function is not ECS-like. * It is provided as an auxilliary method, that will be more * intuitive to those familiar with OO design - * @see Engine.addComponent() - which this function makes a call to * @param componentType the class type of the component to add * @param componentData the component data * @return true if successful @@ -74,12 +73,11 @@ public class Entity { /** * Removes the provided component from this entity. *

- * This function calls removeComponent() in the engine, so + * This function calls Engine.removeComponent(Entity, Type), so * that should be used instead. * @deprecated This function is not ECS-like. * It is provided as an auxilliary method, that will be more * intuitive to those familiar with OO design - * @see Engine.removeComponent() - which this function makes a call to * @param componentType the class type of the component to remove * @return true if successful */ @@ -91,12 +89,11 @@ public class Entity { /** * Gets the component data associated to this entity. *

- * This function calls getComponentData() in the engine, so + * This function calls Engine.getComponentData(), so * that should be used instead. * @deprecated This function is not ECS-like. * It is provided as an auxilliary method, that will be more * intuitive to those familiar with OO design - * @see Engine.getComponentData() - which this function makes a call to * @param componentType the class type of the component to fetch data from * @return the data Object, which requires casting */