Restructured component data
Removed the now superfluous ComponentArray Flattened the Type->Map<Entity, Object> array
This commit is contained in:
parent
051836852c
commit
84d71c0df7
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,7 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user