UNPKG

@tendrock/database

Version:

A database lib under the Tendrock ecosystem for Minecraft Bedrock Edition Script API

332 lines (331 loc) 13.4 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { Block, Entity, EntityInitializationCause, ItemStack, system, world } from "@minecraft/server"; import { NamespacedDatabaseManager } from "./NamespacedDatabaseManager"; import { UniqueIdUtils } from "../helper/UniqueIdUtils"; import { Utils } from "../helper/Utils"; import { SetMap } from "@tenolib/map"; export class DatabaseManager { constructor() { this._databaseManagerMap = new Map(); this._isInitialized = false; this._whenReadyCallbackList = new Array(); this._blockToDatabaseMap = new SetMap(); this._itemToDatabaseMap = new SetMap(); this._entityToDatabaseMap = new SetMap(); this._changingEntityDatabaseBuffer = new Map(); this._flushInterval = 3 * 6 * 20; this._autoUpdateSourceEntity = false; this._autoFlush = true; this._startFlushWhenPlayerLeaveTask(); this._loadWorldDynamicPropertiesWhenWorldLoaded(); } *_loadAndParseWorldDynamicPropertiesGenerator() { for (const id of world.getDynamicPropertyIds()) { const { namespace, lid, dataIdentifier } = Utils.parseIdentifier(id); if (!namespace) continue; const manager = this._getOrCreateNamespacedManager(namespace); if (lid) { manager._addBlockDataId(UniqueIdUtils.RuntimeId, lid, id, dataIdentifier); } else { manager._addWorldDataId(UniqueIdUtils.RuntimeId, id, dataIdentifier); } yield; } } _loadWorldDynamicProperties() { return __awaiter(this, void 0, void 0, function* () { yield Utils.runJob(this._loadAndParseWorldDynamicPropertiesGenerator()); this._isInitialized = true; this._startAutoFlushTask(); this._doReady(); }); } _loadWorldDynamicPropertiesWhenWorldLoaded() { world.afterEvents.worldLoad.subscribe(() => { this._loadWorldDynamicProperties(); }); } _getOrCreateNamespacedManager(namespace) { let databaseManager = this._databaseManagerMap.get(namespace); if (databaseManager) { return databaseManager; } databaseManager = NamespacedDatabaseManager._create(UniqueIdUtils.RuntimeId, namespace, this); this._databaseManagerMap.set(namespace, databaseManager); return databaseManager; } _getNamespacedManager(namespace) { return this._databaseManagerMap.get(namespace); } _doReady() { this._whenReadyCallbackList.forEach(callback => callback()); this._whenReadyCallbackList = []; } whenReady(callback) { if (this._isInitialized) { callback(); return undefined; } this._whenReadyCallbackList.push(callback); return () => { this._whenReadyCallbackList.splice(this._whenReadyCallbackList.indexOf(callback), 1); }; } isReady() { return this._isInitialized; } getOrCreate(namespace, gameObject) { const databaseManager = this._getOrCreateNamespacedManager(namespace); return databaseManager.getOrCreate(gameObject); } get(namespace, gameObject) { const databaseManager = this._getNamespacedManager(namespace); if (!databaseManager) { return undefined; } return databaseManager.get(gameObject); } setData(namespace, gameObject, identifier, value) { const database = this.getOrCreate(namespace, gameObject); database.set(identifier, value); } getData(namespace, gameObject, identifier) { const database = this.get(namespace, gameObject); return database === null || database === void 0 ? void 0 : database.get(identifier); } getDataInstance(namespace, gameObject, identifier, objectConstructor, options) { const database = this.get(namespace, gameObject); return database === null || database === void 0 ? void 0 : database.getInstance(identifier, objectConstructor, options); } getDataInstanceIfPresent(namespace, gameObject, identifier) { const database = this.get(namespace, gameObject); return database === null || database === void 0 ? void 0 : database.getInstanceIfPresent(identifier); } getDataInstanceOrCreate(namespace, gameObject, identifier, objectConstructor, options) { const database = this.getOrCreate(namespace, gameObject); return database.getInstanceOrCreate(identifier, objectConstructor, options); } remove(namespace, gameObject, clearData = false) { const databaseManager = this._getNamespacedManager(namespace); databaseManager === null || databaseManager === void 0 ? void 0 : databaseManager.remove(gameObject, clearData); } _prepare(gameObject) { if (gameObject instanceof Block) { return { uniqueId: UniqueIdUtils.getBlockUniqueId(gameObject), gameObjectToDatabaseMap: this._blockToDatabaseMap, }; } else if (gameObject instanceof Entity) { return { uniqueId: UniqueIdUtils.getEntityUniqueId(gameObject), gameObjectToDatabaseMap: this._entityToDatabaseMap, }; } else if (gameObject instanceof ItemStack) { return { uniqueId: UniqueIdUtils.getItemUniqueId(gameObject), gameObjectToDatabaseMap: this._itemToDatabaseMap, }; } else { return { uniqueId: 'world@0', }; } } getDatabaseListByGameObject(gameObject) { var _a; const { uniqueId, gameObjectToDatabaseMap } = this._prepare(gameObject); if (!gameObjectToDatabaseMap || !uniqueId) { return []; } return ((_a = gameObjectToDatabaseMap.get(uniqueId)) !== null && _a !== void 0 ? _a : []); } getDatabaseList(namespace, type) { const manager = this._getNamespacedManager(namespace); if (!manager) { return []; } return manager.getDatabaseList(type); } setFlushInterval(interval, flush = true) { this._flushInterval = interval; if (flush) { this.flush(); } this._startAutoFlushTask(); } getFlushInterval() { return this._flushInterval; } setAutoFlush(value = true) { this._autoFlush = value; if (value) { this._clearFlushJobIfPresent(); } else { this._startAutoFlushTask(); } } autoFlush() { return this._autoFlush; } setAutoUpdateSourceEntity(value = true) { this._autoUpdateSourceEntity = value; } autoUpdateSourceEntity() { return this._autoUpdateSourceEntity; } *flushDatabase(database) { database._beginFlush(UniqueIdUtils.RuntimeId); const dirtyIdList = database._getDirtyDataIdList(UniqueIdUtils.RuntimeId); for (const identifier of dirtyIdList) { if (database.size() <= 0) { yield; break; } const value = database.get(identifier); database._saveData(UniqueIdUtils.RuntimeId, identifier, value); yield; } // console.log(`flush ${database._getDirtyDataIdList(UniqueIdUtils.RuntimeId).length} data`); database._endFlush(UniqueIdUtils.RuntimeId); } flushDatabaseSync(database) { database._beginFlush(UniqueIdUtils.RuntimeId); const dirtyIdList = database._getDirtyDataIdList(UniqueIdUtils.RuntimeId); for (const identifier of dirtyIdList) { if (database.size() <= 0) { break; } const value = database.get(identifier); database._saveData(UniqueIdUtils.RuntimeId, identifier, value); } database._endFlush(UniqueIdUtils.RuntimeId); } *flushAllDataGenerator() { const managerValues = this._databaseManagerMap.values(); for (const manager of managerValues) { manager._beginFlush(UniqueIdUtils.RuntimeId); const databaseValues = manager.getDirtyDatabaseList(); if (databaseValues.length > 0) { for (const database of databaseValues) { yield* this.flushDatabase(database); } } manager._endFlush(UniqueIdUtils.RuntimeId); } } flushSync() { if (!this.isReady()) return; const managerValues = this._databaseManagerMap.values(); for (const manager of managerValues) { manager._beginFlush(UniqueIdUtils.RuntimeId); const databaseValues = manager.getDirtyDatabaseList(); if (databaseValues.length <= 0) { continue; } for (const database of databaseValues) { this.flushDatabaseSync(database); } manager._endFlush(UniqueIdUtils.RuntimeId); } } flush() { if (!this.isReady()) return; system.runJob(this.flushAllDataGenerator()); } _startFlushWhenPlayerLeaveTask() { world.beforeEvents.playerLeave.subscribe(({ player }) => { if (world.getAllPlayers().length === 1) { this.flushSync(); } else { for (const manager of this._databaseManagerMap.values()) { this.flushDatabase(manager.getOrCreate(player)); } } }); } _clearFlushJobIfPresent() { if (this._autoFlushTaskId !== undefined) { system.clearJob(this._autoFlushTaskId); } } _startAutoFlushTask() { this._clearFlushJobIfPresent(); if (!this._autoFlush) return; this._autoFlushTaskId = system.runInterval(() => { this.flush(); }, this._flushInterval); } _getBlockToDatabaseMap(runtimeId) { Utils.assertInvokedByTendrock(runtimeId); return this._blockToDatabaseMap; } _getEntityToDatabaseMap(runtimeId) { Utils.assertInvokedByTendrock(runtimeId); return this._entityToDatabaseMap; } _getItemToDatabaseMap(runtimeId) { Utils.assertInvokedByTendrock(runtimeId); return this._itemToDatabaseMap; } _setChangingEntityDatabaseBuffer(runtimeId, locationId, entityDatabase) { Utils.assertInvokedByTendrock(runtimeId); // console.log(`set changing entity database buffer: ${locationId}`) this._changingEntityDatabaseBuffer.set(locationId, entityDatabase); } _getChangingEntityDatabaseBuffer(runtimeId, locationId) { Utils.assertInvokedByTendrock(runtimeId); return { entityDatabase: this._changingEntityDatabaseBuffer.get(locationId), cleanBuffer: () => { this._changingEntityDatabaseBuffer.delete(locationId); } }; } } export const databaseManager = new DatabaseManager(); world.beforeEvents.entityRemove.subscribe(({ removedEntity }) => { if (!databaseManager.autoUpdateSourceEntity()) return; const removedEntityDatabaseList = databaseManager .getDatabaseListByGameObject(removedEntity) .filter((e) => e.getUid() === removedEntity.id); if (removedEntityDatabaseList.length <= 0) return; const removedEntityDatabase = removedEntityDatabaseList[0]; const locationId = Utils.getLocationId(Object.assign(Object.assign({}, removedEntity.location), { dimension: removedEntity.dimension }), true); databaseManager._setChangingEntityDatabaseBuffer(UniqueIdUtils.RuntimeId, locationId, removedEntityDatabase); system.runTimeout(() => { databaseManager._getChangingEntityDatabaseBuffer(UniqueIdUtils.RuntimeId, locationId).cleanBuffer(); }, 3); }); world.afterEvents.entitySpawn.subscribe(({ entity, cause }) => { if (!databaseManager.autoUpdateSourceEntity()) return; if (cause !== EntityInitializationCause.Event && cause !== EntityInitializationCause.Transformed) return; const locationId = Utils.getLocationId(Object.assign(Object.assign({}, entity.location), { dimension: entity.dimension }), true); // console.log('entity spawned: ', locationId) const { cleanBuffer, entityDatabase } = databaseManager._getChangingEntityDatabaseBuffer(UniqueIdUtils.RuntimeId, locationId); if (entityDatabase) { entityDatabase._setEntity(UniqueIdUtils.RuntimeId, entity); cleanBuffer(); } });