Compare commits

..

2 Commits

Author SHA1 Message Date
e578e72b46 Version change to 1.0.0 2021-06-13 14:06:26 +12:00
84d71c0df7 Restructured component data
Removed the now superfluous ComponentArray
Flattened the Type->Map<Entity, Object> array
2021-06-13 13:45:30 +12:00
6 changed files with 70 additions and 151 deletions

View File

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>nz.ac.massey.javaecs</groupId> <groupId>nz.ac.massey.javaecs</groupId>
<artifactId>javaecs</artifactId> <artifactId>javaecs</artifactId>
<version>0.9.9-RELEASE_CANDIDATE</version> <version>1.0.0</version>
<properties> <properties>
<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>

View File

@ -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<Object> componentArray;
// The mappings between data and entity
private Map<Entity, Integer> entityComponentDataMap;
private Map<Integer, Entity> 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;
}
}

View File

@ -17,9 +17,11 @@ import java.util.HashMap;
* Manages the addition, sorting and retrieving of components and component data * Manages the addition, sorting and retrieving of components and component data
*/ */
class ComponentManager{ class ComponentManager{
private Map<Type, ComponentArray> componentArrays = new HashMap<>(); private Map<Type, Map<Entity, Object>> componentArrays = new HashMap<>();
private Map<Type, Integer> componentPosIndex = new HashMap<>(); // Need to be able to map bit indices and component types
private Map<Integer, Type> indexComponentType = new HashMap<>(); private Map<Integer, Type> indexComponentType = new HashMap<>();
private Map<Type, Integer> componentTypeIndex = new HashMap<>();
//
/** /**
* Adds the specified component to the provided entity. * Adds the specified component to the provided entity.
@ -30,7 +32,8 @@ class ComponentManager{
* @param entity the entity to associate data to * @param entity the entity to associate data to
*/ */
protected boolean addComponentToEntity(Type componentType, Object componentData, Entity entity){ 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 // HashMap lookups take time, use the known bitstates to avoid
int index = entityRegistrations.nextSetBit(0); int index = entityRegistrations.nextSetBit(0);
while(index != -1){ while(index != -1){
if (!componentArrays.get(indexComponentType.get(index)).removeData(entity)){ componentArrays.get(indexComponentType.get(index)).remove(entity);
Engine.writeErr(indexComponentType.get(index).getTypeName());
}
index = entityRegistrations.nextSetBit(index+1); index = entityRegistrations.nextSetBit(index+1);
} }
} }
@ -55,7 +56,7 @@ class ComponentManager{
* @return the Object data found, or null if it was not found * @return the Object data found, or null if it was not found
*/ */
public Object getComponent(Type componentType, Entity entity){ 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){ public Integer getComponentIndex(Type type){
try{ try{
return componentPosIndex.get(type); return componentTypeIndex.get(type);
} }
catch (NullPointerException e){ catch (NullPointerException e){
return -1; return -1;
@ -94,11 +95,11 @@ class ComponentManager{
* @return true if the component was moved successfully, else false * @return true if the component was moved successfully, else false
*/ */
public boolean moveComponentData(Entity sourceEntity, Entity destinationEntity, Type component){ public boolean moveComponentData(Entity sourceEntity, Entity destinationEntity, Type component){
if (componentArrays.get(component).moveData(sourceEntity, destinationEntity) == 0){ Object data = componentArrays.get(component).get(sourceEntity);
componentArrays.get(component).put(destinationEntity, data);
componentArrays.get(component).remove(sourceEntity);
return true; return true;
} }
else return false;
}
/** /**
* Moves all component data from one entity to another * Moves all component data from one entity to another
@ -109,9 +110,8 @@ class ComponentManager{
public void moveAllComponentData(Entity sourceEntity, Entity destinationEntity, BitSet sourceRegistrations){ public void moveAllComponentData(Entity sourceEntity, Entity destinationEntity, BitSet sourceRegistrations){
int result = sourceRegistrations.nextSetBit(0); int result = sourceRegistrations.nextSetBit(0);
while (result != -1){ while (result != -1){
Type key = indexComponentType.get(result); moveComponentData(sourceEntity, destinationEntity, indexComponentType.get(result));
componentArrays.get(key).moveData(sourceEntity, destinationEntity); result = sourceRegistrations.nextSetBit(result +1);
result = sourceRegistrations.nextSetBit(result+1);
} }
} }
@ -135,9 +135,9 @@ class ComponentManager{
Engine.writeErr("Component " + type.getTypeName() + " is already registered"); Engine.writeErr("Component " + type.getTypeName() + " is already registered");
return false; return false;
} }
componentArrays.put(type, new ComponentArray(arraySize)); componentArrays.put(type, new HashMap<>());
indexComponentType.put(componentPosIndex.size(), type); indexComponentType.put(componentTypeIndex.size(), type);
componentPosIndex.put(type, componentPosIndex.size()); componentTypeIndex.put(type, componentTypeIndex.size());
return true; return true;
} }
@ -149,6 +149,18 @@ class ComponentManager{
* @param entity the entity to remove the component from * @param entity the entity to remove the component from
*/ */
public boolean removeComponentFromEntity(Type componentType, Entity entity){ public boolean removeComponentFromEntity(Type componentType, Entity entity){
return componentArrays.get(componentType).removeData(entity); componentArrays.get(componentType).remove(entity);
return true;
}
/**
* Checks if the entity contains data for the provided component
* @param entity the entity to check
* @param componentType the component class type
* @return true if the entity has component data
*/
public boolean entityHasComponentData(Entity entity, Type componentType){
if (componentArrays.get(componentType).containsKey(entity)) return true;
else return false;
} }
} }

View File

@ -16,24 +16,45 @@ import java.util.Set;
import java.util.BitSet; import java.util.BitSet;
import java.util.HashSet; import java.util.HashSet;
/**
* Abstract class that all systems should inherit from<p>
* Defines four required components:
* <ol>
* <li>The list of <b>entities</b> that have all the components required for this system</li>
* <li>The <b>BitSet</b> of component registrations required for this system</li>
* <li>The <b>init()</b> function, where logic that needs to be run once is performed</li>
* <li>The <b>update(dt)</b> function, where logic that needs to be run regularly is performed. <i>dt is the delta time in millseconds</i></li>
* </ol>
* Additionally, the object constructor should define the actual values of the BitSet.<p>
* Additional functions can be implemented as required.
*/
public abstract class ECSSystem{ public abstract class ECSSystem{
protected Set<Entity> entities = new HashSet<>(); protected Set<Entity> entities = new HashSet<>();
protected BitSet registrationSet; protected BitSet registrationSet;
public ECSSystem(){
registrationSet = new BitSet();
}
public BitSet getRegistrationSet(){ public BitSet getRegistrationSet(){
return registrationSet; return registrationSet;
} }
/** /**
* Functionality that should be run only once. * Functionality that should be run only once.
* Implement additional parameterised init() functions as required. * Implement additional parameterised <b>init()</b> functions as required.
* <p>
* This is distinct from the constructor as this may be run as required any time between
* the construction and <b>update(dt)</b>. It is not intended to set the actual bits of <b>registrationSet</b>
* 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 * Functionality that is expected to be called regularly
* Intended as a template only; may be superficially implemented. * Intended as a template only; may be superficially implemented.
* Implement additional parameterised update() functions as required. * Implement additional parameterised <b>update()</b> functions as required.
* @param dt delta-time; the change in time in milliseconds since this function was last run * @param dt delta-time; the change in time in milliseconds since this function was last run
*/ */
public abstract void update(double dt); public abstract void update(double dt);

View File

@ -116,6 +116,16 @@ public class Engine {
return entityManager.getNumEntities(); return entityManager.getNumEntities();
} }
/**
* Checks if the entity is subscribed to the provided type
* @param entity the entity to search
* @param componentType the class type of the component to search
* @return true if the entity is subscribed
*/
public boolean entityHasComponent(Entity entity, Type componentType){
return entityManager.getRegistrations(entity).get(componentManager.getComponentIndex(componentType));
}
/*************************************** /***************************************
** Manage Components ** ** Manage Components **
***************************************/ ***************************************/

View File

@ -5,7 +5,7 @@ import java.lang.reflect.Type;
/** /**
* Entity class. * Entity class.
* Whilst an entity is just an Integer, and using Integer values * 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 * distinguish between entities and Integers. Also, in this form it may be extended to also include things
* like names, grouping, specific ordering etc * like names, grouping, specific ordering etc
*/ */
@ -56,12 +56,11 @@ public class Entity {
/** /**
* Adds the provided component to this entity. * Adds the provided component to this entity.
* <p> * <p>
* <b>This function calls addComponent() in the engine,</b> so * <b>This function calls <i>Engine.addComponent(Entity, Type, Object)</i></b>, so
* that should be used instead. * that should be used instead.
* @deprecated This function is not ECS-like. * @deprecated This function is not ECS-like.
* It is provided as an auxilliary method, that will be more * It is provided as an auxilliary method, that will be more
* intuitive to those familiar with OO design * 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 componentType the class type of the component to add
* @param componentData the component data * @param componentData the component data
* @return true if successful * @return true if successful
@ -74,12 +73,11 @@ public class Entity {
/** /**
* Removes the provided component from this entity. * Removes the provided component from this entity.
* <p> * <p>
* <b>This function calls removeComponent() in the engine,</b> so * <b>This function calls <i>Engine.removeComponent(Entity, Type)</i></b>, so
* that should be used instead. * that should be used instead.
* @deprecated This function is not ECS-like. * @deprecated This function is not ECS-like.
* It is provided as an auxilliary method, that will be more * It is provided as an auxilliary method, that will be more
* intuitive to those familiar with OO design * 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 * @param componentType the class type of the component to remove
* @return true if successful * @return true if successful
*/ */
@ -91,12 +89,11 @@ public class Entity {
/** /**
* Gets the component data associated to this entity. * Gets the component data associated to this entity.
* <p> * <p>
* <b>This function calls getComponentData() in the engine,</b> so * <b>This function calls <i>Engine.getComponentData()</i></b>, so
* that should be used instead. * that should be used instead.
* @deprecated This function is not ECS-like. * @deprecated This function is not ECS-like.
* It is provided as an auxilliary method, that will be more * It is provided as an auxilliary method, that will be more
* intuitive to those familiar with OO design * 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 * @param componentType the class type of the component to fetch data from
* @return the data Object, which requires casting * @return the data Object, which requires casting
*/ */