Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e000492b0 | ||
|
|
23abf57b6e | ||
|
|
b1efb9802d | ||
|
|
7524a0096a | ||
|
|
4354eac0cf |
Vendored
+1
-1
@@ -14,7 +14,7 @@
|
||||
"type": "java",
|
||||
"name": "Launch App",
|
||||
"request": "launch",
|
||||
"mainClass": "nz.ac.massey.programming_project_159333_s1_2021.App",
|
||||
"mainClass": "nz.ac.massey.javaecs.App",
|
||||
"projectName": "javaecs"
|
||||
}
|
||||
]
|
||||
|
||||
+6
@@ -1,9 +1,15 @@
|
||||
package nz.ac.massey.javaecs;
|
||||
|
||||
public class FrameRateSystem extends ECSSystem{
|
||||
@Override
|
||||
void init() {}
|
||||
|
||||
@Override
|
||||
void update() {}
|
||||
|
||||
void update(double dt, double idleTime){
|
||||
System.out.print(String.format("dt: %.3g (%.3g idle) ", dt, idleTime));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+6
@@ -6,12 +6,18 @@ public class LogVec2DSystem extends ECSSystem{
|
||||
this.gameEngine = gameEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
void init() {}
|
||||
|
||||
@Override
|
||||
void update() {}
|
||||
|
||||
void update(double dt){
|
||||
for (Integer entity : entities) {
|
||||
Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
|
||||
System.out.println(String.format("X: %.6g, Y: %.6g", pos.x, pos.y));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+5
-1
@@ -6,9 +6,13 @@ public class PhysicsSystem extends ECSSystem{
|
||||
this.gameEngine = gameEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
void init() {}
|
||||
|
||||
void update(double dt){
|
||||
@Override
|
||||
void update() {}
|
||||
|
||||
void update(Double dt){
|
||||
for (Integer entity : entities) {
|
||||
Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
|
||||
RidgidBody ridgidBody = (RidgidBody)gameEngine.getComponentData(entity, RidgidBody.class);
|
||||
+8
-8
@@ -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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>nz.ac.massey.programming_project_159333_s1_2021</groupId>
|
||||
<groupId>nz.ac.massey.javaecs</groupId>
|
||||
<artifactId>javaecs</artifactId>
|
||||
<version>1.1-SNAPSHOT</version>
|
||||
<version>1.2-PRERELEASE</version>
|
||||
<properties>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
@@ -17,10 +17,10 @@
|
||||
<maven-javadoc-plugin.version>3.0.0</maven-javadoc-plugin.version>
|
||||
<coveralls-maven-plugin.version>4.3.0</coveralls-maven-plugin.version>
|
||||
<!-- JaCoCo thresholds. Increase gradually as you add tests. -->
|
||||
<jacoco.unit-tests.limit.instruction-ratio>0%</jacoco.unit-tests.limit.instruction-ratio>
|
||||
<jacoco.unit-tests.limit.branch-ratio>0%</jacoco.unit-tests.limit.branch-ratio>
|
||||
<jacoco.unit-tests.limit.class-complexity>20</jacoco.unit-tests.limit.class-complexity>
|
||||
<jacoco.unit-tests.limit.method-complexity>5</jacoco.unit-tests.limit.method-complexity>
|
||||
<jacoco.unit-tests.limit.instruction-ratio>75%</jacoco.unit-tests.limit.instruction-ratio>
|
||||
<jacoco.unit-tests.limit.branch-ratio>50%</jacoco.unit-tests.limit.branch-ratio>
|
||||
<jacoco.unit-tests.limit.class-complexity>30</jacoco.unit-tests.limit.class-complexity>
|
||||
<jacoco.unit-tests.limit.method-complexity>10</jacoco.unit-tests.limit.method-complexity>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -134,7 +134,7 @@
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<!-- <execution>
|
||||
<execution>
|
||||
<id>check-unit-test</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
@@ -180,7 +180,7 @@
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution> -->
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
@@ -34,7 +34,7 @@ class ComponentArray{
|
||||
|
||||
public void removeData(int entity){
|
||||
if (!entityComponentDataMap.containsKey(entity)){
|
||||
System.err.println("Attempted to remove non-existent entity");
|
||||
ECS.writeErr("Attempted to remove non-existent entity");
|
||||
return;
|
||||
}
|
||||
// Get the componentData index of the entity
|
||||
@@ -55,7 +55,7 @@ class ComponentArray{
|
||||
|
||||
public void insertData(int entity, Object component){
|
||||
if (entityComponentDataMap.containsKey(entity)){
|
||||
System.err.println("Entity is already subscribed to the component");
|
||||
ECS.writeErr("Entity is already subscribed to the component");
|
||||
return;
|
||||
}
|
||||
int index = componentArray.size();
|
||||
@@ -78,7 +78,7 @@ class ComponentArray{
|
||||
|
||||
public Object getData(int entity){
|
||||
if (!entityComponentDataMap.containsKey(entity)){
|
||||
System.err.println("Attempted to retrieve non-existent data");
|
||||
ECS.writeErr("Attempted to retrieve non-existent data");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,14 @@ class ComponentManager{
|
||||
Map<Type, Integer> componentPosIndex = new HashMap<>();
|
||||
int componentPos = 0;
|
||||
|
||||
public void registerComponent(Type type){
|
||||
public boolean registerComponent(Type type){
|
||||
if (componentArrays.containsKey(type)){
|
||||
System.err.println("Component " + type.getTypeName() + " is already registered");
|
||||
return;
|
||||
ECS.writeErr("Component " + type.getTypeName() + " is already registered");
|
||||
return false;
|
||||
}
|
||||
componentArrays.put(type, new ComponentArray());
|
||||
componentPosIndex.put(type, componentPos++);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addComponentToEntity(Type componentName, Object componentData, int entity){
|
||||
|
||||
@@ -12,6 +12,7 @@ package nz.ac.massey.javaecs;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.BitSet;
|
||||
/**
|
||||
@@ -21,6 +22,10 @@ import java.util.BitSet;
|
||||
* for documentation and more information.
|
||||
*/
|
||||
public class ECS {
|
||||
// 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;
|
||||
@@ -36,7 +41,7 @@ public class ECS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the ECS with the specified value(s)
|
||||
* Initialises the ECS with the specified value
|
||||
* @param maxEntities the maximum number of entities to allow
|
||||
*/
|
||||
public ECS(int maxEntities){
|
||||
@@ -80,8 +85,8 @@ public class ECS {
|
||||
* 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);
|
||||
boolean registerComponent(Type type){
|
||||
return componentManager.registerComponent(type);
|
||||
}
|
||||
|
||||
Integer getComponentIndex(Type type){
|
||||
@@ -94,10 +99,14 @@ public class ECS {
|
||||
* @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));
|
||||
boolean addComponent(int entity, Type componentName, Object component){
|
||||
if (entityManager.registerComponent(componentManager.getComponentIndex(componentName), entity))
|
||||
{
|
||||
systemManager.entityRegistrationsChanged(entity, entityManager.getRegistrations(entity));
|
||||
componentManager.addComponentToEntity(componentName, component, entity);
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,10 +114,14 @@ public class ECS {
|
||||
* @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));
|
||||
boolean removeComponent(int 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,8 +140,8 @@ public class ECS {
|
||||
* @param systemName
|
||||
* @param action
|
||||
*/
|
||||
void registerSystem(Type systemType, ECSSystem action){
|
||||
systemManager.registerSystem(systemType, action);
|
||||
boolean registerSystem(Type systemType, ECSSystem action){
|
||||
return systemManager.registerSystem(systemType, action);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,9 +150,47 @@ public class ECS {
|
||||
* @param signature the new signature data
|
||||
*/
|
||||
void setSystemSignature(Type system, BitSet signature){
|
||||
systemManager.setSignature(system, signature);
|
||||
systemManager.setRegistrationSignature(system, signature);
|
||||
}
|
||||
Integer getMaxEntities(){
|
||||
return entityManager.currentSize;
|
||||
}
|
||||
|
||||
// Encapsulate syserr writes so they may be redirected out of the lib
|
||||
|
||||
/**
|
||||
* Writes an error message to the set PrintStream
|
||||
* <p>
|
||||
* <i>Default 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
|
||||
*/
|
||||
static void setErr(PrintStream newErr){
|
||||
errorStream = newErr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current error PrintStream
|
||||
* @return the current PrintStream that errors are written to
|
||||
*/
|
||||
static PrintStream getErr(){
|
||||
return errorStream;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,10 @@ package nz.ac.massey.javaecs;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
class ECSSystem{
|
||||
abstract class ECSSystem{
|
||||
Set<Integer> entities = new HashSet<>();
|
||||
|
||||
// Abstractions are included to remind the library user these two implementations (and/or variations) should be implemented
|
||||
abstract void init();
|
||||
abstract void update();
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class EntityManager{
|
||||
|
||||
public Integer addEntity(){
|
||||
if (unusedEntities.size() == 0){
|
||||
System.err.println("No available space to create a new entity");
|
||||
ECS.writeErr("No available space to create a new entity");
|
||||
return -1;
|
||||
}
|
||||
return unusedEntities.remove();
|
||||
@@ -64,16 +64,36 @@ class EntityManager{
|
||||
entityRegistrations.get(entity).clear();
|
||||
}
|
||||
|
||||
public void registerComponent(int component, int entity){
|
||||
public boolean registerComponent(int component, int entity){
|
||||
if (entity >= currentSize){
|
||||
System.err.println("Attempted to assign a component to non-existent entity: " + entity);
|
||||
return;
|
||||
ECS.writeErr("Attempted to assign a component to non-existent entity: " + entity);
|
||||
return false;
|
||||
}
|
||||
else if (entityRegistrations.get(entity).get(component))
|
||||
{
|
||||
ECS.writeErr("Entity is already assigned to the component");
|
||||
return false;
|
||||
}
|
||||
else{
|
||||
entityRegistrations.get(entity).set(component);
|
||||
return true;
|
||||
}
|
||||
entityRegistrations.get(entity).set(component);
|
||||
}
|
||||
|
||||
public void unregisterComponent(int component, int entity){
|
||||
entityRegistrations.get(entity).clear(component);
|
||||
public boolean unregisterComponent(int component, int entity){
|
||||
try{
|
||||
if (entityRegistrations.get(entity).get(component))
|
||||
{
|
||||
entityRegistrations.get(entity).clear(component);
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
catch (IndexOutOfBoundsException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public BitSet getRegistrations(int entity){
|
||||
@@ -86,11 +106,11 @@ class EntityManager{
|
||||
|
||||
public boolean resize(int newSize, SystemManager systemManager, ComponentManager componentManager){
|
||||
if (newSize < currentSize - unusedEntities.size()){
|
||||
System.err.println("Attempted to resize the maximum entity count to a number smaller than the current assigned entity count.");
|
||||
ECS.writeErr("Attempted to resize the maximum entity count to a number smaller than the current assigned entity count.");
|
||||
return false;
|
||||
}
|
||||
else if (newSize == currentSize){
|
||||
System.err.println("Attempted to set the newSize to the current size");
|
||||
ECS.writeErr("Attempted to set the newSize to the current size");
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
@@ -110,7 +130,7 @@ class EntityManager{
|
||||
setRegistrations(newPos, getRegistrations(integer));
|
||||
// Invoke an update in the SystemManager
|
||||
systemManager.entityDestroyed(integer);
|
||||
systemManager.entitySignatureChanged(newPos, getRegistrations(newPos));
|
||||
systemManager.entityRegistrationsChanged(newPos, getRegistrations(newPos));
|
||||
// Invoke the change in the components
|
||||
componentManager.moveComponentData(integer, newPos);
|
||||
componentManager.entityDestroyed(integer);
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
class SystemManager{
|
||||
Map<Type, BitSet> signatures = new HashMap<>();
|
||||
Map<Type, BitSet> registrationSignatures = new HashMap<>();
|
||||
Map<Type, ECSSystem> systems = new HashMap<>();
|
||||
|
||||
// Registering the system adds it to the array of systems.
|
||||
@@ -29,16 +29,16 @@ class SystemManager{
|
||||
// table.
|
||||
public boolean registerSystem(Type systemType, ECSSystem system){
|
||||
if (systems.containsKey(systemType)){
|
||||
System.err.println("System already registered");
|
||||
ECS.writeErr("System already registered");
|
||||
return false;
|
||||
}
|
||||
systems.put(systemType, system);
|
||||
signatures.put(systemType, new BitSet());
|
||||
registrationSignatures.put(systemType, new BitSet());
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setSignature(Type system, BitSet registrations){
|
||||
signatures.put(system, registrations);
|
||||
public void setRegistrationSignature(Type systemType, BitSet registrations){
|
||||
registrationSignatures.put(systemType, registrations);
|
||||
}
|
||||
|
||||
public void entityDestroyed(int entity){
|
||||
@@ -47,13 +47,13 @@ class SystemManager{
|
||||
}
|
||||
}
|
||||
|
||||
public void entitySignatureChanged(int entity, BitSet entitySignature){
|
||||
public void entityRegistrationsChanged(int entity, BitSet entityRegistrations){
|
||||
for (Type key : systems.keySet()) {
|
||||
// Check if the signature is null
|
||||
if (!entitySignature.equals(null)){
|
||||
BitSet srcCpy = (BitSet)entitySignature.clone();
|
||||
srcCpy.and(signatures.get(key));
|
||||
if (srcCpy.equals(signatures.get(key))){ // Bitwise check if the entity is subscribed to this system
|
||||
if (!entityRegistrations.equals(null)){
|
||||
BitSet srcCpy = (BitSet)entityRegistrations.clone();
|
||||
srcCpy.and(registrationSignatures.get(key));
|
||||
if (srcCpy.equals(registrationSignatures.get(key))){ // Bitwise check if the entity is subscribed to this system
|
||||
systems.get(key).entities.add(entity);
|
||||
}
|
||||
else{
|
||||
|
||||
@@ -2,12 +2,18 @@ package nz.ac.massey.javaecs;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -21,6 +27,10 @@ class AppTest {
|
||||
TestSystem system;
|
||||
|
||||
class TestObject{
|
||||
TestObject() {}
|
||||
TestObject(int i){
|
||||
this.i = i;
|
||||
}
|
||||
int i = 1;
|
||||
}
|
||||
|
||||
@@ -62,7 +72,7 @@ class AppTest {
|
||||
for (int i = 0; i < gameEngine.getMaxEntities() + 5; i++) {
|
||||
if (i >= gameEngine.getMaxEntities()){
|
||||
assertThrows(IndexOutOfBoundsException.class, () -> {
|
||||
int entity = gameEngine.createEntity();
|
||||
gameEngine.createEntity();
|
||||
});
|
||||
}
|
||||
else{
|
||||
@@ -161,6 +171,67 @@ class AppTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEntityManagerConstructor(){
|
||||
gameEngine = new ECS(5);
|
||||
assertEquals(5, gameEngine.getMaxEntities());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssignToNonExistentEntity(){
|
||||
assertFalse(gameEngine.addComponent(1025, TestObject.class, new TestObject()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveComponent(){
|
||||
int 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 = System.err;
|
||||
PrintStream newErr = new PrintStream(bytes);
|
||||
System.setErr(newErr);
|
||||
assertTrue(gameEngine.resizeMaximum(1024));
|
||||
System.setErr(orig);
|
||||
newErr.flush(); // ensure the bytes are recieved
|
||||
byte[] errBytes = bytes.toByteArray();
|
||||
String result = new String(errBytes);
|
||||
System.err.println("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(1, TestObject.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssignComponentAlreadyAssigned(){
|
||||
int 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()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish a simple ECS, with a single system and component
|
||||
|
||||
Reference in New Issue
Block a user