Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sisilicon
GitHub Repository: sisilicon/worldedit-be
Path: blob/master/src/library/classes/databaseBuilder.ts
1784 views
1
/* eslint-disable @typescript-eslint/ban-types */
2
/* eslint-disable @typescript-eslint/no-explicit-any */
3
import { Entity, World, world } from "@minecraft/server";
4
import { Database } from "../@types/classes/databaseBuilder";
5
import { contentLog } from "@notbeer-api";
6
7
const databases: { [k: string]: DatabaseImpl<any> } = {};
8
const parsers: ((key: string, value: any, databaseName: string) => any)[] = [];
9
10
function parseJSON(databaseName: string, json: string) {
11
return JSON.parse(json, (key, value) => {
12
for (const parser of parsers) value = parser(key, value, databaseName);
13
return value;
14
});
15
}
16
17
function getDatabaseKey(name: string, provider?: World | Entity) {
18
return name + "//" + (provider instanceof Entity ? provider.id : "world");
19
}
20
21
class DatabaseManager {
22
load<T extends object = { [key: string]: any }>(name: string, provider: World | Entity = world) {
23
const key = getDatabaseKey(name, provider);
24
if (!databases[key]) {
25
databases[key] = new DatabaseImpl<T>(name, provider);
26
databases[key].load();
27
}
28
return <Database<T>>databases[key];
29
}
30
31
delete(name: string, provider: World | Entity = world) {
32
const key = getDatabaseKey(name, provider);
33
const database = databases[key] ?? new DatabaseImpl<any>(name, provider);
34
if (database.isValid) database.delete();
35
delete databases[key];
36
}
37
38
find(regexp: RegExp, provider: World | Entity = world) {
39
return provider.getDynamicPropertyIds().filter((name) => name.match(regexp));
40
}
41
42
getRawData(name: string, provider: World | Entity = world) {
43
const key = getDatabaseKey(name, provider);
44
const database = databases[key] ?? new DatabaseImpl<any>(name, provider);
45
return database.rawData;
46
}
47
48
addParser(parser: (key: string, value: any, database: string) => any) {
49
parsers.push(parser);
50
}
51
}
52
53
export const Databases = new DatabaseManager();
54
55
class DatabaseImpl<T extends object = { [key: string]: any }> implements Database<T> {
56
private _data: T = <any>{};
57
private loaded = false;
58
private valid = true;
59
60
constructor(
61
private name: string,
62
private provider: World | Entity = world
63
) {}
64
65
get data() {
66
if (!this.valid) throw new Error(`Can't get data from invalid database "${this.name}".`);
67
if (!this.loaded) this.load();
68
return this._data;
69
}
70
71
set data(value: T) {
72
if (!this.valid) throw new Error(`Can't set data on invalid database "${this.name}".`);
73
this.loaded = true;
74
this._data = value;
75
}
76
77
get rawData(): string | undefined {
78
let data: string | undefined;
79
data = <string>this.provider.getDynamicProperty(this.name);
80
let page: string | undefined;
81
let i = 2;
82
while (data && (page = <string>this.provider.getDynamicProperty(`__page${i++}__` + this.name))) data += page;
83
return data;
84
}
85
86
get isLoaded() {
87
return this.loaded;
88
}
89
90
get isValid() {
91
return this.valid;
92
}
93
94
clear(): void {
95
if (!this.valid) throw new Error(`Can't clear data from invalid database "${this.name}".`);
96
if (!this.loaded) this.load();
97
this._data = {} as T;
98
}
99
100
save(): void {
101
if (!this.valid) throw new Error(`Can't save data to invalid database "${this.name}".`);
102
103
const data = JSON.stringify(this._data);
104
// Try smaller divisions of data until the right number of pages is found.
105
// 50 subdivions allow for a little more than 1.5 MB per database.
106
divisions: for (let i = 1; i <= 50; i++) {
107
let page: number | undefined = undefined;
108
const stepSize = Math.ceil(data.length / i);
109
for (let j = 0; j < data.length; j += stepSize) {
110
try {
111
this.provider.setDynamicProperty((page ? `__page${page}__` : "") + this.name, data.slice(j, j + stepSize));
112
page = (page ?? 1) + 1;
113
} catch {
114
continue divisions;
115
}
116
}
117
// Remove unused pages
118
while (this.provider.getDynamicProperty(`__page${page}__` + this.name)) {
119
this.provider.setDynamicProperty(`__page${page!++}__` + this.name, undefined);
120
}
121
this.loaded = true;
122
return;
123
}
124
125
contentLog.error(`Failed to save database ${this.name} to ${this.provider instanceof Entity ? this.provider.nameTag ?? this.provider.id : "the world"}`);
126
contentLog.debug(contentLog.stack());
127
}
128
129
load() {
130
if (!this.valid) throw new Error(`Can't load data from invalid database "${this.name}".`);
131
if (this.loaded) return;
132
try {
133
this._data = parseJSON(this.name, this.rawData ?? "{}");
134
} catch (err) {
135
contentLog.error(`Failed to load database ${this.name} from ${this.provider instanceof Entity ? this.provider.nameTag ?? this.provider.id : "the world"}`);
136
if (err) contentLog.debug(err, err.stack);
137
return;
138
}
139
this.loaded = true;
140
}
141
142
unload() {
143
if (!this.valid) throw new Error(`Can't unload invalid database "${this.name}".`);
144
this.loaded = false;
145
this._data = undefined;
146
Databases.delete(this.name, this.provider);
147
}
148
149
delete() {
150
if (!this.valid) throw new Error(`Can't delete invalid database "${this.name}".`);
151
this.provider.setDynamicProperty(this.name, undefined);
152
let page = 2;
153
while (this.provider.getDynamicProperty(`__page${page}__` + this.name)) {
154
this.provider.setDynamicProperty(`__page${page++}__` + this.name, undefined);
155
}
156
this.valid = false;
157
Databases.delete(this.name, this.provider);
158
}
159
160
toJSON() {
161
const json: any = { __dbName__: this.name };
162
if (this.provider instanceof Entity) json.__dbProvider__ = this.provider.id;
163
return json;
164
}
165
}
166
167
Databases.addParser((_, value) => {
168
if (!value || typeof value !== "object" || !value.__dbName__) return value;
169
const provider = value.__dbProvider__ ? world.getEntity(value.__dbProvider__) : world;
170
const key = getDatabaseKey(value.__dbName__, provider);
171
if (!databases[key]) databases[key] = new DatabaseImpl(value.__dbName__, provider);
172
return databases[key];
173
});
174
175