This commit is contained in:
Rizky 2024-02-11 18:48:38 +07:00
parent bf1a6bd3a2
commit 6c579b3866
6 changed files with 175 additions and 14 deletions

View File

@ -1,11 +1,12 @@
import { compress, decompress } from "wasm-gzip"; import { compress, decompress } from "wasm-gzip";
import { apiProxy } from "../../../base/load/api/api-proxy";
import { loadApiProxyDef } from "../../../base/load/api/api-proxy-def"; import { loadApiProxyDef } from "../../../base/load/api/api-proxy-def";
import { dbProxy } from "../../../base/load/db/db-proxy";
import { w } from "../../../utils/types/general";
import { PG } from "../../ed/logic/ed-global"; import { PG } from "../../ed/logic/ed-global";
import { evalCJS } from "../../ed/logic/ed-sync"; import { evalCJS } from "../../ed/logic/ed-sync";
import { treeRebuild } from "../../ed/logic/tree/build"; import { treeRebuild } from "../../ed/logic/tree/build";
import { w } from "../../../utils/types/general"; import { simpleHash } from "../utils/simple-hash";
import { dbProxy } from "../../../base/load/db/db-proxy";
import { apiProxy } from "../../../base/load/api/api-proxy";
const encoder = new TextEncoder(); const encoder = new TextEncoder();
export const viLoadSnapshot = async (p: PG) => { export const viLoadSnapshot = async (p: PG) => {
@ -22,7 +23,8 @@ export const viLoadSnapshot = async (p: PG) => {
api: api.apiTypes, api: api.apiTypes,
prisma: api.prismaTypes, prisma: api.prismaTypes,
}); });
const hash = hashCode(zip);
const hash = simpleHash(zip);
const res = await p.sync?.code.action({ const res = await p.sync?.code.action({
type: "check-typings", type: "check-typings",
site_id: p.site.id, site_id: p.site.id,
@ -84,11 +86,3 @@ export const applyEnv = (p: PG, src?: string) => {
} }
} }
}; };
const hashCode = function (s: string) {
var h = 0,
l = s.length,
i = 0;
if (l > 0) while (i < l) h = ((h << 5) - h + s.charCodeAt(i++)) | 0;
return h;
};

View File

@ -0,0 +1,15 @@
export const simpleHash = (str: string, seed = 0) => {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};

BIN
bun.lockb

Binary file not shown.

View File

@ -13,10 +13,12 @@
"lmdb": "^2.8.5", "lmdb": "^2.8.5",
"mime": "^3.0.0", "mime": "^3.0.0",
"pino": "^8.16.1", "pino": "^8.16.1",
"msgpackr": "^1.10.1",
"pino-pretty": "^10.2.3", "pino-pretty": "^10.2.3",
"radash": "^11.0.0", "radash": "^11.0.0",
"radix3": "^1.1.0", "radix3": "^1.1.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"unzipper": "^0.10.14" "unzipper": "^0.10.14",
"fast-myers-diff": "^3.2.0"
} }
} }

View File

@ -0,0 +1,16 @@
import { describe, expect, test } from "bun:test";
import { Diff } from "./diff";
describe("simple diff", async () => {
const server = await Diff.server("ini ionadi a");
const patch = server.getPatch("new");
const client = await Diff.client(patch);
await server.update("rako12");
const newPatch = server.getPatch(client.ts);
await client.applyPatch(newPatch);
expect(await client.data).toBe("rako12");
});

134
pkgs/core/utils/diff.ts Normal file
View File

@ -0,0 +1,134 @@
import { applyPatch, calcPatch } from "fast-myers-diff";
import { Packr } from "msgpackr";
import { gunzip, gzip } from "zlib";
const MAX_HISTORY = 10;
const packr = new Packr({});
type PATCH_RESULT<T> =
| {
mode: "new";
ts: string;
data: number[];
}
| {
mode: "patch";
ts: string;
diff: any;
};
export class Diff<T> {
ts = "";
mode: "client" | "server" = "server";
private _data: number[] = [];
private _history = {} as Record<string, number[]>;
constructor() {}
get data() {
const _data = new Uint8Array(this._data);
return new Promise<T>((done) => {
if (typeof window === "undefined") {
gunzipAsync(_data).then((result) => {
done(packr.unpack(result));
});
}
});
}
async update(data: T) {
if (this.mode === "server") {
this._data = (await gzipAsync(packr.pack(data))).toJSON().data;
this.ts =
(Date.now() + "").substring(5) + Math.round(performance.now() * 10000);
this._history[this.ts] = this._data;
let i = 0;
for (const k of Object.keys(this._history).sort(
(a, b) => parseInt(b) - parseInt(a)
)) {
if (i > MAX_HISTORY) {
delete this._history[k];
}
i++;
}
}
}
getPatch(ts: "new" | string): PATCH_RESULT<T> {
if (ts !== "new") {
const old_data = this._history[ts];
if (old_data) {
const result_diff = [...calcPatch(old_data, this._data)];
return { diff: result_diff, mode: "patch", ts: this.ts };
}
}
return { data: this._data, mode: "new", ts: this.ts };
}
async applyPatch(patch: PATCH_RESULT<T>) {
if (patch.mode === "new") {
this.ts = patch.ts;
if (patch.data) this._data = patch.data;
} else {
this.ts = patch.ts;
const num_array = [];
for (const num of applyPatch(this._data, patch.diff)) {
if (Array.isArray(num)) {
for (const n of num) {
num_array.push(n);
}
} else {
num_array.push(num);
}
}
this._data = num_array;
}
}
static async server<T>(str: T) {
const diff = new Diff<T>();
await diff.update(str);
return diff;
}
static async client<T>(patch: PATCH_RESULT<T>) {
const diff = new Diff<T>();
diff.mode = "client";
if (patch.mode === "new") {
diff.ts = patch.ts;
if (patch.data) diff._data = patch.data;
}
return diff;
}
}
export const gzipAsync = (bin: Uint8Array | string) => {
return new Promise<Buffer>((resolve, reject) => {
gzip(bin, (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
};
export const gunzipAsync = (bin: Uint8Array) => {
return new Promise<Buffer>((resolve, reject) => {
gunzip(bin, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
};