UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

316 lines (246 loc) • 8.4 kB
import { assert } from "../../../core/assert.js"; import { IllegalStateException } from "../../../core/fsm/exceptions/IllegalStateException.js"; import { objectKeyByValue } from "../../../core/model/object/objectKeyByValue.js"; import ObservedEnum from "../../../core/model/ObservedEnum.js"; import Task from "../../../core/process/task/Task.js"; import TaskGroup from "../../../core/process/task/TaskGroup.js"; import { GameAssetType } from "../../asset/GameAssetType.js"; import { StaticKnowledgeDataTableDescriptor } from "./StaticKnowledgeDataTableDescriptor.js"; /** * @readonly * @enum {number} */ export const StaticKnowledgeState = { Initial: 0, Loading: 1, Loaded: 2, Failed: 3 }; export class StaticKnowledgeDatabase { /** * * @type {ObservedEnum<StaticKnowledgeState>} */ __state = new ObservedEnum(StaticKnowledgeState.Initial, StaticKnowledgeState); /** * * @type {StaticKnowledgeDataTableDescriptor[]} * @private */ __specs = []; /** * * @type {boolean} */ #validation_enabled = false; /** * * @param {boolean} v */ set validation_enabled(v) { this.#validation_enabled = v; } /** * Reset database, drop all table data */ reset() { this.__state.set(StaticKnowledgeState.Initial); const tableDescriptors = this.__specs; const n = tableDescriptors.length; for (let i = 0; i < n; i++) { const descriptor = tableDescriptors[i]; descriptor.table.reset(); } } /** * * @param {string} id * @param {string} path * @param {StaticKnowledgeDataTable} table */ add(id, path, table) { assert.defined(table,'table'); assert.notNull(table,'table'); const state = this.__state.getValue(); if (state !== StaticKnowledgeState.Initial) { throw new IllegalStateException(`Adding new tables is only allowed in Initial state, current state is ${objectKeyByValue(StaticKnowledgeState, state)}`); } const existing = this.getDescriptorById(id); if (existing !== undefined) { if (existing.id === id && existing.source === path && Object.getPrototypeOf(existing.table) === Object.getPrototypeOf(table)) { // attempting to add the same thing console.warn(`Attempting to ad the same table again, ignoring. id='${id}', path='${path}' (table prototype matching)`); return; } throw new Error(`Table '${id}' already exists`); } const descriptor = StaticKnowledgeDataTableDescriptor.from(id, path, table); this.__specs.push(descriptor); //expose table accessor this[id] = table; } /** * @template T * @param {string} id * @returns {StaticKnowledgeDataTableDescriptor|undefined} */ getDescriptorById(id) { const descriptors = this.__specs; const n = descriptors.length; for (let i = 0; i < n; i++) { /** * * @type {StaticKnowledgeDataTableDescriptor} */ const descriptor = descriptors[i]; if (descriptor.id === id) { return descriptor; } } } /** * @template T * @param {string} id * @returns {StaticKnowledgeDataTable<T>|undefined} */ getTable(id) { const descriptor = this.getDescriptorById(id); if (descriptor !== undefined) { return descriptor.table; } return undefined; } /** * * @returns {Promise} */ promise() { const state = this.__state; return new Promise(function (resolve, reject) { function attemptResolution(value) { if (value === StaticKnowledgeState.Loaded) { return resolve(); } else if (value === StaticKnowledgeState.Failed) { return reject('Failed to load Static Knowledge'); } } state.process(attemptResolution); }); } /** * * @param {ConcurrentExecutor} executor * @return {Promise} */ validate(executor) { const tasks = this.__specs.map(d => { const table = d.table; const task = table.validate(this, console.error); return task; }); const taskGroup = new TaskGroup(tasks, 'Database Validation'); const promise = Task.promise(taskGroup); executor.runGroup(taskGroup); return promise; } /** * * @param {StaticKnowledgeDataTableDescriptor} descriptor * @param {AssetManager} assetManager * @param {ConcurrentExecutor} executor * @private */ async __link_table(descriptor, assetManager, executor) { /** * * @type {StaticKnowledgeDataTable} */ const t = descriptor.table; await t.link(this, assetManager, executor); } /** * * @param {AssetManager} am * @param {ConcurrentExecutor} executor * @returns {Promise} */ link(am, executor) { const promises = this.__specs.map(d => { const promise = this.__link_table(d, am, executor); promise.catch((e) => { /** * * @type {StaticKnowledgeDataTable} */ const t = d.table; console.error('Failed to link table:', t, e); }); }); return Promise.all([ promises ]); } /** * * @private * @param {StaticKnowledgeDataTableDescriptor} descriptor * @param {AssetManager} assetManager * @param {ConcurrentExecutor} executor * @returns {Promise<StaticKnowledgeDataTable>} */ async __load_table(descriptor, assetManager, executor) { const path = descriptor.source; const table = descriptor.table; if (table.isStaticKnowledgeDataTable !== true) { throw new Error('Illegal argument, table.isStaticKnowledgeDataTable !== true'); } const asset = await assetManager.promise(path, GameAssetType.JSON); const data = asset.create(); await table.load(data, executor); return table; } /** * Load a table at runtime * @param {StaticKnowledgeDataTableDescriptor} descriptor * @param {AssetManager} assetManager * @param {ConcurrentExecutor} executor * @private */ async __hot_load(descriptor, assetManager, executor) { await this.__load_table(descriptor, assetManager, executor); await this.__link_table(descriptor, assetManager, executor); } /** * * @param {AssetManager} am * @param {ConcurrentExecutor} executor * @returns {Promise} */ load(am, executor) { if (this.__state.getValue() !== StaticKnowledgeState.Initial) { throw new Error('Illegal state'); } this.__state.set(StaticKnowledgeState.Loading); const self = this; const promises = []; const n = this.__specs.length; for (let i = 0; i < n; i++) { const descriptor = this.__specs[i]; const p = this.__load_table(descriptor, am, executor); promises.push(p); } return Promise.all(promises).then(() => { let p = this.link(am, executor); if (this.#validation_enabled) { p = p.then(() => this.validate(executor)); } return p; }).then(function () { self.__state.set(StaticKnowledgeState.Loaded); }, function (error) { console.error('Failed to load static knowledge', error); self.__state.set(StaticKnowledgeState.Failed); }); } } StaticKnowledgeDatabase.prototype.isStaticKnowledgeDatabase = true;