@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
316 lines (246 loc) • 8.4 kB
JavaScript
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;