Fixed javadocs, cleaned many lines

Prepped for release at v0.9.9
Breaking changes for all implementations
This commit is contained in:
Brychan Dempsey 2021-06-12 00:51:35 +12:00
parent 999ffa41dd
commit 051836852c
9 changed files with 117 additions and 42 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.2-PRERELEASE</version> <version>0.9.9-RELEASE_CANDIDATE</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

@ -19,7 +19,9 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
/**
* Stores component data in a packed interal list, and provides methods for accessing this data
*/
class ComponentArray{ class ComponentArray{
// The object data array // The object data array
private List<Object> componentArray; private List<Object> componentArray;

View File

@ -13,6 +13,9 @@ import java.lang.reflect.Type;
import java.util.BitSet; import java.util.BitSet;
import java.util.HashMap; import java.util.HashMap;
/**
* 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, ComponentArray> componentArrays = new HashMap<>();
private Map<Type, Integer> componentPosIndex = new HashMap<>(); private Map<Type, Integer> componentPosIndex = new HashMap<>();
@ -27,9 +30,6 @@ 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){
return componentArrays.get(componentType).insertData(entity, false);
} */
return componentArrays.get(componentType).insertData(entity, componentData); return componentArrays.get(componentType).insertData(entity, componentData);
} }
@ -93,7 +93,7 @@ class ComponentManager{
* @param component the component class type to consider * @param component the component class type to consider
* @return true if the component was moved successfully, else false * @return true if the component was moved successfully, else false
*/ */
protected 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){ if (componentArrays.get(component).moveData(sourceEntity, destinationEntity) == 0){
return true; return true;
} }
@ -106,7 +106,7 @@ class ComponentManager{
* @param destinationEntity the entity to move data to * @param destinationEntity the entity to move data to
* @param sourceRegistrations the component registrations of the source entity * @param sourceRegistrations the component registrations of the source entity
*/ */
protected 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); Type key = indexComponentType.get(result);
@ -120,7 +120,7 @@ class ComponentManager{
* @param type the class type to register * @param type the class type to register
* @return true if the component was registered successfully, else false * @return true if the component was registered successfully, else false
*/ */
protected boolean registerComponent(Type type){ public boolean registerComponent(Type type){
return registerComponent(type, 16); return registerComponent(type, 16);
} }
@ -130,7 +130,7 @@ class ComponentManager{
* @param arraySize the number of elements to prereserve space for. * @param arraySize the number of elements to prereserve space for.
* @return true if the component was registered successfully, else false * @return true if the component was registered successfully, else false
*/ */
protected boolean registerComponent(Type type, int arraySize){ public boolean registerComponent(Type type, int arraySize){
if (componentArrays.containsKey(type)){ if (componentArrays.containsKey(type)){
Engine.writeErr("Component " + type.getTypeName() + " is already registered"); Engine.writeErr("Component " + type.getTypeName() + " is already registered");
return false; return false;

View File

@ -20,17 +20,21 @@ public abstract class ECSSystem{
protected Set<Entity> entities = new HashSet<>(); protected Set<Entity> entities = new HashSet<>();
protected BitSet registrationSet; protected BitSet registrationSet;
public BitSet getRegistrationSet; 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.
* These should run once, when the system is first initialised.
*/ */
public abstract void init(); public abstract void init();
/** /**
* 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.
* These should be run each game loop or otherwise sensible regular interval * @param dt delta-time; the change in time in milliseconds since this function was last run
*/ */
public abstract void update(); public abstract void update(double dt);
} }

View File

@ -1,7 +1,8 @@
package nz.ac.massey.javaecs; package nz.ac.massey.javaecs;
/** /**
* ECS manager class. * ECS engine class.
* Call this class and its functions to interact correctly with the ECS system. * Call this class and its functions to interact correctly with the ECS system.
* This is the entry-point to the library
* *
* Contributors: * Contributors:
* Brychan Dempsey - brychand@hotmail.com * Brychan Dempsey - brychand@hotmail.com
@ -18,7 +19,8 @@ import java.util.BitSet;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
/** /**
* The ECS manager. * The ECS management engine. Supplemented by the EntityManager, ComponentManager, and System Manager
* These four classes provide the actual functionality of the ecs
* <p> * <p>
* See https://git.software.kauripeak.co.nz/BrychanD/JavaECS * See https://git.software.kauripeak.co.nz/BrychanD/JavaECS
* for documentation and more information. * for documentation and more information.
@ -39,6 +41,9 @@ public class Engine {
entityManager = new EntityManager(); entityManager = new EntityManager();
componentManager = new ComponentManager(); componentManager = new ComponentManager();
systemManager = new SystemManager(); systemManager = new SystemManager();
// In a non-ECS type manner (instead OO-like), provide an additional method to
// add components to an entity: entity.addComponent().
Entity.engineRef = this;
} }
/** /**
@ -49,6 +54,7 @@ public class Engine {
entityManager = new EntityManager(maxEntities); entityManager = new EntityManager(maxEntities);
componentManager = new ComponentManager(); componentManager = new ComponentManager();
systemManager = new SystemManager(); systemManager = new SystemManager();
Entity.engineRef = this;
} }
/** /**
@ -156,7 +162,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)); // entityRegistrationRemoved(entity, componentManager.getComponentIndex(componentType)); systemManager.entityRegistrationsChanged(entity, entityManager.getRegistrations(entity));
return true; return true;
} }
else return false; else return false;
@ -191,6 +197,7 @@ public class Engine {
* Registers the system to the SystemManager * Registers the system to the SystemManager
* @param systemType the type of the system * @param systemType the type of the system
* @param instance the instance of the system * @param instance the instance of the system
* @return true if successful
*/ */
public boolean registerSystem(Type systemType, ECSSystem instance){ public boolean registerSystem(Type systemType, ECSSystem instance){
return systemManager.registerSystem(systemType, instance); return systemManager.registerSystem(systemType, instance);
@ -202,7 +209,7 @@ public class Engine {
* @param signature the new signature data * @param signature the new signature data
*/ */
public void setSystemSignature(Type system, BitSet signature){ public void setSystemSignature(Type system, BitSet signature){
systemManager.setRegistrationSignature(system, signature); systemManager.setSystemRegistraions(system, signature);
} }

View File

@ -1,11 +1,17 @@
package nz.ac.massey.javaecs; package nz.ac.massey.javaecs;
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), it is encapsulated to help * would be more performant (no function call, primitive arrays & etc.), it is encapsulated to help
* distinguish between entities and Integers. * distinguish between entities and Integers. Also, in this form it may be extended to also include things
* like names, grouping, specific ordering etc
*/ */
public class Entity { public class Entity {
protected static Engine engineRef;
private int value; private int value;
public Entity(int value){ public Entity(int value){
@ -20,11 +26,11 @@ public class Entity {
* Returns the int value wrapped as an entity. * Returns the int value wrapped as an entity.
* Used to provide a distinction between creating a new entity, and * Used to provide a distinction between creating a new entity, and
* using an int value we assume is a valid entity. * using an int value we assume is a valid entity.
* * <p>
* Functionally, this is no different to creating a new entity. * Functionally, this is no different to creating a new entity.
* *
* @param value * @param value the integer value to read as an Entity
* @return * @return an Entity object representing the ID value
*/ */
public static Entity asEntity(int value){ public static Entity asEntity(int value){
return new Entity(value); return new Entity(value);
@ -44,6 +50,58 @@ public class Entity {
@Override @Override
public int hashCode(){ public int hashCode(){
return value; return value; // each entity is an id; the value is implicitly unique, so use it as the hash code
}
/**
* Adds the provided component to this entity.
* <p>
* <b>This function calls addComponent() in the engine,</b> 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
*/
@Deprecated
public boolean addComponent(Type componentType, Object componentData){
return engineRef.addComponent(this, componentType, componentData);
}
/**
* Removes the provided component from this entity.
* <p>
* <b>This function calls removeComponent() in the engine,</b> 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
*/
@Deprecated
public boolean removeComponent(Type componentType){
return engineRef.removeComponent(this, componentType);
}
/**
* Gets the component data associated to this entity.
* <p>
* <b>This function calls getComponentData() in the engine,</b> 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
*/
@Deprecated
public Object getComponent(Type componentType){
return engineRef.getComponentData(this, componentType);
} }
} }

View File

@ -15,7 +15,6 @@ import java.util.NoSuchElementException;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
// Define the manager classes internally - should be moved to seperate source files as appropriate
/** /**
* Manages data from the perspective of the entity. * Manages data from the perspective of the entity.
* <p> * <p>
@ -23,7 +22,7 @@ import java.util.ArrayList;
*/ */
class EntityManager{ class EntityManager{
// According to https://stackoverflow.com/questions/12524826/why-should-i-use-deque-over-stack // According to https://stackoverflow.com/questions/12524826/why-should-i-use-deque-over-stack
// ArrayDeque is likely faster than a LinkedList, when used as one. // ArrayDeque is likely faster than a LinkedList, when used in place of one.
// We can also supply a size to the constructor of ArrayDeque, which avoids resizing the collection // We can also supply a size to the constructor of ArrayDeque, which avoids resizing the collection
// at initialisation time (took 1.4s vs 1.8s for 1M) // at initialisation time (took 1.4s vs 1.8s for 1M)
private Deque<Entity> unusedEntities; private Deque<Entity> unusedEntities;
@ -39,7 +38,8 @@ class EntityManager{
for (int i = 0; i < maxSize; i++) { for (int i = 0; i < maxSize; i++) {
unusedEntities.add(new Entity(i)); unusedEntities.add(new Entity(i));
entityRegistrations.add(null); // Leave bitsets out as if they are null the entity isn't initialised properly entityRegistrations.add(null); // Annul the entries, to set list size, but keep the unused
// entities in an invalid state
} }
} }
@ -73,7 +73,7 @@ class EntityManager{
* Gets the current maximum size * Gets the current maximum size
* @return the value of currentSize * @return the value of currentSize
*/ */
protected Integer getMaxSize(){ public Integer getMaxSize(){
return maxSize; return maxSize;
} }
@ -82,7 +82,7 @@ class EntityManager{
* @param entity the entity whose BitSet to retrieve * @param entity the entity whose BitSet to retrieve
* @return the BitSet of the provided entity, or a new, empty BitSet if the result was null or out of bounds * @return the BitSet of the provided entity, or a new, empty BitSet if the result was null or out of bounds
*/ */
protected BitSet getRegistrations(Entity entity){ public BitSet getRegistrations(Entity entity){
try{ try{
BitSet registrations = entityRegistrations.get(entity.getValue()); BitSet registrations = entityRegistrations.get(entity.getValue());
if (registrations != null){ if (registrations != null){
@ -171,7 +171,7 @@ class EntityManager{
* @param componentManager reference to the insanced ComponentManager * @param componentManager reference to the insanced ComponentManager
* @return true if the operation succeeded, otherwise false * @return true if the operation succeeded, otherwise false
*/ */
protected boolean resize(int newSize, SystemManager systemManager, ComponentManager componentManager){ public boolean resize(int newSize, SystemManager systemManager, ComponentManager componentManager){
if (newSize < maxSize - unusedEntities.size()){ if (newSize < maxSize - unusedEntities.size()){
Engine.writeErr("Attempted to resize the maximum entity count to a number smaller than the current assigned entity count."); Engine.writeErr("Attempted to resize the maximum entity count to a number smaller than the current assigned entity count.");
return false; return false;
@ -181,9 +181,12 @@ class EntityManager{
return true; return true;
} }
else{ else{
// Consistency should be maintained. // Every element above the index must be reordered
// This is computationally expensive; we must re-order every assigned entity above newSize, if the newSize is smaller
if (newSize < maxSize){ if (newSize < maxSize){
// Loop through every entity value above our desired index
// and attempt to remove it from the unused entity queue
// If that fails, the entity is in use, and must have its
// data copied to a lower index
Deque<Entity> outOfBounds = new ArrayDeque<>(maxSize - newSize); Deque<Entity> outOfBounds = new ArrayDeque<>(maxSize - newSize);
for (int i = newSize; i < maxSize; i++) { for (int i = newSize; i < maxSize; i++) {
Entity entityI = Entity.asEntity(i); Entity entityI = Entity.asEntity(i);
@ -193,6 +196,7 @@ class EntityManager{
outOfBounds.add(entityI); outOfBounds.add(entityI);
} }
} }
// Process all out-of-bounds entities, and destroy them as we go
while(outOfBounds.size() > 0){ while(outOfBounds.size() > 0){
Entity old = outOfBounds.remove(); Entity old = outOfBounds.remove();
Entity newPos = addEntity(); Entity newPos = addEntity();
@ -204,7 +208,7 @@ class EntityManager{
componentManager.moveAllComponentData(old, newPos, getRegistrations(old)); componentManager.moveAllComponentData(old, newPos, getRegistrations(old));
} }
for (int i = newSize; i < maxSize; i++) { for (int i = newSize; i < maxSize; i++) {
// Remove out-of-bounds data // Remove out-of-bounds registration data
entityRegistrations.remove(newSize); entityRegistrations.remove(newSize);
} }
} }
@ -221,7 +225,7 @@ class EntityManager{
} }
} }
protected int getNumEntities(){ public int getNumEntities(){
return maxSize - unusedEntities.size(); return maxSize - unusedEntities.size();
} }
} }

View File

@ -14,8 +14,11 @@ import java.util.BitSet;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
/**
* Manages system-focused aspects, such as ensuring a system has the correct list of current entities.
* Manages registration of new systems
*/
class SystemManager{ class SystemManager{
//private Map<Type, BitSet> registrationSignatures = new HashMap<>();
private Map<Type, ECSSystem> systems = new HashMap<>(); private Map<Type, ECSSystem> systems = new HashMap<>();
/** /**
@ -32,9 +35,7 @@ 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. This is not lightweight, * evaluate if the entity is still relevant to each system.
* 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
*/ */
@ -61,7 +62,7 @@ class SystemManager{
* @param system the instance of the system * @param system the instance of the system
* @return true if the system was added successfully. False if it was already registered; with an error message written to the log * @return true if the system was added successfully. False if it was already registered; with an error message written to the log
*/ */
protected boolean registerSystem(Type systemType, ECSSystem system){ public boolean registerSystem(Type systemType, ECSSystem system){
if (systems.containsKey(systemType)){ if (systems.containsKey(systemType)){
Engine.writeErr("System \'" + systemType.getTypeName() + "\' already registered"); Engine.writeErr("System \'" + systemType.getTypeName() + "\' already registered");
return false; return false;
@ -75,7 +76,7 @@ class SystemManager{
* @param systemType the class type of the system * @param systemType the class type of the system
* @param registrations the BitSet containing the required registrations set to true * @param registrations the BitSet containing the required registrations set to true
*/ */
protected void setRegistrationSignature(Type systemType, BitSet registrations){ public void setSystemRegistraions(Type systemType, BitSet registrations){
systems.get(systemType).registrationSet = registrations; systems.get(systemType).registrationSet = registrations;
} }

View File

@ -33,7 +33,7 @@ class AppTest {
class TestSystem extends ECSSystem{ class TestSystem extends ECSSystem{
public void init(){} public void init(){}
public void update() { public void update(double dt) {
for (Entity entity : entities) { for (Entity entity : entities) {
TestObject entityComponent = (TestObject)gameEngine.getComponentData(entity, TestObject.class); TestObject entityComponent = (TestObject)gameEngine.getComponentData(entity, TestObject.class);
entityComponent.i++; entityComponent.i++;
@ -99,7 +99,7 @@ class AppTest {
gameEngine.addComponent(entity, TestObject.class, new TestObject()); gameEngine.addComponent(entity, TestObject.class, new TestObject());
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
system.update(); system.update(0.1);
} }
assertEquals(6, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i); assertEquals(6, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
@ -132,7 +132,6 @@ class AppTest {
for (int i = 256; i < 512; i++) { for (int i = 256; i < 512; i++) {
gameEngine.destroyEntity(Entity.asEntity(i)); gameEngine.destroyEntity(Entity.asEntity(i));
} }
int k = 0;
} }
/** /**