Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide Unit Test for Issue #214 Engine.getEntitiesFor() during EntityListener.entityRemoved #217

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 285 additions & 0 deletions ashley/tests/com/badlogic/ashley/core/PooledEngineTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import static org.junit.Assert.*;

import com.badlogic.ashley.systems.IteratingSystem;
import org.junit.Test;

import com.badlogic.ashley.signals.Listener;
Expand Down Expand Up @@ -100,6 +101,129 @@ public void reset () {
}
}

private static class UniquePooledCompnentA implements Component, Poolable {
public int count = 1;

@Override
public void reset() {
count = 1;
}
}

private static class UniquePooledCompnentB implements Component, Poolable {
public float value = 1f;

@Override
public void reset() {
value = 1f;
}
}

private class RemoveAFromBSystem extends IteratingSystem{

final int numberToRemove;
int numberRemoved = 0;
public RemoveAFromBSystem(int numberToRemove){
super(Family.all(UniquePooledCompnentA.class, UniquePooledCompnentB.class).get());
this.numberToRemove = numberToRemove;
}

//Removes one each update
@Override
public void update(float deltaTime) {
super.update(deltaTime);
numberRemoved = 0;
}

@Override
protected void processEntity(Entity entity, float deltaTime) {
if(numberRemoved < numberToRemove){
if(entity.hasComponent(ComponentType.getFor(UniquePooledCompnentA.class))){
getEngine().removeEntity(entity);
//entity.remove(UniquePooledCompnentA.class);
numberRemoved++;
}
}
}
}

private interface IAssertEntityInFamily{
public void assertAgainst(Entity entity);
}

private class ListeningAndLookingForASystem extends IteratingSystem{

EntityListener el;
IAssertEntityInFamily asserter;
final Family targetFamily;
public ListeningAndLookingForASystem(Family familyToListenOn, IAssertEntityInFamily entityAsserter){
super(familyToListenOn);
this.asserter = entityAsserter;
this.targetFamily = familyToListenOn;
}

@Override
public void addedToEngine(Engine engine) {
super.addedToEngine(engine);

final Engine eg = engine;
if(el == null){
el = new EntityListener() {
@Override
public void entityAdded(Entity entity) {

}

@Override
public void entityRemoved(Entity entity) {
for(Entity e : eg.getEntitiesFor(Family.all(UniquePooledCompnentA.class).get())){
asserter.assertAgainst(e);
}
}
};
}
engine.addEntityListener(targetFamily, el);
}

@Override
protected void processEntity(Entity entity, float deltaTime) {

}
}

private class GenerateABSystem extends EntitySystem{

int numberToAdd = 1;
public GenerateABSystem(int numberToAdd){
super();
this.numberToAdd = numberToAdd;
}

//Removes one each update
@Override
public void update(float deltaTime) {
super.update(deltaTime);
for(int i=0;i<numberToAdd;i++) {
PooledEngine engine = (PooledEngine) getEngine();

//Try to use up a pooled entity on a non-matching
// entity
Entity bOnly = engine.createEntity();
bOnly.add(engine.createComponent(UniquePooledCompnentB.class));
engine.addEntity(bOnly);

Entity ab = engine.createEntity();
ab.add(engine.createComponent(UniquePooledCompnentA.class));
ab.add(engine.createComponent(UniquePooledCompnentB.class));
engine.addEntity(ab);
}


}

}


@Test
public void entityRemovalListenerOrder () {
PooledEngine engine = new PooledEngine();
Expand Down Expand Up @@ -247,4 +371,165 @@ public void recycleComponent() {

engine.removeAllEntities();
}

@Test
public void getEntitiesForFamilyReturnsOnlyEntitiesInFamily(){

final PooledEngine engine = new PooledEngine();

int aCount = 2;
int bCount = 3;

for(int i = 0;i<aCount;i++){
Entity a = engine.createEntity();
a.add(engine.createComponent(UniquePooledCompnentA.class));
engine.addEntity(a);
}

for(int i = 0;i<bCount;i++){
Entity b = engine.createEntity();
b.add(engine.createComponent(UniquePooledCompnentA.class));
b.add(engine.createComponent(UniquePooledCompnentB.class));
engine.addEntity(b);
}

engine.addSystem(new RemoveAFromBSystem(1));

assertEquals(5, engine.getEntitiesFor(Family.all(UniquePooledCompnentA.class).get()).size());
engine.update(0.16f);
assertEquals(4, engine.getEntitiesFor(Family.all(UniquePooledCompnentA.class).get()).size());
engine.update(0.16f);
assertEquals(3, engine.getEntitiesFor(Family.all(UniquePooledCompnentA.class).get()).size());
engine.update(0.16f);
assertEquals(2, engine.getEntitiesFor(Family.all(UniquePooledCompnentA.class).get()).size());

//From here, the system family shouldn't find the 2 A-only entities
engine.update(0.16f);
assertEquals(2, engine.getEntitiesFor(Family.all(UniquePooledCompnentA.class).get()).size());
engine.update(0.16f);
assertEquals(2, engine.getEntitiesFor(Family.all(UniquePooledCompnentA.class).get()).size());
}


@Test
public void getEntitiesForInEntityListenerShouldReturnOnlyEntitisInFamily(){

final PooledEngine engine = new PooledEngine();

//Flexible so we can check at volumes, and high churn
int aCount = 200;
int bCount = 400;
int total = aCount + bCount;
int addsPerIteration = 5;
int removesPerIteration = 6;
int changePerIteration = removesPerIteration - addsPerIteration;

for(int i = 0;i<aCount;i++){
Entity a = engine.createEntity();
a.add(engine.createComponent(UniquePooledCompnentA.class));
engine.addEntity(a);
}

for(int i = 0;i<bCount;i++){
Entity b = engine.createEntity();
b.add(engine.createComponent(UniquePooledCompnentA.class));
b.add(engine.createComponent(UniquePooledCompnentB.class));
engine.addEntity(b);
}

engine.addSystem(new GenerateABSystem(addsPerIteration)); //Adds 2
engine.addSystem(new RemoveAFromBSystem(removesPerIteration));
engine.addSystem(new ListeningAndLookingForASystem(Family.all(UniquePooledCompnentB.class).get(), new IAssertEntityInFamily() {
@Override
public void assertAgainst(Entity entity) {
assertTrue("Entity does not have value", entity.hasComponent(ComponentType.getFor(UniquePooledCompnentA.class)));
}
}));

int iterations = Math.abs(bCount/changePerIteration);
for(int i=0;i<iterations;i++){
int expectedTotal = total-((changePerIteration)*i);

assertEquals("Before Update Total should be right on iteration " + i, expectedTotal, engine.getEntitiesFor(Family.all(UniquePooledCompnentA.class).get()).size());
engine.update(0.16f);
assertEquals("After Update Total Should be right on iteration " + i, expectedTotal - changePerIteration, engine.getEntitiesFor(Family.all(UniquePooledCompnentA.class).get()).size());
}
}


private static class FollowerComponent implements Component, Poolable{

@Override
public void reset() {

}
}

private static class EnemyComponent implements Component, Poolable{
@Override
public void reset() {

}
}

private static class OtherComponent implements Component, Poolable{

@Override
public void reset() {

}
}

@Test
public void removeDuringEntityRemovedHandledOk(){
final PooledEngine engine = new PooledEngine();

final Entity enemy = engine.createEntity();
enemy.add(engine.createComponent(EnemyComponent.class));
engine.addEntity(enemy);

final Entity otherFollower = engine.createEntity();
otherFollower.add(engine.createComponent(FollowerComponent.class));
otherFollower.add(engine.createComponent(OtherComponent.class));
engine.addEntity(otherFollower);

final Entity enemyWithFollower = engine.createEntity();
enemyWithFollower.add(engine.createComponent(EnemyComponent.class));
enemyWithFollower.add(engine.createComponent(FollowerComponent.class));
engine.addEntity(enemyWithFollower);


engine.addEntityListener(Family.all(EnemyComponent.class).get(), new EntityListener() {
private boolean isMidRemoval = false;
private ComponentMapper<FollowerComponent> fm = ComponentMapper.getFor(FollowerComponent.class);
@Override
public void entityAdded(Entity entity) {

}

@Override
public void entityRemoved(Entity entity) {
ImmutableArray<Entity> followers = engine.getEntitiesFor(Family.all(FollowerComponent.class).get());

for(Entity follower: followers){
FollowerComponent fc = fm.get(follower);
assertTrue("Follower Component is not defined for entity matching Family.all(FollowerComponent.class)", fc != null);
//UNCOMMENT THIS TO REPRODUCE ISSUE #214
//follower.removeAll();
engine.removeEntity(follower);
}
}
});

engine.addSystem(new EntitySystem() {
@Override
public void update(float deltaTime) {
super.update(deltaTime);
getEngine().removeEntity(enemy);
}
});

engine.update(0.16f);
engine.update(0.16f);
}
}