Skip to content

Instantly share code, notes, and snippets.

@IanSSenne
Last active February 7, 2022 01:51
Show Gist options
  • Save IanSSenne/6c940f2a2224c8ade8aa1a8004876371 to your computer and use it in GitHub Desktop.
Save IanSSenne/6c940f2a2224c8ade8aa1a8004876371 to your computer and use it in GitHub Desktop.
a small Persistant storage i wrote for minecrafts gametest 1.18.2 (free for anyone to use with credit)
import * as MC from "mojang-minecraft";
interface IStringSaver {
save(value: string): void;
load(): string;
hasData(): boolean;
}
class MemorySaver implements IStringSaver {
private _data: string;
constructor() {
this._data = "";
}
public save(value: string): void {
this._data = value;
}
public load(): string {
return this._data;
}
public hasData(): boolean {
return this._data !== "";
}
}
class EntityNameSaver implements IStringSaver {
private _entities: MC.Entity[] = [];
constructor(private position: MC.BlockLocation, private prefix: string = "") {
if (this.prefix.includes("|")) {
throw new Error("Prefix cannot contain |");
}
this.syncEntities();
}
private syncEntities() {
this._entities = MC.World.getDimension("overworld")
.getEntitiesAtBlockLocation(this.position)
.filter((entity) => entity.nameTag.startsWith("DB" + this.prefix));
}
public save(value: string): void {
this.syncEntities();
let chunkSize = 32767 - 8 - this.prefix.length;
let chunkCount = Math.ceil(value.length / chunkSize);
if (this._entities.length < chunkCount) {
for (let i = this._entities.length; i < chunkCount; i++) {
let e = MC.World.getDimension("overworld").spawnEntity(
"minecraft:armor_stand",
this.position
);
// this is an empty payload, if for some reason one or more of these are not assigned data they will not effect the output
e.nameTag = "DB" + this.prefix + "|99999";
this._entities.push(e);
}
} else if (this._entities.length > chunkCount) {
for (let i = this._entities.length - 1; i > chunkCount; i--) {
this._entities[i].kill();
}
this._entities.splice(chunkCount);
}
for (let i = 0; i < chunkCount; i++) {
let chunk = value.substr(i * chunkSize, chunkSize);
this._entities[i].nameTag =
"DB" + this.prefix + "|" + i.toString().padStart(5, "0") + chunk;
}
}
public load(): string {
this.syncEntities();
let sortedEntities = this._entities.sort(
(a, b) =>
parseInt(
a.nameTag.substring(3 + this.prefix.length, 8 + this.prefix.length)
) -
parseInt(
b.nameTag.substring(3 + this.prefix.length, 8 + this.prefix.length)
)
);
let content = sortedEntities
.map((ent) => ent.nameTag.substring(8 + this.prefix.length))
.join("");
return content;
}
public hasData(): boolean {
this.syncEntities();
return this._entities.length > 0;
}
}
class DB<T extends IStringSaver> {
public static tickQueue: Function[] = [];
private value: Record<string, any> | null = null;
private lastSaveTime = 0;
private hasSaveQueued: boolean;
constructor(private saveHandler: T) {
this.value = null;
this.lastSaveTime = Date.now();
this.hasSaveQueued = false;
}
public queueSave(): void {
if (!this.hasSaveQueued) {
const handler = (e) => {
// if (this.lastSaveTime < Date.now() - 1000) {
this.saveData();
this.lastSaveTime = Date.now();
this.hasSaveQueued = false;
DB.tickQueue.splice(DB.tickQueue.indexOf(handler), 1);
// }
// MC.Commands.run(`say hi`, MC.World.getDimension("overworld"));
};
DB.tickQueue.push(handler);
this.hasSaveQueued = true;
}
}
private maybeLoadData() {
if (this.value !== null) {
return;
}
let data = this.saveHandler.hasData() ? this.saveHandler.load() : "{}";
this.value = JSON.parse(data);
}
public saveData(): void {
this.saveHandler.save(JSON.stringify(this.value));
}
public get(key: string): any {
this.maybeLoadData();
return this.value[key];
}
public set(key: string, value: any): void {
this.maybeLoadData();
this.value[key] = value;
this.queueSave();
}
public has(key: string): boolean {
this.maybeLoadData();
return this.value.hasOwnProperty(key);
}
public delete(key: string): void {
this.maybeLoadData();
delete this.value[key];
this.queueSave();
}
public clear(): void {
this.maybeLoadData();
this.value = {};
this.queueSave();
}
public keys(): string[] {
this.maybeLoadData();
return Object.keys(this.value);
}
public size(): number {
return this.keys().length;
}
public values(): any[] {
this.maybeLoadData();
return Object.values(this.value);
}
public entries(): [string, any][] {
this.maybeLoadData();
return Object.entries(this.value);
}
public forEach(
callback: (value: any, key: string, map: DB<T>) => void,
thisArg?: any
): void {
this.maybeLoadData();
Object.keys(this.value).forEach((key) => {
callback(this.value[key], key, this);
});
}
debug() {
return this.saveHandler.load().length;
}
}
MC.World.events.tick.subscribe(() => {
for (const handler of DB.tickQueue) {
try {
handler();
} catch (e) {
console.warn(e.message, e.stack);
}
}
});
// same as above but not minified
import * as MC from "mojang-minecraft";
class MemorySaver {
constructor() {
this._data = "";
}
save(value) {
this._data = value;
}
load() {
return this._data;
}
hasData() {
return this._data !== "";
}
}
class EntityNameSaver {
constructor(position, prefix = "") {
this.position = position;
this.prefix = prefix;
this._entities = [];
if (this.prefix.includes("|")) {
throw new Error("Prefix cannot contain |");
}
this.syncEntities();
}
syncEntities() {
this._entities = MC.World.getDimension("overworld")
.getEntitiesAtBlockLocation(this.position)
.filter((entity) => entity.nameTag.startsWith("DB" + this.prefix));
}
save(value) {
this.syncEntities();
let chunkSize = 32767 - 8 - this.prefix.length;
let chunkCount = Math.ceil(value.length / chunkSize);
if (this._entities.length < chunkCount) {
for (let i = this._entities.length; i < chunkCount; i++) {
let e = MC.World.getDimension("overworld").spawnEntity("minecraft:armor_stand", this.position);
// this is an empty payload, if for some reason one or more of these are not assigned data they will not effect the output
e.nameTag = "DB" + this.prefix + "|99999";
this._entities.push(e);
}
}
else if (this._entities.length > chunkCount) {
for (let i = this._entities.length - 1; i > chunkCount; i--) {
this._entities[i].kill();
}
this._entities.splice(chunkCount);
}
for (let i = 0; i < chunkCount; i++) {
let chunk = value.substr(i * chunkSize, chunkSize);
this._entities[i].nameTag =
"DB" + this.prefix + "|" + i.toString().padStart(5, "0") + chunk;
}
}
load() {
this.syncEntities();
let sortedEntities = this._entities.sort((a, b) => parseInt(a.nameTag.substring(3 + this.prefix.length, 8 + this.prefix.length)) -
parseInt(b.nameTag.substring(3 + this.prefix.length, 8 + this.prefix.length)));
let content = sortedEntities
.map((ent) => ent.nameTag.substring(8 + this.prefix.length))
.join("");
return content;
}
hasData() {
this.syncEntities();
return this._entities.length > 0;
}
}
class DB {
constructor(saveHandler) {
this.saveHandler = saveHandler;
this.value = null;
this.lastSaveTime = 0;
this.value = null;
this.lastSaveTime = Date.now();
this.hasSaveQueued = false;
}
queueSave() {
if (!this.hasSaveQueued) {
const handler = (e) => {
if (this.lastSaveTime < Date.now() - 1000) {
this.saveData();
this.lastSaveTime = Date.now();
this.hasSaveQueued = false;
DB.tickQueue.splice(DB.tickQueue.indexOf(handler), 1);
}
};
DB.tickQueue.push(handler);
this.hasSaveQueued = true;
}
}
maybeLoadData() {
if (this.value !== null) {
return;
}
let data = this.saveHandler.hasData() ? this.saveHandler.load() : "{}";
this.value = JSON.parse(data);
}
saveData() {
this.saveHandler.save(JSON.stringify(this.value));
}
get(key) {
this.maybeLoadData();
return this.value[key];
}
set(key, value) {
this.maybeLoadData();
this.value[key] = value;
this.queueSave();
}
has(key) {
this.maybeLoadData();
return this.value.hasOwnProperty(key);
}
delete(key) {
this.maybeLoadData();
delete this.value[key];
this.queueSave();
}
clear() {
this.maybeLoadData();
this.value = {};
this.queueSave();
}
keys() {
this.maybeLoadData();
return Object.keys(this.value);
}
size() {
return this.keys().length;
}
values() {
this.maybeLoadData();
return Object.values(this.value);
}
entries() {
this.maybeLoadData();
return Object.entries(this.value);
}
forEach(callback, thisArg) {
this.maybeLoadData();
Object.keys(this.value).forEach((key) => {
callback(this.value[key], key, this);
});
}
debug() {
return this.saveHandler.load().length;
}
}
DB.tickQueue = [];
MC.World.events.tick.subscribe(() => {
for (const handler of DB.tickQueue) {
try {
handler();
}
catch (e) {
console.warn(e.message, e.stack);
}
}
});
export { DB as DataStore, MemorySaver, EntityNameSaver };
// this is a module you can add to your gametest library that adds the PersistantStorage as DataStore
import*as t from"mojang-minecraft";class e{constructor(){this._data=""}save(t){this._data=t}load(){return this._data}hasData(){return""!==this._data}}class s{constructor(t,e=""){if(this.position=t,this.prefix=e,this._entities=[],this.prefix.includes("|"))throw new Error("Prefix cannot contain |");this.syncEntities()}syncEntities(){this._entities=t.World.getDimension("overworld").getEntitiesAtBlockLocation(this.position).filter((t=>t.nameTag.startsWith("DB"+this.prefix)))}save(e){this.syncEntities();let s=32759-this.prefix.length,i=Math.ceil(e.length/s);if(this._entities.length<i)for(let e=this._entities.length;e<i;e++){let e=t.World.getDimension("overworld").spawnEntity("minecraft:armor_stand",this.position);e.nameTag="DB"+this.prefix+"|99999",this._entities.push(e)}else if(this._entities.length>i){for(let t=this._entities.length-1;t>i;t--)this._entities[t].kill();this._entities.splice(i)}for(let t=0;t<i;t++){let i=e.substr(t*s,s);this._entities[t].nameTag="DB"+this.prefix+"|"+t.toString().padStart(5,"0")+i}}load(){return this.syncEntities(),this._entities.sort(((t,e)=>parseInt(t.nameTag.substring(3+this.prefix.length,8+this.prefix.length))-parseInt(e.nameTag.substring(3+this.prefix.length,8+this.prefix.length)))).map((t=>t.nameTag.substring(8+this.prefix.length))).join("")}hasData(){return this.syncEntities(),this._entities.length>0}}class i{constructor(t){this.saveHandler=t,this.value=null,this.lastSaveTime=0,this.value=null,this.lastSaveTime=Date.now(),this.hasSaveQueued=!1}queueSave(){if(!this.hasSaveQueued){const t=e=>{this.lastSaveTime<Date.now()-1e3&&(this.saveData(),this.lastSaveTime=Date.now(),this.hasSaveQueued=!1,i.tickQueue.splice(i.tickQueue.indexOf(t),1))};i.tickQueue.push(t),this.hasSaveQueued=!0}}maybeLoadData(){if(null!==this.value)return;let t=this.saveHandler.hasData()?this.saveHandler.load():"{}";this.value=JSON.parse(t)}saveData(){this.saveHandler.save(JSON.stringify(this.value))}get(t){return this.maybeLoadData(),this.value[t]}set(t,e){this.maybeLoadData(),this.value[t]=e,this.queueSave()}has(t){return this.maybeLoadData(),this.value.hasOwnProperty(t)}delete(t){this.maybeLoadData(),delete this.value[t],this.queueSave()}clear(){this.maybeLoadData(),this.value={},this.queueSave()}keys(){return this.maybeLoadData(),Object.keys(this.value)}size(){return this.keys().length}values(){return this.maybeLoadData(),Object.values(this.value)}entries(){return this.maybeLoadData(),Object.entries(this.value)}forEach(t,e){this.maybeLoadData(),Object.keys(this.value).forEach((e=>{t(this.value[e],e,this)}))}debug(){return this.saveHandler.load().length}}i.tickQueue=[],t.World.events.tick.subscribe((()=>{for(const t of i.tickQueue)try{t()}catch(t){console.warn(t.message,t.stack)}}));export{i as DataStore,e as MemorySaver,s as EntityNameSaver};
const testSaver = new EntityNameSaver(
new MC.BlockLocation(0, 200, 0),
"TestDB"
);
const testDB = new DB(testSaver);
let deltaTimeArray = [];
//tickevent
//@ts-ignore
MC.World.events.tick.subscribe(({ currentTick, deltaTime }) => {
for (let i = 0; i < 10000; i++) {
testDB.set("tick" + i, Date.now());
}
// @ts-ignore
deltaTimeArray.unshift(deltaTime);
if (deltaTimeArray.length > 250) {
deltaTimeArray.pop();
}
console.warn(
"TPS" +
(1 /
(deltaTimeArray.reduce(
(previousValue, currentValue) => previousValue + currentValue
) /
deltaTimeArray.length) +
"," +
deltaTimeArray.length)
);
});
MC.World.events.beforeChat.subscribe(({ message }) => {
try {
const [cmd, target, ...payloadarr] = message.split(" ");
const payload = payloadarr.join(" ");
if (Reflect.has(testDB, cmd)) {
let res = testDB[cmd](target, payload);
MC.Commands.run(
`say testDB.${cmd}("${target}"): ${res}`,
MC.World.getDimension("overworld")
);
}
} catch (e) {
MC.Commands.run(`say ${e.message}`, MC.World.getDimension("overworld"));
e.stack.split("\n").forEach((element) => {
MC.Commands.run(`say ${element}`, MC.World.getDimension("overworld"));
});
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment