@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
250 lines (193 loc) • 6.1 kB
JavaScript
import { assert } from "../../../core/assert.js";
import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
import { noop } from "../../../core/function/noop.js";
import DataType from "../../../core/parser/simple/DataType.js";
import { BinaryObjectSerializationAdapter } from "../../ecs/storage/binary/object/BinaryObjectSerializationAdapter.js";
import { Blackboard } from "./Blackboard.js";
export class BlackboardDynamicStorageAdapter {
constructor() {
/**
*
* @type {Blackboard}
* @private
*/
this.__blackboard = null;
/**
*
* @type {Storage}
* @private
*/
this.__storage = null;
/**
*
* @type {boolean}
* @private
*/
this.__is_linked = false;
/**
*
* @type {string}
* @private
*/
this.__storage_key = "";
/**
*
* @type {BinaryObjectSerializationAdapter}
* @private
*/
this.__serialization_adapter = new BinaryObjectSerializationAdapter();
this.__serialization_in_progress = false;
this.__need_write = false;
/**
* "setTimeout" Handle
* @type {number}
* @private
*/
this.__write_timeout_handle = -1;
/**
* In milliseconds
* @type {number}
* @private
*/
this.__write_timeout_delay = 200;
/**
* Last time write happened
* @type {number}
* @private
*/
this.__last_write_timestamp = 0;
}
setStorageKey(value) {
this.__storage_key = value;
}
/**
* @param {BinarySerializationRegistry} s
*/
setSerialization(s) {
assert.defined(s, 's');
this.__serialization_adapter.initialize(s);
}
/**
* @param {Blackboard} v
*/
setBlackboard(v) {
this.__blackboard = v;
}
/**
* @param {Storage} v
*/
setStorage(v) {
this.__storage = v;
}
/**
* Read data from storage and populate blackboard accordingly
*/
async read() {
const data = await this.__storage.promiseLoadBinary(this.__storage_key);
this.__blackboard.reset();
if (data === undefined) {
// no data
return;
}
const buffer = BinaryBuffer.fromArrayBuffer(data);
const object = this.__serialization_adapter.deserialize(buffer);
if (!(object instanceof Blackboard)) {
throw new Error('Expected blackboard, got something else');
}
this.__blackboard.copy(object);
}
__handle_serialization_finalized() {
this.__serialization_in_progress = false;
if (this.__need_write) {
// write is required
this.write();
}
}
/**
* Write data to storage, this is not guaranteed to result in immediate writing
*/
write() {
if (this.__serialization_in_progress) {
this.__need_write = true;
return;
}
if (this.__write_timeout_handle !== -1) {
// there is a pending write already
return;
}
const current_time = performance.now();
const time_since_last_write = current_time - this.__last_write_timestamp;
const time_until_next_write = this.__write_timeout_delay - time_since_last_write;
if (time_until_next_write <= 0) {
// enough time has passed since last write to do an immediate write
this.__last_write_timestamp = current_time;
this.__write_immediate();
return;
}
// not enough time has passed since last write, let's schedule another write
this.__write_timeout_handle = setTimeout(this.__handle_write_timeout.bind(this), time_until_next_write);
}
__handle_write_timeout() {
// clear timeout handle
this.__write_timeout_handle = -1;
this.__write_immediate();
}
/**
* Perform write right now
* @private
*/
__write_immediate() {
this.__serialization_in_progress = true;
this.__need_write = false;
const buffer = new BinaryBuffer();
this.__serialization_adapter.serialize(buffer, this.__blackboard, Blackboard.typeName);
buffer.trim();
const success = () => {
this.__handle_serialization_finalized();
}
const reject = () => {
this.__handle_serialization_finalized();
}
this.__storage.storeBinary(this.__storage_key, buffer.data, success, reject, noop);
}
/**
*
* @param {string} name
* @private
*/
__watch_property(name) {
const value = this.__blackboard.acquire(name, DataType.Any);
value.onChanged.add(this.write, this);
this.write();
}
/**
*
* @param {string} name
* @private
*/
__unwatch_property(name) {
const value = this.__blackboard.acquire(name, DataType.Any);
value.onChanged.remove(this.write, this);
}
async link() {
if (this.__is_linked) {
return;
}
this.__is_linked = true;
await this.read();
this.__blackboard.traverse((name, value, type) => {
this.__watch_property(name);
});
this.__blackboard.on.added.add(this.__watch_property, this);
}
async unlink() {
if (!this.__is_linked) {
return;
}
this.__is_linked = false;
this.__blackboard.traverse((name, value, type) => {
this.__unwatch_property(name);
});
this.__blackboard.on.added.remove(this.__watch_property, this);
}
}