Skip to content

Commit

Permalink
added signals for when an entity is added or removed to a family
Browse files Browse the repository at this point in the history
  • Loading branch information
alexdmiller committed Jun 14, 2014
1 parent 589056f commit 983badd
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 18 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"uglify-js" : "2.*",
"browserify" : "1.17.3",
"mocha" : "1.8.1",
"should" : "1.2.1"
"should" : "1.2.1",
"sinon" : "1.10.2"
},
"repository" : {
"type" : "git",
Expand Down
21 changes: 19 additions & 2 deletions src/family.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var Class = require('./class'),
EntityList = require('./entitylist');
EntityList = require('./entitylist'),
Signal = require('./signal');

/**
* The family is a collection of entities having all the specified components.
Expand All @@ -21,6 +22,18 @@ var Family = module.exports = Class.extend({
* @private
*/
this._entities = new EntityList();

/**
* @public
* @readonly
*/
this.entityAdded = new Signal();

/**
* @public
* @readonly
*/
this.entityRemoved = new Signal();
},

/**
Expand All @@ -40,6 +53,7 @@ var Family = module.exports = Class.extend({
addEntityIfMatch: function (entity) {
if (!this._entities.has(entity) && this._matchEntity(entity)) {
this._entities.add(entity);
this.entityAdded.emit(entity);
}
},

Expand All @@ -50,7 +64,10 @@ var Family = module.exports = Class.extend({
* @param {Entity} entity
*/
removeEntity: function (entity) {
this._entities.remove(entity);
if (this._entities.has(entity)) {
this._entities.remove(entity);
this.entityRemoved.emit(entity);
}
},

/**
Expand Down
8 changes: 8 additions & 0 deletions src/system.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ var System = module.exports = Class.extend({
this.world = null;
},

addedToWorld: function(world) {
this.world = world;
},

removedFromWorld: function(world) {
this.world = null;
},

/**
* Update the entities.
* @public
Expand Down
74 changes: 60 additions & 14 deletions src/world.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ var World = module.exports = Class.extend({
* @param {System} system
*/
addSystem: function (system) {
system.world = this;
this._systems.push(system);
system.addedToWorld(this);
return this;
},

Expand All @@ -51,6 +51,7 @@ var World = module.exports = Class.extend({
for (i = 0, len = systems.length; i < len; ++i) {
if (systems[i] === system) {
systems.splice(i, 1);
system.removedFromWorld();
}
}
},
Expand Down Expand Up @@ -107,21 +108,12 @@ var World = module.exports = Class.extend({
* @return {Array} an array of entities.
*/
getEntities: function (/* componentNames */) {
var familyId, families, node;
var familyId, families;

familyId = '$' + Array.prototype.join.call(arguments, ',');
families = this._families;

if (!families[familyId]) {
families[familyId] = new Family(
Array.prototype.slice.call(arguments)
);
for (node = this._entities.head; node; node = node.next) {
families[familyId].addEntityIfMatch(node.entity);
}
}
familyId = this._getFamilyId(arguments);
this._ensureFamilyExists(arguments);

return families[familyId].getEntities();
return this._families[familyId].getEntities();
},

/**
Expand All @@ -138,6 +130,60 @@ var World = module.exports = Class.extend({
}
},

/**
* Returns the signal for entities added with the specified components. The
* signal is also emitted when a component is added to an entity causing it
* match the specified component names.
* @public
* @param {...String} componentNames
* @return {Signal} A signal which is emitted every time an entity with
* specified components is added.
*/
entityAdded: function(/* componentNames */) {
var familyId, families;

familyId = this._getFamilyId(arguments);
this._ensureFamilyExists(arguments);

return this._families[familyId].entityAdded;
},

/**
* Returns the signal for entities removed with the specified components.
* The signal is also emitted when a component is removed from an entity
* causing it to no longer match the specified component names.
* @public
* @param {...String} componentNames
* @return {Signal} A signal which is emitted every time an entity with
* specified components is removed.
*/
entityRemoved: function(/* componentNames */) {
var familyId, families;

familyId = this._getFamilyId(arguments);
this._ensureFamilyExists(arguments);

return this._families[familyId].entityRemoved;
},

_ensureFamilyExists: function(components) {
var families = this._families;
var familyId = this._getFamilyId(components);

if (!families[familyId]) {
families[familyId] = new Family(
Array.prototype.slice.call(components)
);
for (var node = this._entities.head; node; node = node.next) {
families[familyId].addEntityIfMatch(node.entity);
}
}
},

_getFamilyId: function(components) {
return '$' + Array.prototype.join.call(components, ',');
},

/**
* Handler to be called when a component is added to an entity.
* @private
Expand Down
87 changes: 86 additions & 1 deletion test/world.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*globals describe: true, it: true */

var CES = require('../'),
var sinon = require('sinon'),
CES = require('../'),
CompA = CES.Component.extend({ name: 'a' }),
CompB = CES.Component.extend({ name: 'b' }),
CompC = CES.Component.extend({ name: 'c' });
Expand Down Expand Up @@ -85,4 +86,88 @@ describe('world', function () {
world.getEntities('a', 'b', 'c').length.should.equal(99);
world.getEntities('a', 'b').length.should.equal(100);
});

it('should emit signal when entity with one component is added', function() {
var world = new CES.World();

var aListener = sinon.spy();
var bListener = sinon.spy();
world.entityAdded('a').add(aListener);
world.entityAdded('b').add(bListener);

var entity = new CES.Entity();
entity.addComponent(new CompA());
world.addEntity(entity);

aListener.calledOnce.should.be.true;
bListener.calledOnce.should.be.false;
});

it('should emit signal when entity with two components is added', function() {
var world = new CES.World();

var aListener = sinon.spy();
var abListener = sinon.spy();
var cListener = sinon.spy();

world.entityAdded('a').add(aListener);
world.entityAdded('a', 'b').add(abListener);
world.entityAdded('c').add(abListener);

var entity = new CES.Entity();
entity.addComponent(new CompA());
entity.addComponent(new CompB());
world.addEntity(entity);

aListener.calledOnce.should.be.true;
abListener.calledOnce.should.be.true;
cListener.calledOnce.should.be.false;
});

it('should emit signal when entity is removed', function() {
var world = new CES.World();

var aListener = sinon.spy();
var bListener = sinon.spy();
world.entityRemoved('a').add(aListener);
world.entityRemoved('b').add(bListener);

var entity = new CES.Entity();
entity.addComponent(new CompA());
world.addEntity(entity);

aListener.calledOnce.should.be.false;
bListener.calledOnce.should.be.false;

world.removeEntity(entity);

aListener.calledOnce.should.be.true;
bListener.calledOnce.should.be.false;
});

describe('with system', function() {
it('addToWorld should be called when system is added', function() {
var world = new CES.World();
var system = new CES.System();
var addedToWorld = sinon.spy(system, 'addedToWorld');

world.addSystem(system);

addedToWorld.calledOnce.should.be.true;
});

it('addToWorld should be called when system is removed', function() {
var world = new CES.World();
var system = new CES.System();
var removedFromWorld = sinon.spy(system, 'removedFromWorld');

world.addSystem(system);

removedFromWorld.calledOnce.should.be.false;

world.removeSystem(system);

removedFromWorld.calledOnce.should.be.true;
});
})
});

0 comments on commit 983badd

Please sign in to comment.