Compare commits
No commits in common. "master" and "Initial-Release" have entirely different histories.
master
...
Initial-Re
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -14,7 +14,7 @@
|
|||||||
"type": "java",
|
"type": "java",
|
||||||
"name": "Launch App",
|
"name": "Launch App",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mainClass": "nz.ac.massey.javaecs.App",
|
"mainClass": "nz.ac.massey.programming_project_159333_s1_2021.App",
|
||||||
"projectName": "javaecs"
|
"projectName": "javaecs"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
28
README.md
28
README.md
@ -1,28 +1,4 @@
|
|||||||
# JavaECS
|
# JavaECS
|
||||||
An implementation of an Entity-Component-System written in Java.
|
An implementation of an Entity-Component-System in Java. Based on the C++ implementation by [Austin Morlan](https://code.austinmorlan.com/austin/ecs).
|
||||||
|
|
||||||
## Introduction to ECS
|
See the [documentation](https://git.software.kauripeak.co.nz/BrychanD/JavaECS-Docs/wiki) for implmentation details
|
||||||
The primary goal of an ECS is to provide fast access to many entities; especially where those entities share many of the same properties. It also solves issues regarding adaptability in an inheritance-based engine.
|
|
||||||
ECS is more of a conceptual idea rather than an actual engine structure. There exists many variations of the concept, each adding in different features and models as required.
|
|
||||||
|
|
||||||
The primary example of an ECS is [EnTT](https://github.com/skypjack/entt), which is used in Mojang's Minecraft.
|
|
||||||
|
|
||||||
There are four key elements to an ECS:
|
|
||||||
1. The **entity**, which is a simple ID, usually an index.
|
|
||||||
2. The **component**, which is a struct or class that stores data
|
|
||||||
3. The **system**, which is a functionality that is executed regularly by the engine.
|
|
||||||
4. The **engine** (*sometimes split into parts called 'managers'*), which controls the interaction between these components, and provides access to the external program.
|
|
||||||
|
|
||||||
## About JavaECS
|
|
||||||
The focus of JavaECS is more about the structure rather than raw performance. It remains performant, but there may be multiple areas where improvements can be made.
|
|
||||||
|
|
||||||
In a quick port of Alex Beimler's [ECS Benchmark](https://github.com/abeimler/ecs_benchmark), JavaECS performs at about the same speed as [ECS](https://github.com/redxdev/ECS) (~90 ms). The results aren't normalised between test environments, so take them with a grain of salt; but it tends to suggest that the project has decent performance.
|
|
||||||
|
|
||||||
This project is inspired by:
|
|
||||||
* [C++ implementation](https://austinmorlan.com/posts/entity_component_system/) by Austin Morlan.
|
|
||||||
* [Nomad Game Engine](https://medium.com/@savas/nomad-game-engine-part-2-ecs-9132829188e5) by Niko Savas
|
|
||||||
* [EntityX](https://github.com/alecthomas/entityx) by Alec Thomas.
|
|
||||||
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
See the [documentation](https://git.software.kauripeak.co.nz/BrychanD/JavaECS-Docs/wiki) for implementation details
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>nz.ac.massey.javaecs</groupId>
|
<groupId>nz.ac.massey.programming_project_159333_s1_2021</groupId>
|
||||||
<artifactId>javaecs</artifactId>
|
<artifactId>javaecs</artifactId>
|
||||||
<version>1.0.1</version>
|
<version>1.1-SNAPSHOT</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>
|
||||||
@ -17,10 +17,10 @@
|
|||||||
<maven-javadoc-plugin.version>3.0.0</maven-javadoc-plugin.version>
|
<maven-javadoc-plugin.version>3.0.0</maven-javadoc-plugin.version>
|
||||||
<coveralls-maven-plugin.version>4.3.0</coveralls-maven-plugin.version>
|
<coveralls-maven-plugin.version>4.3.0</coveralls-maven-plugin.version>
|
||||||
<!-- JaCoCo thresholds. Increase gradually as you add tests. -->
|
<!-- JaCoCo thresholds. Increase gradually as you add tests. -->
|
||||||
<jacoco.unit-tests.limit.instruction-ratio>75%</jacoco.unit-tests.limit.instruction-ratio>
|
<jacoco.unit-tests.limit.instruction-ratio>0%</jacoco.unit-tests.limit.instruction-ratio>
|
||||||
<jacoco.unit-tests.limit.branch-ratio>50%</jacoco.unit-tests.limit.branch-ratio>
|
<jacoco.unit-tests.limit.branch-ratio>0%</jacoco.unit-tests.limit.branch-ratio>
|
||||||
<jacoco.unit-tests.limit.class-complexity>30</jacoco.unit-tests.limit.class-complexity>
|
<jacoco.unit-tests.limit.class-complexity>20</jacoco.unit-tests.limit.class-complexity>
|
||||||
<jacoco.unit-tests.limit.method-complexity>10</jacoco.unit-tests.limit.method-complexity>
|
<jacoco.unit-tests.limit.method-complexity>5</jacoco.unit-tests.limit.method-complexity>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -60,18 +60,6 @@
|
|||||||
</executions>
|
</executions>
|
||||||
|
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>attach-javadocs</id>
|
|
||||||
<goals>
|
|
||||||
<goal>jar</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-enforcer-plugin</artifactId>
|
<artifactId>maven-enforcer-plugin</artifactId>
|
||||||
@ -146,7 +134,7 @@
|
|||||||
<goal>report</goal>
|
<goal>report</goal>
|
||||||
</goals>
|
</goals>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<!-- <execution>
|
||||||
<id>check-unit-test</id>
|
<id>check-unit-test</id>
|
||||||
<phase>test</phase>
|
<phase>test</phase>
|
||||||
<goals>
|
<goals>
|
||||||
@ -192,7 +180,7 @@
|
|||||||
</rule>
|
</rule>
|
||||||
</rules>
|
</rules>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution> -->
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
class ComponentArray{
|
||||||
|
List<Object> componentArray = new ArrayList<>();
|
||||||
|
|
||||||
|
Map<Integer, Integer> entityComponentDataMap = new HashMap<>();
|
||||||
|
Map<Integer, Integer> componentDataEntityMap = new HashMap<>();
|
||||||
|
|
||||||
|
public void entityDestroyed(int entity){
|
||||||
|
if (entityComponentDataMap.containsKey(entity)){
|
||||||
|
removeData(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeData(int entity){
|
||||||
|
if (!entityComponentDataMap.containsKey(entity)){
|
||||||
|
System.err.println("Attempted to remove non-existent entity");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
int 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertData(int entity, Object component){
|
||||||
|
if (entityComponentDataMap.containsKey(entity)){
|
||||||
|
System.err.println("Entity is already subscribed to the component");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int index = componentArray.size();
|
||||||
|
entityComponentDataMap.put(entity, index);
|
||||||
|
componentDataEntityMap.put(index, entity);
|
||||||
|
|
||||||
|
componentArray.add(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves component data to another entity. (Copies, deletes and inserts data)
|
||||||
|
* @param sourceEntity
|
||||||
|
* @param destinationEntity
|
||||||
|
*/
|
||||||
|
public void moveData(int sourceEntity, int destinationEntity){
|
||||||
|
Object data = entityComponentDataMap.get(sourceEntity);
|
||||||
|
removeData(sourceEntity);
|
||||||
|
insertData(destinationEntity, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getData(int entity){
|
||||||
|
if (!entityComponentDataMap.containsKey(entity)){
|
||||||
|
System.err.println("Attempted to retrieve non-existent data");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return componentArray.get(entityComponentDataMap.get(entity));
|
||||||
|
}
|
||||||
|
}
|
@ -10,156 +10,53 @@ package nz.ac.massey.javaecs;
|
|||||||
*/
|
*/
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
/**
|
class ComponentManager{
|
||||||
* Manages the addition, sorting and retrieving of components and component data
|
Map<Type, ComponentArray> componentArrays = new HashMap<>();
|
||||||
*/
|
Map<Type, Integer> componentPosIndex = new HashMap<>();
|
||||||
public class ComponentManager{
|
int componentPos = 0;
|
||||||
public Map<Type, Map<Entity, Object>> componentArrays = new HashMap<>();
|
|
||||||
// Need to be able to map bit indices and component types
|
|
||||||
public Map<Integer, Type> indexComponentType = new HashMap<>();
|
|
||||||
public Map<Type, Integer> componentTypeIndex = new HashMap<>();
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
public void registerComponent(Type type){
|
||||||
* Adds the specified component to the provided entity.
|
|
||||||
* Does not ensure synchronisation with the other managers; ensure this is done!
|
|
||||||
* @param componentType the class type of the component to add
|
|
||||||
* @param componentData the component data to associate
|
|
||||||
* @param entity the entity to associate data to
|
|
||||||
*/
|
|
||||||
public boolean addComponentToEntity(Type componentType, Object componentData, Entity entity){
|
|
||||||
componentArrays.get(componentType).put(entity, componentData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals to the ComponentManager the entity was destroyed. All component data references should be removed.
|
|
||||||
* @param entity the entity that was destroyed.
|
|
||||||
*/
|
|
||||||
public void entityDestroyed(Entity entity, BitSet entityRegistrations){
|
|
||||||
// HashMap lookups take time, use the known bitstates to avoid
|
|
||||||
int index = entityRegistrations.nextSetBit(0);
|
|
||||||
while(index != -1){
|
|
||||||
componentArrays.get(indexComponentType.get(index)).remove(entity);
|
|
||||||
index = entityRegistrations.nextSetBit(index+1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the component data associated with the entity
|
|
||||||
* @param componentType the class type of data to look for
|
|
||||||
* @param entity the entity to find data for
|
|
||||||
* @return the Object data found, or null if it was not found
|
|
||||||
*/
|
|
||||||
public Object getComponent(Type componentType, Entity entity){
|
|
||||||
return componentArrays.get(componentType).get(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the registration index of the component type
|
|
||||||
* @param type the class type of the component
|
|
||||||
* @return the index of the component type, or -1 if it isn't found
|
|
||||||
*/
|
|
||||||
public Integer getComponentIndex(Type type){
|
|
||||||
try{
|
|
||||||
return componentTypeIndex.get(type);
|
|
||||||
}
|
|
||||||
catch (NullPointerException e){
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the type of the component at the provided index
|
|
||||||
* @param index the index of the component
|
|
||||||
* @return the class type of the index. `null` if not found
|
|
||||||
*/
|
|
||||||
public Type getComponentType(Integer index){
|
|
||||||
try{
|
|
||||||
return indexComponentType.get(index);
|
|
||||||
}
|
|
||||||
catch (NullPointerException e){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves a single component data from one entity to another
|
|
||||||
* @param sourceEntity the entity to move data from
|
|
||||||
* @param destinationEntity the entity to move data to
|
|
||||||
* @param component the component class type to consider
|
|
||||||
* @return true if the component was moved successfully, else false
|
|
||||||
*/
|
|
||||||
public boolean moveComponentData(Entity sourceEntity, Entity destinationEntity, Type component){
|
|
||||||
Object data = componentArrays.get(component).get(sourceEntity);
|
|
||||||
componentArrays.get(component).put(destinationEntity, data);
|
|
||||||
componentArrays.get(component).remove(sourceEntity);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves all component data from one entity to another
|
|
||||||
* @param sourceEntity the entity to move data from
|
|
||||||
* @param destinationEntity the entity to move data to
|
|
||||||
* @param sourceRegistrations the component registrations of the source entity
|
|
||||||
*/
|
|
||||||
public void moveAllComponentData(Entity sourceEntity, Entity destinationEntity, BitSet sourceRegistrations){
|
|
||||||
int result = sourceRegistrations.nextSetBit(0);
|
|
||||||
while (result != -1){
|
|
||||||
moveComponentData(sourceEntity, destinationEntity, indexComponentType.get(result));
|
|
||||||
result = sourceRegistrations.nextSetBit(result +1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the component type
|
|
||||||
* @param type the class type to register
|
|
||||||
* @return true if the component was registered successfully, else false
|
|
||||||
*/
|
|
||||||
public boolean registerComponent(Type type){
|
|
||||||
return registerComponent(type, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the component type
|
|
||||||
* @param type the class type to register
|
|
||||||
* @param arraySize the number of elements to prereserve space for.
|
|
||||||
* @return true if the component was registered successfully, else false
|
|
||||||
*/
|
|
||||||
public boolean registerComponent(Type type, int arraySize){
|
|
||||||
if (componentArrays.containsKey(type)){
|
if (componentArrays.containsKey(type)){
|
||||||
Engine.writeErr("Component " + type.getTypeName() + " is already registered");
|
System.err.println("Component " + type.getTypeName() + " is already registered");
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
componentArrays.put(type, new HashMap<>());
|
componentArrays.put(type, new ComponentArray());
|
||||||
indexComponentType.put(componentTypeIndex.size(), type);
|
componentPosIndex.put(type, componentPos++);
|
||||||
componentTypeIndex.put(type, componentTypeIndex.size());
|
}
|
||||||
return true;
|
|
||||||
|
public void addComponentToEntity(Type componentName, Object componentData, int entity){
|
||||||
|
componentArrays.get(componentName).insertData(entity, componentData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeComponentFromEntity(Type componentName, int entity){
|
||||||
|
componentArrays.get(componentName).removeData(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Java does not allow reflective typing, so must cast this in the retrieving function.
|
||||||
|
public Object getComponent(Type componentType, int entity){
|
||||||
|
return componentArrays.get(componentType).getData(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void entityDestroyed(int entity){
|
||||||
|
for (Type key : componentArrays.keySet()) {
|
||||||
|
componentArrays.get(key).entityDestroyed(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getComponentIndex(Type type){
|
||||||
|
return componentPosIndex.get(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the specified component from the entity.
|
* Moves component data from one entity to another
|
||||||
* <p>
|
* @param sourceEntity
|
||||||
* Be sure to call SystemManager.entityRegistrationsChanged after calling this function
|
* @param destinationEntity
|
||||||
* @param componentType the class type of the component to remove
|
|
||||||
* @param entity the entity to remove the component from
|
|
||||||
*/
|
*/
|
||||||
public boolean removeComponentFromEntity(Type componentType, Entity entity){
|
public void moveComponentData(int sourceEntity, int destinationEntity){
|
||||||
componentArrays.get(componentType).remove(entity);
|
for (Type key : componentArrays.keySet()) {
|
||||||
return true;
|
componentArrays.get(key).moveData(sourceEntity, destinationEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
145
javaecs/src/main/java/nz/ac/massey/javaecs/ECS.java
Normal file
145
javaecs/src/main/java/nz/ac/massey/javaecs/ECS.java
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package nz.ac.massey.javaecs;
|
||||||
|
/**
|
||||||
|
* ECS manager class.
|
||||||
|
* Call this class and its functions to interact correctly with the ECS system.
|
||||||
|
*
|
||||||
|
* Contributors:
|
||||||
|
* Brychan Dempsey - brychand@hotmail.com
|
||||||
|
*
|
||||||
|
* References:
|
||||||
|
* Based on the implementation by Austin Morlan:
|
||||||
|
* https://code.austinmorlan.com/austin/ecs - 'A simple C++ Entity Component System' - released under MIT licence
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.BitSet;
|
||||||
|
/**
|
||||||
|
* The ECS manager.
|
||||||
|
* <p>
|
||||||
|
* See <href src="https://git.software.kauripeak.co.nz/BrychanD/JavaECS">https://git.software.kauripeak.co.nz/BrychanD/JavaECS</href>
|
||||||
|
* for documentation and more information.
|
||||||
|
*/
|
||||||
|
public class ECS {
|
||||||
|
protected EntityManager entityManager;
|
||||||
|
protected ComponentManager componentManager;
|
||||||
|
protected SystemManager systemManager;
|
||||||
|
/**
|
||||||
|
* Initialises the ECS with default values
|
||||||
|
* <p>
|
||||||
|
* Maximum 1024 enitites default
|
||||||
|
*/
|
||||||
|
public ECS(){
|
||||||
|
entityManager = new EntityManager();
|
||||||
|
componentManager = new ComponentManager();
|
||||||
|
systemManager = new SystemManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the ECS with the specified value(s)
|
||||||
|
* @param maxEntities the maximum number of entities to allow
|
||||||
|
*/
|
||||||
|
public ECS(int maxEntities){
|
||||||
|
entityManager = new EntityManager(maxEntities);
|
||||||
|
componentManager = new ComponentManager();
|
||||||
|
systemManager = new SystemManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /**
|
||||||
|
* Creates a new entity
|
||||||
|
* @return the index of the new entity
|
||||||
|
* @throws IndexOutOfBoundsException
|
||||||
|
*/
|
||||||
|
Integer createEntity() throws IndexOutOfBoundsException{
|
||||||
|
int newEntity = entityManager.addEntity();
|
||||||
|
if (newEntity == -1) throw new IndexOutOfBoundsException("Could not create a new entity");
|
||||||
|
return newEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to resize the maximum number of entities
|
||||||
|
* @param newSize the new maximum number of entities
|
||||||
|
* @return true if the operation succeeded
|
||||||
|
*/
|
||||||
|
boolean resizeMaximum(int newSize){
|
||||||
|
return entityManager.resize(newSize, systemManager, componentManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals each manager to remove the specified entity
|
||||||
|
* @param entity the entity to destroy
|
||||||
|
*/
|
||||||
|
void destroyEntity(int entity){
|
||||||
|
entityManager.removeEntity(entity);
|
||||||
|
componentManager.entityDestroyed(entity);
|
||||||
|
systemManager.entityDestroyed(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the specified name in the component manager
|
||||||
|
* @param name the name to register. Should be the component class name or a suitable name for primitive types
|
||||||
|
*/
|
||||||
|
void registerComponent(Type type){
|
||||||
|
componentManager.registerComponent(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer getComponentIndex(Type type){
|
||||||
|
return componentManager.getComponentIndex(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an exisiting component to an exisiting entity
|
||||||
|
* @param entity the entity to add the component to
|
||||||
|
* @param componentName the class name of the component to add
|
||||||
|
* @param component the actual component data
|
||||||
|
*/
|
||||||
|
void addComponent(int entity, Type componentName, Object component){
|
||||||
|
componentManager.addComponentToEntity(componentName, component, entity);
|
||||||
|
entityManager.registerComponent(componentManager.getComponentIndex(componentName), entity);
|
||||||
|
systemManager.entitySignatureChanged(entity, entityManager.getRegistrations(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the component from the specified entity
|
||||||
|
* @param entity the entity to remove the component from
|
||||||
|
* @param componentName the class name of the component
|
||||||
|
*/
|
||||||
|
void removeComponent(int entity, Type componentType){
|
||||||
|
componentManager.removeComponentFromEntity(componentType, entity);
|
||||||
|
entityManager.unregisterComponent(componentManager.getComponentIndex(componentType), entity);
|
||||||
|
systemManager.entitySignatureChanged(entity, entityManager.getRegistrations(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the actual data of the component associated to the entity.
|
||||||
|
* May require casting from Object to the known data type
|
||||||
|
* @param entity the entity to retrieve the data for
|
||||||
|
* @param componentType the class type of the component
|
||||||
|
* @return the component data Object associated with the entity
|
||||||
|
*/
|
||||||
|
Object getComponentData(int entity, Type componentType){
|
||||||
|
return componentManager.getComponent(componentType, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param systemName
|
||||||
|
* @param action
|
||||||
|
*/
|
||||||
|
void registerSystem(Type systemType, ECSSystem action){
|
||||||
|
systemManager.registerSystem(systemType, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the specified system's signature to the provided signature
|
||||||
|
* @param system the class name of the system to set the signature of
|
||||||
|
* @param signature the new signature data
|
||||||
|
*/
|
||||||
|
void setSystemSignature(Type system, BitSet signature){
|
||||||
|
systemManager.setSignature(system, signature);
|
||||||
|
}
|
||||||
|
Integer getMaxEntities(){
|
||||||
|
return entityManager.currentSize;
|
||||||
|
}
|
||||||
|
}
|
@ -13,49 +13,8 @@ package nz.ac.massey.javaecs;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
/**
|
class ECSSystem{
|
||||||
* Abstract class that all systems should inherit from<p>
|
Set<Integer> entities = new HashSet<>();
|
||||||
* 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 Set<Entity> entities = new HashSet<>();
|
|
||||||
public BitSet registrationSet;
|
|
||||||
|
|
||||||
public ECSSystem(){
|
|
||||||
registrationSet = new BitSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitSet getRegistrationSet(){
|
|
||||||
return registrationSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functionality that should be run only once.
|
|
||||||
* 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() throws Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functionality that is expected to be called regularly
|
|
||||||
* Intended as a template only; may be superficially implemented.
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
public abstract void update(double dt);
|
|
||||||
}
|
}
|
@ -1,288 +0,0 @@
|
|||||||
package nz.ac.massey.javaecs;
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*
|
|
||||||
* References:
|
|
||||||
* Based on the implementation by Austin Morlan:
|
|
||||||
* https://austinmorlan.com/posts/entity_component_system/ - 'A simple C++ Entity Component System'
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
public class Engine {
|
|
||||||
// All internal functions write error messages to errorStream; which defaults to System.err
|
|
||||||
// Can be set to a different PrintStream, to allow errors to be logged elsewhere
|
|
||||||
protected static PrintStream errorStream = System.err;
|
|
||||||
// References to the manager classes
|
|
||||||
protected EntityManager entityManager;
|
|
||||||
protected ComponentManager componentManager;
|
|
||||||
protected SystemManager systemManager;
|
|
||||||
|
|
||||||
/***************************************
|
|
||||||
** Engine Constructors **
|
|
||||||
***************************************/
|
|
||||||
public 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialises the ECS with the specified value
|
|
||||||
* @param maxEntities the maximum number of entities to allow
|
|
||||||
*/
|
|
||||||
public Engine(int maxEntities){
|
|
||||||
entityManager = new EntityManager(maxEntities);
|
|
||||||
componentManager = new ComponentManager();
|
|
||||||
systemManager = new SystemManager();
|
|
||||||
Entity.engineRef = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to resize the maximum number of entities
|
|
||||||
* @param newSize the new maximum number of entities
|
|
||||||
* @return true if the operation succeeded
|
|
||||||
*/
|
|
||||||
public boolean resizeMaximum(int newSize){
|
|
||||||
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
|
|
||||||
* @param entity the entity to destroy
|
|
||||||
*/
|
|
||||||
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));
|
|
||||||
entityManager.removeEntity(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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 **
|
|
||||||
***************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* @return true if the component type was registered successfully
|
|
||||||
*/
|
|
||||||
public boolean registerComponent(Type type){
|
|
||||||
return componentManager.registerComponent(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an exisiting component to an exisiting entity
|
|
||||||
* @param entity the entity to add the component to
|
|
||||||
* @param componentType the class name of the component to add
|
|
||||||
* @param component the actual component data
|
|
||||||
* @return true if the compnent was added to the entity
|
|
||||||
*/
|
|
||||||
public boolean addComponent(Entity entity, Type componentType, Object component){
|
|
||||||
// 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.entityRegistrationsChanged(entity, entityManager.getRegistrations(entity));
|
|
||||||
componentManager.addComponentToEntity(componentType, component, entity);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
writeErr("(" + componentType.getTypeName() + ")");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the component from the specified entity
|
|
||||||
* @param entity the entity to remove the component from
|
|
||||||
* @param componentType the class type of the component
|
|
||||||
* @return true if the component was removed
|
|
||||||
*/
|
|
||||||
public boolean removeComponent(Entity entity, Type componentType){
|
|
||||||
if (entityManager.unregisterComponent(componentManager.getComponentIndex(componentType), entity))
|
|
||||||
{
|
|
||||||
componentManager.removeComponentFromEntity(componentType, entity);
|
|
||||||
systemManager.entityRegistrationsChanged(entity, entityManager.getRegistrations(entity));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the actual data of the component associated to the entity.
|
|
||||||
* Requires casting from Object to the known data type
|
|
||||||
* @param entity the entity to retrieve the data for
|
|
||||||
* @param componentType the class type of the component
|
|
||||||
* @return the component data Object associated with the entity
|
|
||||||
*/
|
|
||||||
public Object getComponentData(Entity entity, Type componentType){
|
|
||||||
return componentManager.getComponent(componentType, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the component index of the provided type
|
|
||||||
* @param type the type to get the index of
|
|
||||||
* @return the index of the component
|
|
||||||
*/
|
|
||||||
public Integer getComponentIndex(Type type){
|
|
||||||
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
|
|
||||||
* @return true if successful
|
|
||||||
*/
|
|
||||||
public boolean registerSystem(Type systemType, ECSSystem instance){
|
|
||||||
return systemManager.registerSystem(systemType, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the specified system's signature to the provided signature
|
|
||||||
* @param system the class name of the system to set the signature of
|
|
||||||
* @param signature the new signature data
|
|
||||||
*/
|
|
||||||
public void setSystemSignature(Type system, BitSet signature){
|
|
||||||
systemManager.setSystemRegistraions(system, signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/****************************************
|
|
||||||
** Helper Functions & Accessors **
|
|
||||||
****************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes an error message to the set PrintStream
|
|
||||||
* <p>
|
|
||||||
* <i>Default is System.err
|
|
||||||
* @param message the message to write
|
|
||||||
*/
|
|
||||||
static void writeErr(String message){
|
|
||||||
errorStream.println(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes an object to the set PrintStream
|
|
||||||
* <p>
|
|
||||||
* <i>Default System.err
|
|
||||||
* @param obj the object to write
|
|
||||||
*/
|
|
||||||
static void writeErr(Object obj){
|
|
||||||
errorStream.println(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the error PrintStream to the provided PrintStream
|
|
||||||
* @param newErr the PrintStream to set as the write destination
|
|
||||||
*/
|
|
||||||
public static void setErr(PrintStream newErr){
|
|
||||||
errorStream = newErr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current error PrintStream
|
|
||||||
* @return the current PrintStream that errors are written to
|
|
||||||
*/
|
|
||||||
public static PrintStream getErr(){
|
|
||||||
return errorStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a the current component manager
|
|
||||||
* @return the current active component manager
|
|
||||||
*/
|
|
||||||
public ComponentManager getComponentManager() {
|
|
||||||
return componentManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a the current enitity manager
|
|
||||||
* @return the current active enitity manager
|
|
||||||
*/
|
|
||||||
public EntityManager getEntityManager() {
|
|
||||||
return entityManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a the current systems manager
|
|
||||||
* @return the current active systems manager
|
|
||||||
*/
|
|
||||||
public SystemManager getSystemManager() {
|
|
||||||
return systemManager;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
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, 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
|
|
||||||
* like names, grouping, specific ordering etc
|
|
||||||
*/
|
|
||||||
public class Entity {
|
|
||||||
protected static Engine engineRef;
|
|
||||||
|
|
||||||
private int value;
|
|
||||||
|
|
||||||
public Entity(int value){
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj)
|
|
||||||
{
|
|
||||||
if (obj instanceof Entity){
|
|
||||||
return (this.getValue() == ((Entity)obj).getValue());
|
|
||||||
}
|
|
||||||
else if (obj instanceof Integer){
|
|
||||||
return (this.value == (Integer)obj);
|
|
||||||
}
|
|
||||||
else return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode(){
|
|
||||||
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 <i>Engine.addComponent(Entity, Type, Object)</i></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
|
|
||||||
* @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 <i>Engine.removeComponent(Entity, Type)</i></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
|
|
||||||
* @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 <i>Engine.getComponentData()</i></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
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,10 @@
|
|||||||
package nz.ac.massey.javaecs;
|
package nz.ac.massey.javaecs;
|
||||||
/**
|
/**
|
||||||
* Entity Manager
|
* Entity Manager
|
||||||
* Controls adding and removing entities, and registration and unregistration of components to specific entities.
|
* This class manages entity allocations; keeping a list
|
||||||
|
* of all unassigned entities values.
|
||||||
|
* Additionally, handles setting the registered component
|
||||||
|
* flags for an entity.
|
||||||
*
|
*
|
||||||
* Contributors:
|
* Contributors:
|
||||||
* Brychan Dempsey - brychand@hotmail.com
|
* Brychan Dempsey - brychand@hotmail.com
|
||||||
@ -9,229 +12,124 @@ package nz.ac.massey.javaecs;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.Deque;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.Queue;
|
||||||
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>
|
||||||
* I.e. Controls adding and removing entities, and registration and deregistration of components.
|
* I.e. Controls adding and removing entities, and registration and deregistration of components.
|
||||||
* <p>
|
|
||||||
* Do not update values in this class unless you have reviewed the JavaECS source!
|
|
||||||
*/
|
*/
|
||||||
public class EntityManager{
|
class EntityManager{
|
||||||
// According to https://stackoverflow.com/questions/12524826/why-should-i-use-deque-over-stack
|
Queue<Integer> unusedEntities;
|
||||||
// ArrayDeque is likely faster than a LinkedList, when used in place of one.
|
List<BitSet> entityRegistrations;
|
||||||
// We can also supply a size to the constructor of ArrayDeque, which avoids resizing the collection
|
int currentSize;
|
||||||
// at initialisation time (took 1.4s vs 1.8s for 1M)
|
|
||||||
public Deque<Entity> unusedEntities;
|
|
||||||
public List<BitSet> entityRegistrations;
|
|
||||||
public int maxSize = 1024;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise the EntityManager with the default max size of 1024
|
|
||||||
*/
|
|
||||||
public EntityManager(){
|
public EntityManager(){
|
||||||
unusedEntities = new ArrayDeque<>(maxSize);
|
currentSize = 1024;
|
||||||
entityRegistrations = new ArrayList<>(maxSize); // Init to maxSize to increase performance
|
unusedEntities = new LinkedList<>();
|
||||||
|
entityRegistrations = new ArrayList<>();
|
||||||
|
|
||||||
for (int i = 0; i < maxSize; i++) {
|
for (int i = 0; i < 1024; i++) {
|
||||||
unusedEntities.add(new Entity(i));
|
unusedEntities.add(i);
|
||||||
entityRegistrations.add(null); // Annul the entries, to set list size, but keep the unused
|
entityRegistrations.add(new BitSet());
|
||||||
// entities in an invalid state
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
|
||||||
* Initialise the EntityManager with the provided maximum size
|
|
||||||
* @param maxEntities the maximum number of entities to allow
|
|
||||||
*/
|
|
||||||
public EntityManager(int maxEntities){
|
public EntityManager(int maxEntities){
|
||||||
maxSize = maxEntities;
|
currentSize = maxEntities;
|
||||||
unusedEntities = new ArrayDeque<>(maxSize);
|
unusedEntities = new LinkedList<>();
|
||||||
entityRegistrations = new ArrayList<>(maxEntities);
|
entityRegistrations = new ArrayList<>();
|
||||||
|
|
||||||
for (int i = 0; i < maxEntities; i++) {
|
for (int i = 0; i < maxEntities; i++) {
|
||||||
unusedEntities.add(new Entity(i));
|
unusedEntities.add(i);
|
||||||
entityRegistrations.add(null);
|
entityRegistrations.add(new BitSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new entity
|
public Integer addEntity(){
|
||||||
* @return the index of the new entity
|
if (unusedEntities.size() == 0){
|
||||||
* @throws NoSuchElementException an exception if there are no more unused entities
|
System.err.println("No available space to create a new entity");
|
||||||
*/
|
return -1;
|
||||||
public Entity addEntity() throws NoSuchElementException{
|
}
|
||||||
Entity result = unusedEntities.remove();
|
return unusedEntities.remove();
|
||||||
entityRegistrations.set(result.getValue(), new BitSet());
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void removeEntity(int entity){
|
||||||
* Gets the current maximum size
|
|
||||||
* @return the value of currentSize
|
|
||||||
*/
|
|
||||||
public Integer getMaxSize(){
|
|
||||||
return maxSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the BitSet containing the registrations of the entity.
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
public BitSet getRegistrations(Entity entity){
|
|
||||||
try{
|
|
||||||
BitSet registrations = entityRegistrations.get(entity.getValue());
|
|
||||||
if (registrations != null){
|
|
||||||
return registrations;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
Engine.writeErr("Registrations not initialised for entity: " + entity.getValue() + "; The entity does not exist");
|
|
||||||
return new BitSet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IndexOutOfBoundsException e){
|
|
||||||
Engine.writeErr("Index out of bounds error getting registrations for " + entity.getValue() + "; The entity might not exist");
|
|
||||||
return new BitSet(); // Using a blank BitSet will retain data safety (that is, no data will be modified)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the specified component index to the entity
|
|
||||||
* @param component the index of the component to register
|
|
||||||
* @param entity the entity to register
|
|
||||||
* @return true if the operation was successful
|
|
||||||
*/
|
|
||||||
public boolean registerComponent(int component, Entity entity){
|
|
||||||
if (entity.getValue() >= maxSize){
|
|
||||||
Engine.writeErr("Attempted to assign a component to non-existent entity: " + entity.getValue());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (entityRegistrations.get(entity.getValue()).get(component))
|
|
||||||
{
|
|
||||||
Engine.writeErr("Entity: " + entity.getValue() + " is already assigned to component " + component);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
entityRegistrations.get(entity.getValue()).set(component);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the entity index back into unusedEntities, and sets the registrations to null
|
|
||||||
* <p>
|
|
||||||
* <b>Does not handle associated data</b> Use the method in ECS to remove entities cleanly,
|
|
||||||
* or otherwise ensure the component data and systems are updated!
|
|
||||||
* @param entity the entity to remove
|
|
||||||
*/
|
|
||||||
public void removeEntity(Entity entity){
|
|
||||||
unusedEntities.add(entity);
|
unusedEntities.add(entity);
|
||||||
entityRegistrations.set(entity.getValue(), null);
|
entityRegistrations.get(entity).clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void registerComponent(int component, int entity){
|
||||||
* Sets the entity's registrations to the provided BitSet.
|
if (entity >= currentSize){
|
||||||
* <p>
|
System.err.println("Attempted to assign a component to non-existent entity: " + entity);
|
||||||
* Does <b>not</b> ensure the systems registrations are updated.
|
return;
|
||||||
* @param entity the entity to set
|
}
|
||||||
* @param registrations the preset registrations
|
entityRegistrations.get(entity).set(component);
|
||||||
*/
|
|
||||||
public void setRegistrations(Entity entity, BitSet registrations){
|
|
||||||
entityRegistrations.set(entity.getValue(), registrations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void unregisterComponent(int component, int entity){
|
||||||
* Unregisters the specified component from the entity
|
entityRegistrations.get(entity).clear(component);
|
||||||
* <p>
|
}
|
||||||
* <b>Does not handle component data</b> Use the method in ECS to remove components cleanly,
|
|
||||||
* or otherwise ensure the component data and systems are updated!
|
public BitSet getRegistrations(int entity){
|
||||||
* @param component the component index to remove
|
return entityRegistrations.get(entity);
|
||||||
* @param entity the entity to remove
|
}
|
||||||
* @return true if successful
|
|
||||||
*/
|
public void setRegistrations(int entity, BitSet registrations){
|
||||||
public boolean unregisterComponent(int component, Entity entity){
|
entityRegistrations.set(entity, registrations);
|
||||||
try{
|
|
||||||
entityRegistrations.get(entity.getValue()).clear(component);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (NullPointerException e){
|
|
||||||
Engine.writeErr("Entity not initialised");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch (IndexOutOfBoundsException e)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resizes the currentSize of the entity manager.
|
|
||||||
* @param newSize the new maximum size
|
|
||||||
* @param systemManager reference to the instanced SystemManager
|
|
||||||
* @param componentManager reference to the insanced ComponentManager
|
|
||||||
* @return true if the operation succeeded, otherwise false
|
|
||||||
*/
|
|
||||||
public boolean resize(int newSize, SystemManager systemManager, ComponentManager componentManager){
|
public boolean resize(int newSize, SystemManager systemManager, ComponentManager componentManager){
|
||||||
if (newSize < maxSize - unusedEntities.size()){
|
if (newSize < currentSize - unusedEntities.size()){
|
||||||
Engine.writeErr("Attempted to resize the maximum entity count to a number smaller than the current assigned entity count.");
|
System.err.println("Attempted to resize the maximum entity count to a number smaller than the current assigned entity count.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (newSize == maxSize){
|
else if (newSize == currentSize){
|
||||||
Engine.writeErr("Attempted to set the newSize to the current size");
|
System.err.println("Attempted to set the newSize to the current size");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
// Every element above the index must be reordered
|
// Consistency should be maintained.
|
||||||
if (newSize < maxSize){
|
// This is computationally expensive; we must re-order every assigned entity above newSize, if the newSize is smaller
|
||||||
// Loop through every entity value above our desired index
|
if (newSize < currentSize){
|
||||||
// and attempt to remove it from the unused entity queue
|
List<Integer> outOfBounds = new ArrayList<>();
|
||||||
// If that fails, the entity is in use, and must have its
|
for (int i = newSize; i < currentSize; i++) {
|
||||||
// data copied to a lower index
|
if (!unusedEntities.remove(i)){
|
||||||
Deque<Entity> outOfBounds = new ArrayDeque<>(maxSize - newSize);
|
|
||||||
for (int i = newSize; i < maxSize; i++) {
|
|
||||||
Entity entityI = Entity.asEntity(i);
|
|
||||||
if (!unusedEntities.remove(entityI)){
|
|
||||||
// Could not remove element, as it didn't exist (already assigned).
|
// Could not remove element, as it didn't exist (already assigned).
|
||||||
// must find it and reassign it a new in-bounds value
|
// must find it and reassign it a new in-bounds value
|
||||||
outOfBounds.add(entityI);
|
outOfBounds.add(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Process all out-of-bounds entities, and destroy them as we go
|
for (Integer integer : outOfBounds) {
|
||||||
while(outOfBounds.size() > 0){
|
int newPos = addEntity();
|
||||||
Entity old = outOfBounds.remove();
|
setRegistrations(newPos, getRegistrations(integer));
|
||||||
Entity newPos = addEntity();
|
|
||||||
setRegistrations(newPos, getRegistrations(old));
|
|
||||||
// Invoke an update in the SystemManager
|
// Invoke an update in the SystemManager
|
||||||
systemManager.entityDestroyed(old);
|
systemManager.entityDestroyed(integer);
|
||||||
systemManager.entityRegistrationsChanged(newPos, getRegistrations(newPos));
|
systemManager.entitySignatureChanged(newPos, getRegistrations(newPos));
|
||||||
// Invoke the change in the components
|
// Invoke the change in the components
|
||||||
componentManager.moveAllComponentData(old, newPos, getRegistrations(old));
|
componentManager.moveComponentData(integer, newPos);
|
||||||
|
componentManager.entityDestroyed(integer);
|
||||||
}
|
}
|
||||||
for (int i = newSize; i < maxSize; i++) {
|
for (int i = newSize; i < currentSize; i++) {
|
||||||
// Remove out-of-bounds registration data
|
// Remove out-of-bounds data
|
||||||
entityRegistrations.remove(newSize);
|
entityRegistrations.remove(newSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
// Init unassigned values
|
// Init unassigned values
|
||||||
for (int i = maxSize; i < newSize; i++) {
|
for (int i = currentSize; i < newSize; i++) {
|
||||||
unusedEntities.add(new Entity(i));
|
unusedEntities.add(i);
|
||||||
entityRegistrations.add(new BitSet());
|
entityRegistrations.add(new BitSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Finally, set the current size
|
// Finally, set the current size
|
||||||
maxSize = newSize;
|
currentSize = newSize;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNumEntities(){
|
|
||||||
return maxSize - unusedEntities.size();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,15 +1,9 @@
|
|||||||
package nz.ac.massey.javaecs;
|
package nz.ac.massey.javaecs;
|
||||||
|
|
||||||
public class FrameRateSystem extends ECSSystem{
|
public class FrameRateSystem extends ECSSystem{
|
||||||
@Override
|
|
||||||
void init() {}
|
void init() {}
|
||||||
|
|
||||||
@Override
|
|
||||||
void update() {}
|
|
||||||
|
|
||||||
void update(double dt, double idleTime){
|
void update(double dt, double idleTime){
|
||||||
System.out.print(String.format("dt: %.3g (%.3g idle) ", dt, idleTime));
|
System.out.print(String.format("dt: %.3g (%.3g idle) ", dt, idleTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -6,18 +6,12 @@ public class LogVec2DSystem extends ECSSystem{
|
|||||||
this.gameEngine = gameEngine;
|
this.gameEngine = gameEngine;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
void init() {}
|
void init() {}
|
||||||
|
|
||||||
@Override
|
|
||||||
void update() {}
|
|
||||||
|
|
||||||
void update(double dt){
|
void update(double dt){
|
||||||
for (Integer entity : entities) {
|
for (Integer entity : entities) {
|
||||||
Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
|
Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
|
||||||
System.out.println(String.format("X: %.6g, Y: %.6g", pos.x, pos.y));
|
System.out.println(String.format("X: %.6g, Y: %.6g", pos.x, pos.y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -6,13 +6,9 @@ public class PhysicsSystem extends ECSSystem{
|
|||||||
this.gameEngine = gameEngine;
|
this.gameEngine = gameEngine;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
void init() {}
|
void init() {}
|
||||||
|
|
||||||
@Override
|
void update(double dt){
|
||||||
void update() {}
|
|
||||||
|
|
||||||
void update(Double dt){
|
|
||||||
for (Integer entity : entities) {
|
for (Integer entity : entities) {
|
||||||
Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
|
Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
|
||||||
RidgidBody ridgidBody = (RidgidBody)gameEngine.getComponentData(entity, RidgidBody.class);
|
RidgidBody ridgidBody = (RidgidBody)gameEngine.getComponentData(entity, RidgidBody.class);
|
@ -14,73 +14,52 @@ import java.util.BitSet;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
/**
|
class SystemManager{
|
||||||
* Manages system-focused aspects, such as ensuring a system has the correct list of current entities.
|
Map<Type, BitSet> signatures = new HashMap<>();
|
||||||
* Manages registration of new systems
|
Map<Type, ECSSystem> systems = new HashMap<>();
|
||||||
* <p>
|
|
||||||
* Do not update data in this class unless you have reviewed the JavaECS source.
|
|
||||||
*/
|
|
||||||
public class SystemManager{
|
|
||||||
public Map<Type, ECSSystem> systems = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
// Registering the system adds it to the array of systems.
|
||||||
* Signals the SystemManager that an entity was destroyed.
|
// In Austin Morlan's implementation, this also creates an instance of the
|
||||||
* Removes the entity from each system's tracked entities
|
// system that can be called from the main thread.
|
||||||
* @param entity the destroyed entity
|
// It returns this object.
|
||||||
*/
|
// In Java, we need to initialise the object first (before this is called),
|
||||||
public void entityDestroyed(Entity entity){
|
// then register that object. Enactment of the system is performed on that
|
||||||
// Unlike components, this isn't simply indexed.
|
// instance.
|
||||||
|
// I.e., create an object that represents the system class; then store that class in the system
|
||||||
|
// table.
|
||||||
|
public boolean registerSystem(Type systemType, ECSSystem system){
|
||||||
|
if (systems.containsKey(systemType)){
|
||||||
|
System.err.println("System already registered");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
systems.put(systemType, system);
|
||||||
|
signatures.put(systemType, new BitSet());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignature(Type system, BitSet registrations){
|
||||||
|
signatures.put(system, registrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void entityDestroyed(int entity){
|
||||||
for (Type key : systems.keySet()) {
|
for (Type key : systems.keySet()) {
|
||||||
systems.get(key).entities.remove(entity);
|
systems.get(key).entities.remove(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void entitySignatureChanged(int entity, BitSet entitySignature){
|
||||||
* Signals the SystemManager that an entity had its registrations changed, so
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
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 (!entitySignature.equals(null)){
|
||||||
BitSet srcCpy = (BitSet)entityRegistrations.clone();
|
BitSet srcCpy = (BitSet)entitySignature.clone();
|
||||||
ECSSystem sys = systems.get(key);
|
srcCpy.and(signatures.get(key));
|
||||||
srcCpy.and(sys.registrationSet);
|
if (srcCpy.equals(signatures.get(key))){ // Bitwise check if the entity is subscribed to this system
|
||||||
if (srcCpy.equals(sys.registrationSet)){ // Bitwise check if the entity is subscribed to this system
|
systems.get(key).entities.add(entity);
|
||||||
sys.entities.add(entity);
|
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
sys.entities.remove(entity);
|
systems.get(key).entities.remove(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the specified system name and system reference
|
|
||||||
* @param systemType the class type 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
|
|
||||||
*/
|
|
||||||
public boolean registerSystem(Type systemType, ECSSystem system){
|
|
||||||
if (systems.containsKey(systemType)){
|
|
||||||
Engine.writeErr("System \'" + systemType.getTypeName() + "\' already registered");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
systems.put(systemType, system);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the registrations the system requires
|
|
||||||
* @param systemType the class type of the system
|
|
||||||
* @param registrations the BitSet containing the required registrations set to true
|
|
||||||
*/
|
|
||||||
public void setSystemRegistraions(Type systemType, BitSet registrations){
|
|
||||||
systems.get(systemType).registrationSet = registrations;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -2,16 +2,13 @@ package nz.ac.massey.javaecs;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
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;
|
||||||
|
|
||||||
@ -20,21 +17,17 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
*/
|
*/
|
||||||
class AppTest {
|
class AppTest {
|
||||||
|
|
||||||
Engine gameEngine;
|
ECS gameEngine;
|
||||||
TestSystem system;
|
TestSystem system;
|
||||||
|
|
||||||
class TestObject{
|
class TestObject{
|
||||||
TestObject() {}
|
|
||||||
TestObject(int i){
|
|
||||||
this.i = i;
|
|
||||||
}
|
|
||||||
int i = 1;
|
int i = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestSystem extends ECSSystem{
|
class TestSystem extends ECSSystem{
|
||||||
public void init(){}
|
public void init(){}
|
||||||
public void update(double dt) {
|
public void update() {
|
||||||
for (Entity entity : entities) {
|
for (Integer entity : entities) {
|
||||||
TestObject entityComponent = (TestObject)gameEngine.getComponentData(entity, TestObject.class);
|
TestObject entityComponent = (TestObject)gameEngine.getComponentData(entity, TestObject.class);
|
||||||
entityComponent.i++;
|
entityComponent.i++;
|
||||||
}
|
}
|
||||||
@ -45,7 +38,7 @@ class AppTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testAssignOne() {
|
void testAssignOne() {
|
||||||
Entity entity = gameEngine.createEntity();
|
int entity = gameEngine.createEntity();
|
||||||
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
||||||
assertEquals(1, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
assertEquals(1, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
||||||
}
|
}
|
||||||
@ -56,7 +49,7 @@ class AppTest {
|
|||||||
@Test
|
@Test
|
||||||
void testAssignMax(){
|
void testAssignMax(){
|
||||||
for (int i = 0; i < gameEngine.getMaxEntities(); i++) {
|
for (int i = 0; i < gameEngine.getMaxEntities(); i++) {
|
||||||
Entity entity = gameEngine.createEntity();
|
int entity = gameEngine.createEntity();
|
||||||
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
||||||
assertEquals(1, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
assertEquals(1, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
||||||
}
|
}
|
||||||
@ -68,12 +61,12 @@ 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(NoSuchElementException.class, () -> {
|
assertThrows(IndexOutOfBoundsException.class, () -> {
|
||||||
gameEngine.createEntity();
|
int entity = gameEngine.createEntity();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
Entity entity = gameEngine.createEntity();
|
int entity = gameEngine.createEntity();
|
||||||
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,7 +77,7 @@ class AppTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testRemoveOne(){
|
void testRemoveOne(){
|
||||||
Entity entity = gameEngine.createEntity();
|
int entity = gameEngine.createEntity();
|
||||||
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
||||||
gameEngine.destroyEntity(entity);
|
gameEngine.destroyEntity(entity);
|
||||||
assertNull(((TestObject)gameEngine.getComponentData(entity, TestObject.class)));
|
assertNull(((TestObject)gameEngine.getComponentData(entity, TestObject.class)));
|
||||||
@ -95,11 +88,11 @@ class AppTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testRunSystem(){
|
void testRunSystem(){
|
||||||
Entity entity = gameEngine.createEntity();
|
int entity = gameEngine.createEntity();
|
||||||
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(0.1);
|
system.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(6, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
assertEquals(6, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
||||||
@ -111,7 +104,7 @@ class AppTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testResizeMinimal(){
|
void testResizeMinimal(){
|
||||||
Entity entity = gameEngine.createEntity();
|
int entity = gameEngine.createEntity();
|
||||||
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
||||||
|
|
||||||
gameEngine.resizeMaximum(10);
|
gameEngine.resizeMaximum(10);
|
||||||
@ -125,12 +118,12 @@ class AppTest {
|
|||||||
@Test
|
@Test
|
||||||
void testLargeCreateDelete(){
|
void testLargeCreateDelete(){
|
||||||
for (int i = 0; i < gameEngine.getMaxEntities(); i++) {
|
for (int i = 0; i < gameEngine.getMaxEntities(); i++) {
|
||||||
Entity entity = gameEngine.createEntity();
|
int entity = gameEngine.createEntity();
|
||||||
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
||||||
assertEquals(1, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
assertEquals(1, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
||||||
}
|
}
|
||||||
for (int i = 256; i < 512; i++) {
|
for (int i = 256; i < 512; i++) {
|
||||||
gameEngine.destroyEntity(Entity.asEntity(i));
|
gameEngine.destroyEntity(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,12 +133,12 @@ class AppTest {
|
|||||||
@Test
|
@Test
|
||||||
void testLargeCreateDeleteResize(){
|
void testLargeCreateDeleteResize(){
|
||||||
for (int i = 0; i < gameEngine.getMaxEntities(); i++) {
|
for (int i = 0; i < gameEngine.getMaxEntities(); i++) {
|
||||||
Entity entity = gameEngine.createEntity();
|
int entity = gameEngine.createEntity();
|
||||||
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
||||||
assertEquals(1, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
assertEquals(1, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
||||||
}
|
}
|
||||||
for (int i = 256; i < 512; i++) {
|
for (int i = 256; i < 512; i++) {
|
||||||
gameEngine.destroyEntity(Entity.asEntity(i));
|
gameEngine.destroyEntity(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
gameEngine.resizeMaximum(1024 - 255);
|
gameEngine.resizeMaximum(1024 - 255);
|
||||||
@ -157,97 +150,24 @@ class AppTest {
|
|||||||
@Test
|
@Test
|
||||||
void testLargeCreateDeleteResizeTooSmall(){
|
void testLargeCreateDeleteResizeTooSmall(){
|
||||||
for (int i = 0; i < gameEngine.getMaxEntities(); i++) {
|
for (int i = 0; i < gameEngine.getMaxEntities(); i++) {
|
||||||
Entity entity = gameEngine.createEntity();
|
int entity = gameEngine.createEntity();
|
||||||
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
||||||
assertEquals(1, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
assertEquals(1, ((TestObject)gameEngine.getComponentData(entity, TestObject.class)).i);
|
||||||
}
|
}
|
||||||
for (int i = 256; i < 512; i++) {
|
for (int i = 256; i < 512; i++) {
|
||||||
gameEngine.destroyEntity(Entity.asEntity(i));
|
gameEngine.destroyEntity(i);
|
||||||
}
|
}
|
||||||
assertFalse(gameEngine.resizeMaximum(512));
|
assertFalse(gameEngine.resizeMaximum(512));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testEntityManagerConstructor(){
|
|
||||||
gameEngine = new Engine(5);
|
|
||||||
assertEquals(5, gameEngine.getMaxEntities());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testAssignToNonExistentEntity(){
|
|
||||||
assertFalse(gameEngine.addComponent(Entity.asEntity(1025), TestObject.class, new TestObject()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testRemoveComponent(){
|
|
||||||
Entity entity = gameEngine.createEntity();
|
|
||||||
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
|
||||||
gameEngine.removeComponent(entity, TestObject.class);
|
|
||||||
assertFalse(gameEngine.entityManager.getRegistrations(entity).get(gameEngine.getComponentIndex(TestObject.class)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testEqualResize(){
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
PrintStream orig = Engine.getErr();
|
|
||||||
PrintStream newErr = new PrintStream(bytes);
|
|
||||||
Engine.setErr(newErr);
|
|
||||||
assertTrue(gameEngine.resizeMaximum(1024));
|
|
||||||
Engine.setErr(orig);
|
|
||||||
newErr.flush(); // ensure the bytes are recieved
|
|
||||||
byte[] errBytes = bytes.toByteArray();
|
|
||||||
String result = new String(errBytes);
|
|
||||||
Engine.writeErr("Captured in redirect: " + result);
|
|
||||||
assertTrue(result.trim().equals("Attempted to set the newSize to the current size"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testLargerResize(){
|
|
||||||
assertTrue(gameEngine.resizeMaximum(2048));
|
|
||||||
assertEquals(2048, gameEngine.getMaxEntities());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testComponentAlreadyRegistered(){
|
|
||||||
assertFalse(gameEngine.registerComponent(TestObject.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testRemoveNonExistentComponentData(){
|
|
||||||
assertFalse(gameEngine.removeComponent(Entity.asEntity(1), TestObject.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testAssignComponentAlreadyAssigned(){
|
|
||||||
Entity entity = gameEngine.createEntity();
|
|
||||||
gameEngine.addComponent(entity, TestObject.class, new TestObject());
|
|
||||||
assertFalse(gameEngine.addComponent(entity, TestObject.class, new TestObject()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testRegisterExistingSystem(){
|
|
||||||
assertFalse(gameEngine.registerSystem(TestSystem.class, new TestSystem()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetRegistrationsOutOfRange(){
|
|
||||||
Entity entity = Entity.asEntity(-1);
|
|
||||||
assertEquals(new BitSet(), gameEngine.entityManager.getRegistrations(entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetRegistrationsUnassignedEntity(){
|
|
||||||
Entity entity = Entity.asEntity(25);
|
|
||||||
assertEquals(new BitSet(), gameEngine.entityManager.getRegistrations(entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establish a simple ECS, with a single system and component
|
* Establish a simple ECS, with a single system and component
|
||||||
*/
|
*/
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void testSetup(){
|
void testSetup(){
|
||||||
gameEngine = new Engine();
|
gameEngine = new ECS();
|
||||||
gameEngine.registerComponent(TestObject.class);
|
gameEngine.registerComponent(TestObject.class);
|
||||||
|
|
||||||
system = new TestSystem();
|
system = new TestSystem();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user