Skip to content

Commit 5d30929

Browse files
committed
initial release
1 parent 3a66291 commit 5d30929

7 files changed

Lines changed: 400 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
package-lock.json

ArrayDatabase.d.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export declare class ArrayDatabase<T = any> {
2+
readonly id: string;
3+
static readonly PROPERTY_MAX_SIZE = 12000;
4+
static readonly PREFIX = "array";
5+
private readonly cache;
6+
private cacheLoaded;
7+
private currentKeyIndex;
8+
constructor(id: string);
9+
getAll(): T[];
10+
add(value: T): this;
11+
has(value: T): boolean;
12+
clear(): void;
13+
values(): IterableIterator<T>;
14+
find(callbackfn: (value: T) => boolean): T | undefined;
15+
filter(callbackfn: (value: T) => boolean): T[];
16+
map<U>(callbackfn: (value: T) => U): U[];
17+
forEach(callbackfn: (value: T) => void): void;
18+
some(callbackfn: (value: T) => boolean): boolean;
19+
every(callbackfn: (value: T) => boolean): boolean;
20+
[Symbol.iterator](): IterableIterator<T>;
21+
private trySave;
22+
get size(): number;
23+
private load;
24+
private unload;
25+
private get keyPrefix();
26+
private get currentKey();
27+
private get indexKey();
28+
get [Symbol.toStringTag](): string;
29+
}

ArrayDatabase.js

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { world } from '@minecraft/server';
2+
export class ArrayDatabase {
3+
constructor(id) {
4+
this.id = id;
5+
this.cache = [];
6+
this.cacheLoaded = false;
7+
this.currentKeyIndex = 0;
8+
}
9+
getAll() {
10+
if (!this.cacheLoaded)
11+
this.load();
12+
const result = [];
13+
for (const value of this.cache)
14+
result.push(...value);
15+
return result;
16+
}
17+
add(value) {
18+
if (!this.cacheLoaded)
19+
this.load();
20+
const baseData = this.cache[this.currentKeyIndex] ?? [];
21+
baseData.push(value);
22+
this.trySave(baseData);
23+
return this;
24+
}
25+
has(value) {
26+
const locations = this.getAll();
27+
return locations.includes(value);
28+
}
29+
clear() {
30+
const keys = world.getDynamicPropertyIds();
31+
const prefix = this.keyPrefix;
32+
for (const key of keys) {
33+
if (key.startsWith(prefix))
34+
world.setDynamicProperty(key);
35+
}
36+
this.currentKeyIndex = 0;
37+
world.setDynamicProperty(this.indexKey);
38+
this.cache.length = 0;
39+
}
40+
*values() {
41+
if (!this.cacheLoaded)
42+
this.load();
43+
for (const values of this.cache) {
44+
for (const value of values) {
45+
yield value;
46+
}
47+
}
48+
}
49+
find(callbackfn) {
50+
for (const value of this.values()) {
51+
if (callbackfn(value))
52+
return value;
53+
}
54+
return undefined;
55+
}
56+
filter(callbackfn) {
57+
const result = [];
58+
for (const value of this.values()) {
59+
if (callbackfn(value))
60+
result.push(value);
61+
}
62+
return result;
63+
}
64+
map(callbackfn) {
65+
const result = [];
66+
for (const value of this.values()) {
67+
result.push(callbackfn(value));
68+
}
69+
return result;
70+
}
71+
forEach(callbackfn) {
72+
for (const value of this.values()) {
73+
callbackfn(value);
74+
}
75+
}
76+
some(callbackfn) {
77+
for (const value of this.values()) {
78+
if (callbackfn(value))
79+
return true;
80+
}
81+
return false;
82+
}
83+
every(callbackfn) {
84+
for (const value of this.values()) {
85+
if (!callbackfn(value))
86+
return false;
87+
}
88+
return true;
89+
}
90+
[Symbol.iterator]() {
91+
return this.values();
92+
}
93+
trySave(values, swap = []) {
94+
let sizeOK = true;
95+
const stringified = JSON.stringify(values);
96+
if (stringified.length > ArrayDatabase.PROPERTY_MAX_SIZE) {
97+
sizeOK = false;
98+
}
99+
else {
100+
try {
101+
world.setDynamicProperty(this.currentKey, stringified);
102+
this.cache[this.currentKeyIndex] = values;
103+
}
104+
catch (e) {
105+
sizeOK = false;
106+
}
107+
}
108+
if (!sizeOK) {
109+
if (values.length > 0)
110+
swap.push(values.shift());
111+
this.trySave(values, swap);
112+
this.currentKeyIndex++;
113+
world.setDynamicProperty(this.indexKey, this.currentKeyIndex);
114+
this.trySave(swap);
115+
}
116+
}
117+
get size() {
118+
return this.cache.reduce((acc, arr) => acc + arr.length, 0);
119+
}
120+
load() {
121+
this.currentKeyIndex = world.getDynamicProperty(this.indexKey) ?? 0;
122+
const keys = world.getDynamicPropertyIds();
123+
const prefix = this.keyPrefix;
124+
for (const key of keys) {
125+
if (key.includes('_index'))
126+
console.warn(key);
127+
if (!key.startsWith(prefix))
128+
continue;
129+
const index = Number(key.slice(prefix.length));
130+
if (Number.isNaN(index)) {
131+
console.error(`Found invalid key: ${key}`);
132+
continue;
133+
}
134+
this.cache[index] = JSON.parse(world.getDynamicProperty(key) ?? '[]');
135+
}
136+
this.cacheLoaded = true;
137+
}
138+
unload() {
139+
this.cache.length = 0;
140+
this.cacheLoaded = false;
141+
this.currentKeyIndex = 0;
142+
}
143+
get keyPrefix() {
144+
return `${ArrayDatabase.PREFIX}:${this.id}`;
145+
}
146+
get currentKey() {
147+
return `${this.keyPrefix}${this.currentKeyIndex}`;
148+
}
149+
get indexKey() {
150+
return `${ArrayDatabase.PREFIX}:index_${this.id}`;
151+
}
152+
get [Symbol.toStringTag]() {
153+
return this.id;
154+
}
155+
}
156+
ArrayDatabase.PROPERTY_MAX_SIZE = 12000;
157+
ArrayDatabase.PREFIX = 'array';

ArrayDatabase.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import { world } from '@minecraft/server';
2+
3+
export class ArrayDatabase<T = any> {
4+
public static readonly PROPERTY_MAX_SIZE = 12000;
5+
public static readonly PREFIX = 'array';
6+
7+
private readonly cache: T[][] = [];
8+
private cacheLoaded = false;
9+
private currentKeyIndex = 0;
10+
11+
constructor(public readonly id: string) {}
12+
13+
public getAll(): T[] {
14+
if (!this.cacheLoaded) this.load();
15+
const result: T[] = [];
16+
for (const value of this.cache) result.push(...value);
17+
return result;
18+
}
19+
20+
public add(value: T): this {
21+
if (!this.cacheLoaded) this.load();
22+
const baseData = this.cache[this.currentKeyIndex] ?? [];
23+
baseData.push(value);
24+
this.trySave(baseData);
25+
return this;
26+
}
27+
28+
public has(value: T) {
29+
const locations = this.getAll();
30+
return locations.includes(value);
31+
}
32+
33+
public clear() {
34+
const keys = world.getDynamicPropertyIds();
35+
const prefix = this.keyPrefix;
36+
for (const key of keys) {
37+
if (key.startsWith(prefix)) world.setDynamicProperty(key);
38+
}
39+
this.currentKeyIndex = 0;
40+
world.setDynamicProperty(this.indexKey);
41+
this.cache.length = 0;
42+
}
43+
44+
public *values(): IterableIterator<T> {
45+
if (!this.cacheLoaded) this.load();
46+
for (const values of this.cache) {
47+
for (const value of values) {
48+
yield value;
49+
}
50+
}
51+
}
52+
53+
public find(callbackfn: (value: T) => boolean): T | undefined {
54+
for (const value of this.values()) {
55+
if (callbackfn(value)) return value;
56+
}
57+
return undefined;
58+
}
59+
60+
public filter(callbackfn: (value: T) => boolean): T[] {
61+
const result: T[] = [];
62+
for (const value of this.values()) {
63+
if (callbackfn(value)) result.push(value);
64+
}
65+
return result;
66+
}
67+
68+
public map<U>(callbackfn: (value: T) => U): U[] {
69+
const result: U[] = [];
70+
for (const value of this.values()) {
71+
result.push(callbackfn(value));
72+
}
73+
return result;
74+
}
75+
76+
public forEach(callbackfn: (value: T) => void): void {
77+
for (const value of this.values()) {
78+
callbackfn(value);
79+
}
80+
}
81+
82+
public some(callbackfn: (value: T) => boolean): boolean {
83+
for (const value of this.values()) {
84+
if (callbackfn(value)) return true;
85+
}
86+
return false;
87+
}
88+
89+
public every(callbackfn: (value: T) => boolean): boolean {
90+
for (const value of this.values()) {
91+
if (!callbackfn(value)) return false;
92+
}
93+
return true;
94+
}
95+
96+
public [Symbol.iterator](): IterableIterator<T> {
97+
return this.values();
98+
}
99+
100+
private trySave(values: T[], swap: T[] = []) {
101+
let sizeOK = true;
102+
const stringified = JSON.stringify(values);
103+
if (stringified.length > ArrayDatabase.PROPERTY_MAX_SIZE) {
104+
sizeOK = false;
105+
} else {
106+
try {
107+
world.setDynamicProperty(this.currentKey, stringified);
108+
this.cache[this.currentKeyIndex] = values;
109+
} catch (e) {
110+
sizeOK = false;
111+
}
112+
}
113+
114+
if (!sizeOK) {
115+
if (values.length > 0) swap.push(values.shift()!);
116+
this.trySave(values, swap);
117+
this.currentKeyIndex++;
118+
world.setDynamicProperty(this.indexKey, this.currentKeyIndex);
119+
this.trySave(swap);
120+
}
121+
}
122+
123+
public get size(): number {
124+
return this.cache.reduce((acc, arr) => acc + arr.length, 0);
125+
}
126+
127+
private load() {
128+
this.currentKeyIndex = world.getDynamicProperty(this.indexKey) as number ?? 0;
129+
const keys = world.getDynamicPropertyIds();
130+
const prefix = this.keyPrefix;
131+
for (const key of keys) {
132+
if (key.includes('_index')) console.warn(key)
133+
if (!key.startsWith(prefix)) continue;
134+
const index = Number(key.slice(prefix.length));
135+
if (Number.isNaN(index)) {
136+
console.error(`Found invalid key: ${key}`);
137+
continue;
138+
}
139+
this.cache[index] = JSON.parse(
140+
world.getDynamicProperty(key) as string ?? '[]'
141+
);
142+
}
143+
this.cacheLoaded = true;
144+
}
145+
146+
private unload(): void {
147+
this.cache.length = 0;
148+
this.cacheLoaded = false;
149+
this.currentKeyIndex = 0;
150+
}
151+
152+
private get keyPrefix(): string {
153+
return `${ArrayDatabase.PREFIX}:${this.id}` as string;
154+
}
155+
156+
private get currentKey(): string {
157+
return `${this.keyPrefix}${this.currentKeyIndex}`;
158+
}
159+
160+
private get indexKey(): string {
161+
return `${ArrayDatabase.PREFIX}:index_${this.id}` as string;
162+
}
163+
164+
public get [Symbol.toStringTag](): string {
165+
return this.id;
166+
}
167+
}

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,31 @@
11
# ArrayDatabase
22
A database manager for storing unlimited array in ScriptAPI
3+
4+
## Usage
5+
JavaScript
6+
```js
7+
/** @type {ArrayDatabase<string>} */
8+
const db = new ArrayDatabase('test');
9+
10+
db.put('hello');
11+
db.put('world');
12+
db.get(); // ["hello", "world"]
13+
db.has('hello'); // true
14+
db.clear();
15+
db.size; // 0
16+
```
17+
18+
TypeScript
19+
```ts
20+
const db = new ArrayDatabase<Vector3>('test');
21+
22+
db.put({ x: 0, y: 0, z: 0 });
23+
db.put({ x: 1, y: 1, z: 1 });
24+
db.get(); // [{ x: 0, y: 0, z: 0 }, { x: 1, y: 1, z: 1 }]
25+
```
26+
27+
## Benchmark (put)
28+
||x10|x100|
29+
|-|-|-|
30+
|string(100bytes)|0ms|6ms|
31+
|vector3|1ms|34ms|

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"devDependencies": {
3+
"@minecraft/server": "1.10.0",
4+
"typescript": "^5.4.5"
5+
}
6+
}

0 commit comments

Comments
 (0)