@dcl/ecs
Version:
Decentraland ECS
177 lines (176 loc) • 5.86 kB
JavaScript
import { createVersionGSet } from '../systems/crdt/gset';
/**
* @internal
*/
export const MAX_U16 = 0xffff;
const MASK_UPPER_16_ON_32 = 0xffff0000;
/**
* @internal
*/
export const AMOUNT_VERSION_AVAILABLE = MAX_U16 + 1;
// This type matches with @dcl/crdt entity type.
/**
* @internal
*/
export const MAX_ENTITY_NUMBER = MAX_U16;
/**
* This first 512 entities are reserved by the renderer
*/
export const RESERVED_STATIC_ENTITIES = 512;
/**
* @public
*/
export var EntityUtils;
(function (EntityUtils) {
/**
* @returns [entityNumber, entityVersion]
*/
function fromEntityId(entityId) {
return [(entityId & MAX_U16) >>> 0, (((entityId & MASK_UPPER_16_ON_32) >> 16) & MAX_U16) >>> 0];
}
EntityUtils.fromEntityId = fromEntityId;
/**
* @returns compound number from entityNumber and entityVerison
*/
function toEntityId(entityNumber, entityVersion) {
return (((entityNumber & MAX_U16) | ((entityVersion & MAX_U16) << 16)) >>> 0);
}
EntityUtils.toEntityId = toEntityId;
})(EntityUtils || (EntityUtils = {}));
/**
* @public
*/
export var EntityState;
(function (EntityState) {
EntityState[EntityState["Unknown"] = 0] = "Unknown";
/**
* The entity was generated and added to the usedEntities set
*/
EntityState[EntityState["UsedEntity"] = 1] = "UsedEntity";
/**
* The entity was removed from current engine or remotely
*/
EntityState[EntityState["Removed"] = 2] = "Removed";
/**
* The entity is reserved number.
*/
EntityState[EntityState["Reserved"] = 3] = "Reserved";
})(EntityState || (EntityState = {}));
/**
* @public
*/
export function createEntityContainer(opts) {
const reservedStaticEntities = opts?.reservedStaticEntities ?? RESERVED_STATIC_ENTITIES;
// Local entities counter
let entityCounter = reservedStaticEntities;
const usedEntities = new Set();
let toRemoveEntities = [];
const removedEntities = createVersionGSet();
function generateNewEntity() {
if (entityCounter > MAX_ENTITY_NUMBER - 1) {
throw new Error(`It fails trying to generate an entity out of range ${MAX_ENTITY_NUMBER}.`);
}
const entityNumber = entityCounter++;
const entityVersion = removedEntities.getMap().has(entityNumber)
? removedEntities.getMap().get(entityNumber) + 1
: 0;
const entity = EntityUtils.toEntityId(entityNumber, entityVersion);
if (usedEntities.has(entity)) {
return generateNewEntity();
}
usedEntities.add(entity);
return entity;
}
function generateEntity() {
const usedSize = usedEntities.size;
// If all entities until `entityCounter` are being used, we need to generate another one
if (usedSize + reservedStaticEntities >= entityCounter) {
return generateNewEntity();
}
for (const [number, version] of removedEntities.getMap()) {
if (version < MAX_U16) {
const entity = EntityUtils.toEntityId(number, version + 1);
// If the entity is not being used, we can re-use it
// If the entity was removed in this tick, we're not counting for the usedEntities, but we have it in the toRemoveEntityArray
if (!usedEntities.has(entity) && !toRemoveEntities.includes(entity)) {
usedEntities.add(entity);
return entity;
}
}
}
return generateNewEntity();
}
function removeEntity(entity) {
if (entity < reservedStaticEntities)
return false;
if (usedEntities.has(entity)) {
usedEntities.delete(entity);
toRemoveEntities.push(entity);
}
else {
updateRemovedEntity(entity);
}
return true;
}
function releaseRemovedEntities() {
const arr = toRemoveEntities;
if (arr.length) {
toRemoveEntities = [];
for (const entity of arr) {
const [n, v] = EntityUtils.fromEntityId(entity);
removedEntities.addTo(n, v);
}
}
return arr;
}
function updateRemovedEntity(entity) {
const [n, v] = EntityUtils.fromEntityId(entity);
// Update the removed entities map
removedEntities.addTo(n, v);
// Remove the usedEntities if exist
for (let i = 0; i <= v; i++) {
usedEntities.delete(EntityUtils.toEntityId(n, i));
}
return true;
}
function updateUsedEntity(entity) {
const [n, v] = EntityUtils.fromEntityId(entity);
// if the entity was removed then abort fast
if (removedEntities.has(n, v))
return false;
// Update
if (v > 0) {
for (let i = 0; i <= v - 1; i++) {
usedEntities.delete(EntityUtils.toEntityId(n, i));
}
removedEntities.addTo(n, v - 1);
}
usedEntities.add(entity);
return true;
}
function getEntityState(entity) {
const [n, v] = EntityUtils.fromEntityId(entity);
if (n < reservedStaticEntities) {
return EntityState.Reserved;
}
if (usedEntities.has(entity)) {
return EntityState.UsedEntity;
}
const removedVersion = removedEntities.getMap().get(n);
if (removedVersion !== undefined && removedVersion >= v) {
return EntityState.Removed;
}
return EntityState.Unknown;
}
return {
generateEntity,
removeEntity,
getExistingEntities() {
return new Set(usedEntities);
},
getEntityState,
releaseRemovedEntities,
updateRemovedEntity,
updateUsedEntity
};
}