@javelin/ecs
Version:
180 lines • 7.11 kB
JavaScript
import { assert, mutableEmpty, packSparseArray } from "@javelin/core";
import { createArchetype } from "./archetype";
import { getSchemaId } from "./component";
import { UNSAFE_internals } from "./internal";
import { createSignal } from "./signal";
const ERROR_ENTITY_NOT_CREATED = "Failed to locate entity: entity has not been created";
const ERROR_ALREADY_DESTROYED = "Failed to locate entity: entity has been destroyed";
const ERROR_NO_SCHEMA = "Failed to locate component: schema not registered";
export function createStorage(options = {}) {
const archetypes = options.snapshot
? options.snapshot.archetypes.map(snapshot => createArchetype({ snapshot }))
: [createArchetype({ type: [] })];
const entityIndex = [];
const entityRelocating = createSignal();
const entityRelocated = createSignal();
const archetypeCreated = createSignal();
function findArchetype(components) {
const length = components.length;
outer: for (let i = 0; i < archetypes.length; i++) {
const archetype = archetypes[i];
const { type, typeInverse } = archetype;
if (type.length !== length) {
continue;
}
for (let j = 0; j < length; j++) {
if (typeInverse[getSchemaId(components[j])] === undefined) {
continue outer;
}
}
return archetype;
}
return null;
}
function findOrCreateArchetype(components) {
let archetype = findArchetype(components);
if (archetype === null) {
archetype = createArchetype({
type: components.map(getSchemaId),
});
archetypes.push(archetype);
archetypeCreated.dispatch(archetype);
}
return archetype;
}
function getEntityArchetype(entity) {
const archetype = entityIndex[entity];
assert(archetype !== undefined, ERROR_ENTITY_NOT_CREATED);
assert(archetype !== null, ERROR_ALREADY_DESTROYED);
return archetype;
}
function relocate(prev, entity, components, changed) {
const next = findOrCreateArchetype(components);
entityRelocating.dispatch(entity, prev, next, changed);
prev.remove(entity);
next.insert(entity, components);
entityIndex[entity] = next;
entityRelocated.dispatch(entity, prev, next, changed);
}
function attachComponents(entity, components) {
const source = entityIndex[entity];
if (source === undefined || source === null) {
const archetype = findOrCreateArchetype(components);
entityRelocating.dispatch(entity, archetypes[0], archetype, components);
archetype.insert(entity, components);
entityIndex[entity] = archetype;
entityRelocated.dispatch(entity, archetypes[0], archetype, components);
}
else {
const index = source.indices[entity];
const final = components.slice();
for (let i = 0; i < source.type.length; i++) {
const schemaId = source.type[i];
if (components.find(c => getSchemaId(c) === schemaId)) {
// take inserted component
continue;
}
final.push(source.table[i][index]);
}
relocate(source, entity, final, components);
}
}
function detachBySchemaId(entity, type) {
const source = getEntityArchetype(entity);
const removed = [];
const final = [];
const index = source.indices[entity];
for (let i = 0; i < source.type.length; i++) {
const schemaId = source.type[i];
const component = source.table[i][index];
(type.includes(schemaId) ? removed : final).push(component);
}
relocate(source, entity, final, removed);
}
function clearComponents(entity) {
const archetype = getEntityArchetype(entity);
detachBySchemaId(entity, archetype.type);
entityIndex[entity] = null;
}
const tmpComponentsToInsert = [];
function attachOrUpdateComponents(entity, components) {
const archetype = getEntityArchetype(entity);
const index = archetype.indices[entity];
mutableEmpty(tmpComponentsToInsert);
for (let i = 0; i < components.length; i++) {
const component = components[i];
const column = archetype.typeInverse[getSchemaId(component)];
if (column === undefined) {
// Entity component makeup does not match patch component, insert the new
// component.
tmpComponentsToInsert.push(component);
}
else {
// Apply patch to component.
Object.assign(archetype.table[column][index], component);
}
}
if (tmpComponentsToInsert.length > 0) {
attachComponents(entity, tmpComponentsToInsert);
}
}
function hasComponentOfSchema(entity, schema) {
const archetype = getEntityArchetype(entity);
const type = UNSAFE_internals.schemaIndex.get(schema);
assert(type !== undefined, ERROR_NO_SCHEMA);
return archetype.type.includes(type);
}
function getComponentBySchema(entity, schema) {
const type = UNSAFE_internals.schemaIndex.get(schema);
assert(type !== undefined, ERROR_NO_SCHEMA);
return getComponentBySchemaId(entity, type);
}
function getComponentBySchemaId(entity, schemaId) {
const archetype = getEntityArchetype(entity);
const column = archetype.typeInverse[schemaId];
if (column === undefined) {
return null;
}
const entityIndex = archetype.indices[entity];
return archetype.table[column][entityIndex];
}
function getAllComponents(entity) {
const archetype = getEntityArchetype(entity);
const entityIndex = archetype.indices[entity];
const result = [];
for (let i = 0; i < archetype.table.length; i++) {
result.push(archetype.table[i][entityIndex]);
}
return result;
}
function clear() {
mutableEmpty(archetypes);
mutableEmpty(entityIndex);
}
function createSnapshot() {
return {
archetypes: archetypes.map(archetype => ({
type: archetype.type.slice(),
table: archetype.table.map(column => column.map(component => ({ ...component }))),
indices: packSparseArray(archetype.indices),
})),
};
}
return {
archetypeCreated,
archetypes,
attachComponents,
attachOrUpdateComponents,
clear,
clearComponents,
detachBySchemaId,
entityRelocated,
entityRelocating,
getComponentBySchemaId,
getComponentBySchema,
getAllComponents,
createSnapshot,
hasComponentOfSchema,
};
}
//# sourceMappingURL=storage.js.map