Cleaned and minor restructure

This commit is contained in:
Brychan Dempsey 2021-06-11 14:41:22 +12:00
parent 4f4e1bec7b
commit 529f99abbd
6 changed files with 118 additions and 66 deletions

View File

@ -16,6 +16,7 @@ package nz.ac.massey.javaecs;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -27,11 +27,9 @@ 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){
if (componentData == null){ /*if (componentData == null){
// In the case of null component data, insert the boolean false
// (preserves structure by associating data with a null component)
return componentArrays.get(componentType).insertData(entity, false); return componentArrays.get(componentType).insertData(entity, false);
} } */
return componentArrays.get(componentType).insertData(entity, componentData); return componentArrays.get(componentType).insertData(entity, componentData);
} }
@ -40,7 +38,7 @@ class ComponentManager{
* @param entity the entity that was destroyed. * @param entity the entity that was destroyed.
*/ */
public void entityDestroyed(Entity entity, BitSet entityRegistrations){ 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); int index = entityRegistrations.nextSetBit(0);
while(index != -1){ while(index != -1){
if (!componentArrays.get(indexComponentType.get(index)).removeData(entity)){ if (!componentArrays.get(indexComponentType.get(index)).removeData(entity)){
@ -48,11 +46,6 @@ class ComponentManager{
} }
index = entityRegistrations.nextSetBit(index+1); 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 * @param type the class type of the component
* @return the index of the component type, or -1 if it isn't found * @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{ try{
return componentPosIndex.get(type); return componentPosIndex.get(type);
} }
@ -84,7 +77,7 @@ class ComponentManager{
* @param index the index of the component * @param index the index of the component
* @return the class type of the index. `null` if not found * @return the class type of the index. `null` if not found
*/ */
protected Type getComponentType(Integer index){ public Type getComponentType(Integer index){
try{ try{
return indexComponentType.get(index); return indexComponentType.get(index);
} }
@ -139,7 +132,9 @@ class ComponentManager{
} }
/** /**
* Removes the specified component from the entity * Removes the specified component from the entity.
* <p>
* Be sure to call SystemManager.entityRegistrationsChanged after calling this function
* @param componentType the class type of the component to remove * @param componentType the class type of the component to remove
* @param entity the entity to remove the component from * @param entity the entity to remove the component from
*/ */

View File

@ -8,13 +8,15 @@ package nz.ac.massey.javaecs;
* *
* References: * References:
* Based on the implementation by Austin Morlan: * 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.io.PrintStream;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.BitSet; import java.util.BitSet;
import java.util.NoSuchElementException;
/** /**
* The ECS manager. * The ECS manager.
* <p> * <p>
@ -29,11 +31,10 @@ public class Engine {
protected EntityManager entityManager; protected EntityManager entityManager;
protected ComponentManager componentManager; protected ComponentManager componentManager;
protected SystemManager systemManager; protected SystemManager systemManager;
/**
* Initialises the ECS with default values /***************************************
* <p> ** Engine Constructors **
* Maximum 1024 enitites default ***************************************/
*/
public Engine(){ public Engine(){
entityManager = new EntityManager(); entityManager = new EntityManager();
componentManager = new ComponentManager(); componentManager = new ComponentManager();
@ -50,18 +51,6 @@ public class Engine {
systemManager = new SystemManager(); 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 * Attempts to resize the maximum number of entities
* @param newSize the new 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); 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 * Signals each manager to remove the specified entity
* @param entity the entity to destroy * @param entity the entity to destroy
*/ */
public void destroyEntity(Entity entity){ 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)); componentManager.entityDestroyed(entity, entityManager.getRegistrations(entity));
entityManager.removeEntity(entity); entityManager.removeEntity(entity);
systemManager.entityDestroyed(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 * 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 * @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); return componentManager.registerComponent(type);
} }
public Integer getComponentIndex(Type type){
return componentManager.getComponentIndex(type);
}
/** /**
* Adds an exisiting component to an exisiting entity * Adds an exisiting component to an exisiting entity
* @param entity the entity to add the component to * @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 * @return true if the compnent was added to the entity
*/ */
public boolean addComponent(Entity entity, Type componentType, Object component){ 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)); systemManager.entityRegistrationsChanged(entity, entityManager.getRegistrations(entity));
componentManager.addComponentToEntity(componentType, component, entity); componentManager.addComponentToEntity(componentType, component, entity);
return true; return true;
@ -124,7 +156,7 @@ public class Engine {
if (entityManager.unregisterComponent(componentManager.getComponentIndex(componentType), entity)) if (entityManager.unregisterComponent(componentManager.getComponentIndex(componentType), entity))
{ {
componentManager.removeComponentFromEntity(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; return true;
} }
else return false; else return false;
@ -132,7 +164,7 @@ public class Engine {
/** /**
* Gets the actual data of the component associated to the entity. * 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 entity the entity to retrieve the data for
* @param componentType the class type of the component * @param componentType the class type of the component
* @return the component data Object associated with the entity * @return the component data Object associated with the entity
@ -142,14 +174,28 @@ public class Engine {
} }
/** /**
* Registers the system to * Gets the component index of the provided type
* @param systemType * @param type the type to get the index of
* @param action * @return the index of the component
*/ */
public boolean registerSystem(Type systemType, ECSSystem action){ public Integer getComponentIndex(Type type){
return systemManager.registerSystem(systemType, action); 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 * Sets the specified system's signature to the provided signature
* @param system the class name of the system to set the signature of * @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){ public void setSystemSignature(Type system, BitSet signature){
systemManager.setRegistrationSignature(system, 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 * Writes an error message to the set PrintStream
* <p> * <p>
* <i>Default System.err * <i>Default is System.err
* @param message the message to write * @param message the message to write
*/ */
static void writeErr(String message){ static void writeErr(String message){
@ -200,19 +246,27 @@ public class Engine {
return errorStream; return errorStream;
} }
/**
* Gets a the current component manager
* @return the current active component manager
*/
public ComponentManager getComponentManager() { public ComponentManager getComponentManager() {
return componentManager; return componentManager;
} }
/**
* Gets a the current enitity manager
* @return the current active enitity manager
*/
public EntityManager getEntityManager() { public EntityManager getEntityManager() {
return entityManager; return entityManager;
} }
/**
* Gets a the current systems manager
* @return the current active systems manager
*/
public SystemManager getSystemManager() { public SystemManager getSystemManager() {
return systemManager; return systemManager;
} }
public int getNumEntities(){
return entityManager.getNumEntities();
}
} }

View File

@ -11,6 +11,7 @@ package nz.ac.massey.javaecs;
import java.util.BitSet; import java.util.BitSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.Queue; import java.util.Queue;
import java.util.ArrayList; import java.util.ArrayList;
@ -55,13 +56,10 @@ class EntityManager{
/** /**
* Creates a new entity * 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(){ protected Entity addEntity() throws NoSuchElementException{
if (unusedEntities.size() == 0){
Engine.writeErr("No available space to create a new entity");
return null;
}
Entity result = unusedEntities.remove(); Entity result = unusedEntities.remove();
entityRegistrations.set(result.getValue(), new BitSet()); entityRegistrations.set(result.getValue(), new BitSet());
return result; return result;

View File

@ -24,7 +24,7 @@ class SystemManager{
* @param entity the destroyed entity * @param entity the destroyed entity
*/ */
protected void entityDestroyed(Entity 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()) { for (Type key : systems.keySet()) {
systems.get(key).entities.remove(entity); systems.get(key).entities.remove(entity);
} }
@ -32,21 +32,24 @@ class SystemManager{
/** /**
* Signals the SystemManager that an entity had its registrations changed, so * 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 entity the entity that was modified
* @param entityRegistrations the new registrations of the entity * @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()) { for (Type key : systems.keySet()) {
// Check if the signature is null // Check if the signature is null
if (!entityRegistrations.equals(null)){ if (!entityRegistrations.equals(null)){
BitSet srcCpy = (BitSet)entityRegistrations.clone(); BitSet srcCpy = (BitSet)entityRegistrations.clone();
srcCpy.and(systems.get(key).registrationSet); ECSSystem sys = systems.get(key);
if (srcCpy.equals(systems.get(key).registrationSet)){ // Bitwise check if the entity is subscribed to this system srcCpy.and(sys.registrationSet);
systems.get(key).entities.add(entity); if (srcCpy.equals(sys.registrationSet)){ // Bitwise check if the entity is subscribed to this system
sys.entities.add(entity);
} }
else{ else{
systems.get(key).entities.remove(entity); sys.entities.remove(entity);
} }
} }
} }

View File

@ -11,6 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.BitSet; import java.util.BitSet;
import java.util.NoSuchElementException;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -67,7 +68,7 @@ class AppTest {
void testAssignMoreThanMax(){ void testAssignMoreThanMax(){
for (int i = 0; i < gameEngine.getMaxEntities() + 5; i++) { for (int i = 0; i < gameEngine.getMaxEntities() + 5; i++) {
if (i >= gameEngine.getMaxEntities()){ if (i >= gameEngine.getMaxEntities()){
assertThrows(IndexOutOfBoundsException.class, () -> { assertThrows(NoSuchElementException.class, () -> {
gameEngine.createEntity(); gameEngine.createEntity();
}); });
} }