import { isArray, isString, isNonEmptyString, isObject } from './helpers';
import stampit from 'stampit';
import FastBitset from 'fastbitset';
import FastArray from 'fast-array';
/**
* Used to perform matching of entities.
*
* @example
* //matches entities with 'Position' and 'Velocity' components
* const q1 = Query({
* criterions: ["Position", "Velocity"],
* });
*
* //matches entities with 'Position' and 'Velocity' components and without 'Sprite' component
* const q2 = Query({
* criterions: {
* include: ["Position", "Velocity"],
* exclude: ["Sprite"],
* },
* });
*
* //matches entities of type 'Ball'
* const q3 = Query({
* criterions: {
* entityType: "Ball",
* },
* });
*
* @class Query
* @param {Object} opts
* @param {Object|Array} opts.criterions query criterions
*/
const Query = stampit({
deepProps: {
_shouldUpdate: false,
},
init(opts) {
let include = [];
let exclude = [];
let includeBitset;
let excludeBitset;
if (isArray(opts.criterions)) {
include = opts.criterions;
} else if (isObject(opts.criterions)) {
if (isNonEmptyString(opts.criterions.entityType)) {
this._matchType = opts.criterions.entityType;
}
if (isArray(opts.criterions.include)) {
include = opts.criterions.include;
}
if (isArray(opts.criterions.exclude)) {
exclude = opts.criterions.exclude;
}
}
if (include.length > 0) {
includeBitset = new FastBitset();
for (let i = 0; i < include.length; i += 1) {
includeBitset.add(opts.componentsIdsMap[include[i]]);
}
}
if (exclude) {
excludeBitset = new FastBitset();
for (let e = 0; e < exclude.length; e += 1) {
excludeBitset.add(opts.componentsIdsMap[exclude[e]]);
}
}
this._entitiesIndex = FastArray();
this._matchedEntities = FastArray();
this._result = {
entities: this._matchedEntities.arr,
length: 0,
};
this._includes = includeBitset;
this._excludes = excludeBitset;
},
methods: {
/**
* Initializes query. Builds initial entities index.
*
* @public
* @memberof Query#
* @method initialize
* @param {FastArray} allEntities fast array of all entities present in the engine
*/
initialize(allEntities) {
for (let i = 0; i < allEntities.length; i += 1) {
const entity = allEntities.arr[i];
if (entity !== 0 && this.satisfiedBy(entity)) {
this.addToIndex(entity.id);
}
}
this.update(allEntities);
},
/**
* Checks if entity satisfies query criterions.
*
* @public
* @memberof Query#
* @method satisfiedBy
* @param {Entity} entity entity to check
*/
satisfiedBy(entity) {
let satisfies = true;
if (isString(this._matchType)) {
satisfies = entity.type === this._matchType;
}
if (satisfies && this._includes) {
satisfies = this._includes.intersection_size(entity.bitset) === this._includes.size();
}
if (satisfies && this._excludes) {
satisfies = this._excludes.intersection_size(entity.bitset) === 0;
}
return satisfies;
},
/**
* Checks if query should update.
*
* Query should update when entities matching its criterions are added or removed from engine.
* It should also update when entity that was matching criterions has changed and doesn't match it anymore.
*
* @public
* @memberof Query#
* @method shouldUpdate
*/
shouldUpdate() {
return this._shouldUpdate;
},
/**
* Adds entity ID to query index.
*
* @public
* @memberof Query#
* @method addToIndex
* @param {Number} entityId
*/
addToIndex(entityId) {
this._shouldUpdate = true;
this._entitiesIndex.push(entityId);
},
/**
* Removes entity ID from query index.
*
* @public
* @memberof Query#
* @method removeFromIndex
* @param {Number} entityId
*/
removeFromIndex(entityId) {
const indexOfEntity = this._entitiesIndex.indexOf(entityId);
if (indexOfEntity !== -1) {
this._shouldUpdate = true;
this._entitiesIndex.unsetAtIndex(indexOfEntity);
}
},
/**
* Returns entities matched by query.
*
* Returns object with following properties:
* - `length` - number of matched entities
* - `entities` - array with matched entities. __This array length is usually not the same as matched entities number!__
*
* @public
* @memberof Query#
* @method getEntities
* @returns {Object}
*/
getEntities() {
this._result.length = this._matchedEntities.length;
return this._result;
},
/**
* Updates internal matched entities with index.
*
* @public
* @memberof Query#
* @method update
* @param {FastArray} allEntities fast array of all entities present in the engine
*/
update(allEntities) {
if (!this._shouldUpdate) {
return;
}
this._entitiesIndex.compact();
this._matchedEntities.clear();
for (let i = 0; i < this._entitiesIndex.length; i += 1) {
const entityId = this._entitiesIndex.arr[i];
this._matchedEntities.push(allEntities.arr[entityId]);
}
this._shouldUpdate = false;
},
},
});
export default Query;