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>
<groupId>nz.ac.massey.javaecs</groupId>
<artifactId>javaecs</artifactId>
<version>0.9.2-PRERELEASE</version>
<version>0.9.9-RELEASE_CANDIDATE</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<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.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;

View File

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

View File

@ -20,17 +20,21 @@ public abstract class ECSSystem{
protected Set<Entity> entities = new HashSet<>();
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.
* These should run once, when the system is first initialised.
*/
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.
* 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;
/**
* ECS manager class.
* ECS engine class.
* Call this class and its functions to interact correctly with the ECS system.
* This is the entry-point to the library
*
* Contributors:
* Brychan Dempsey - brychand@hotmail.com
@ -18,7 +19,8 @@ import java.util.BitSet;
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>
* See https://git.software.kauripeak.co.nz/BrychanD/JavaECS
* for documentation and more information.
@ -39,6 +41,9 @@ public class Engine {
entityManager = new EntityManager();
componentManager = new ComponentManager();
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);
componentManager = new ComponentManager();
systemManager = new SystemManager();
Entity.engineRef = this;
}
/**
@ -156,7 +162,7 @@ public class Engine {
if (entityManager.unregisterComponent(componentManager.getComponentIndex(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;
}
else return false;
@ -191,6 +197,7 @@ public class Engine {
* Registers the system to the SystemManager
* @param systemType the type of the system
* @param instance the instance of the system
* @return true if successful
*/
public boolean registerSystem(Type systemType, ECSSystem instance){
return systemManager.registerSystem(systemType, instance);
@ -202,7 +209,7 @@ public class Engine {
* @param signature the new signature data
*/
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;
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), it is encapsulated to help
* distinguish between entities and Integers.
* would be more performant (no function call, primitive arrays & 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
*/
public class Entity {
protected static Engine engineRef;
private int value;
public Entity(int value){
@ -20,11 +26,11 @@ public class Entity {
* Returns the int value wrapped as an entity.
* Used to provide a distinction between creating a new entity, and
* using an int value we assume is a valid entity.
*
* <p>
* Functionally, this is no different to creating a new entity.
*
* @param value
* @return
* @param value the integer value to read as an Entity
* @return an Entity object representing the ID value
*/
public static Entity asEntity(int value){
return new Entity(value);
@ -44,6 +50,58 @@ public class Entity {
@Override
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.ArrayList;
// Define the manager classes internally - should be moved to seperate source files as appropriate
/**
* Manages data from the perspective of the entity.
* <p>
@ -23,7 +22,7 @@ import java.util.ArrayList;
*/
class EntityManager{
// 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
// at initialisation time (took 1.4s vs 1.8s for 1M)
private Deque<Entity> unusedEntities;
@ -39,7 +38,8 @@ class EntityManager{
for (int i = 0; i < maxSize; 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
* @return the value of currentSize
*/
protected Integer getMaxSize(){
public Integer getMaxSize(){
return maxSize;
}
@ -82,7 +82,7 @@ class EntityManager{
* @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
*/
protected BitSet getRegistrations(Entity entity){
public BitSet getRegistrations(Entity entity){
try{
BitSet registrations = entityRegistrations.get(entity.getValue());
if (registrations != null){
@ -171,7 +171,7 @@ class EntityManager{
* @param componentManager reference to the insanced ComponentManager
* @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()){
Engine.writeErr("Attempted to resize the maximum entity count to a number smaller than the current assigned entity count.");
return false;
@ -181,9 +181,12 @@ class EntityManager{
return true;
}
else{
// Consistency should be maintained.
// This is computationally expensive; we must re-order every assigned entity above newSize, if the newSize is smaller
// Every element above the index must be reordered
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);
for (int i = newSize; i < maxSize; i++) {
Entity entityI = Entity.asEntity(i);
@ -193,6 +196,7 @@ class EntityManager{
outOfBounds.add(entityI);
}
}
// Process all out-of-bounds entities, and destroy them as we go
while(outOfBounds.size() > 0){
Entity old = outOfBounds.remove();
Entity newPos = addEntity();
@ -204,7 +208,7 @@ class EntityManager{
componentManager.moveAllComponentData(old, newPos, getRegistrations(old));
}
for (int i = newSize; i < maxSize; i++) {
// Remove out-of-bounds data
// Remove out-of-bounds registration data
entityRegistrations.remove(newSize);
}
}
@ -221,7 +225,7 @@ class EntityManager{
}
}
protected int getNumEntities(){
public int getNumEntities(){
return maxSize - unusedEntities.size();
}
}

View File

@ -14,8 +14,11 @@ import java.util.BitSet;
import java.util.Map;
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{
//private Map<Type, BitSet> registrationSignatures = 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
* evaluate if the entity is still relevant to each system. This is not lightweight,
* use entityRegistrationAdded and entityRegistrationRemoved if adding/removing a
* single component
* evaluate if the entity is still relevant to each system.
* @param entity the entity that was modified
* @param entityRegistrations the new registrations of the entity
*/
@ -61,7 +62,7 @@ class SystemManager{
* @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
*/
protected boolean registerSystem(Type systemType, ECSSystem system){
public boolean registerSystem(Type systemType, ECSSystem system){
if (systems.containsKey(systemType)){
Engine.writeErr("System \'" + systemType.getTypeName() + "\' already registered");
return false;
@ -75,7 +76,7 @@ class SystemManager{
* @param systemType the class type of the system
* @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;
}

View File

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