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

Cache #4

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from 2 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
47 changes: 44 additions & 3 deletions lib/entity_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ function EntityManager(components, storage) {

this._entityPool = []
this._entityCounter = 0

this._entityCache = {}
this._entityCache[0] = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be optimized:

this._entityCache = FastMap()
this._entityCache[0] = []

See https://gist.github.com/ooflorent/c58c9a0f08923973a5e3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So do you suggest I add fast.js to the project and require it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Next to utils.js.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting:

/home/user/Projects/mq_makr/makr/lib/fast.js:10
  ForceEfficientMap.prototype = self
  ^^^^^^^^^^^^^^^^^
SyntaxError: Unexpected identifier

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed on the gist.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wow didn't notice the missing brackets

}

EntityManager.prototype.create = function() {
Expand All @@ -37,6 +40,8 @@ EntityManager.prototype.create = function() {
var entity = this._entityInst[id]
this._entityFlag[id] = ENTITY_ALIVE

entity._cacheIndex = this._entityCache[0].push(entity) - 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not add anything to the entity.
Instead create an Int32Array to store indexes like _entityMask.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.


return entity
}

Expand All @@ -55,9 +60,12 @@ EntityManager.prototype.query = function() {
}

var result = []
for (var id = 0; id < this._entityCounter; id++) {
if (this._entityFlag[id] === ENTITY_ALIVE && (this._entityMask[id] & mask) === mask) {
result.push(this._entityInst[id])

var entityMasks = Object.keys(this._entityCache)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a real performance killer.
If we have 32 components it may generate a lot of different masks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will add something that removes the empty ones.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Want me to fix this before the merge?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! But I won't merge it immediately. I want to add more tests and benchmarks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried two approaches so far, changing the property descriptor to enumerable: false so it doesn't appear in Object.keys(). This resulted in quite bad performance when adding/removing components.

Also tried simply deleting the property which I know is a bad thing to do on objects. But the performance is much better. 10x faster when adding components, 100x faster when removing them.

Overall it increased the query performance by roughly 1.5-2x on 1000 entities.

Any other ideas on how to do this?

for (var index = 0; index < entityMasks.length; index++) {
var entityMask = entityMasks[index]
if((entityMask & mask) === mask) {
Array.prototype.push.apply(result, this._entityCache[entityMask])
}
}

Expand All @@ -84,13 +92,21 @@ EntityManager.prototype._accomodate = function(id) {
EntityManager.prototype._add = function(id, component) {
var ctor = component.constructor
var index = this._components.index(ctor)

this._cacheRemove(id)
this._entityMask[id] |= 1 << index
this._cacheAdd(id)
this._storage.set(id, index, component)

}

EntityManager.prototype._remove = function(id, C) {
var index = this._components.index(C)

this._cacheRemove(id)
this._entityMask[id] &= ~(1 << index)
this._cacheAdd(id)

this._storage.delete(id, index)
}

Expand All @@ -104,7 +120,32 @@ EntityManager.prototype._get = function(id, C) {
return this._storage.get(id, index)
}

EntityManager.prototype._cacheAdd = function(id) {
var entity = this._entityInst[id]
var cache = this._entityCache[this._entityMask[id]]
if (cache === undefined) {
this._entityCache[this._entityMask[id]] = []
}
entity._cacheIndex = this._entityCache[this._entityMask[id]].push(entity) - 1
}

EntityManager.prototype._cacheRemove = function(id) {
var entity = this._entityInst[id]
var cache = this._entityCache[this._entityMask[id]]
if (cache.length === 1 || entity._cacheIndex === cache.length - 1) {
cache.pop()
} else {
var lastCacheElement = cache.pop()
lastCacheElement._cacheIndex = entity._cacheIndex
cache[lastCacheElement._cacheIndex] = lastCacheElement
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if lastCacheElement is entity?
Does it work as expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entity._cacheIndex === cache.length - 1
This is the check for it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add tests to check that it returns the correct entities though.

}
entity._cacheIndex = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not mix types. Cache index must be an integer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. it isn't necessary to make it false.

}

EntityManager.prototype._destroy = function(id) {
if (this._entityFlag[id] !== ENTITY_DEAD) {
this._cacheRemove(id)
}
this._entityFlag[id] = ENTITY_DEAD
this._entityMask[id] = 0
this._entityPool.push(id)
Expand Down