From 6c579b386697ac3805bd510fab3be0a62d98b0e5 Mon Sep 17 00:00:00 2001 From: Rizky Date: Sun, 11 Feb 2024 18:48:38 +0700 Subject: [PATCH] wip fix --- app/web/src/nova/vi/load/load-snapshot.tsx | 18 +-- app/web/src/nova/vi/utils/simple-hash.ts | 15 +++ bun.lockb | Bin 268608 -> 269448 bytes pkgs/core/package.json | 6 +- pkgs/core/utils/diff.test.ts | 16 +++ pkgs/core/utils/diff.ts | 134 +++++++++++++++++++++ 6 files changed, 175 insertions(+), 14 deletions(-) create mode 100644 app/web/src/nova/vi/utils/simple-hash.ts create mode 100644 pkgs/core/utils/diff.test.ts create mode 100644 pkgs/core/utils/diff.ts diff --git a/app/web/src/nova/vi/load/load-snapshot.tsx b/app/web/src/nova/vi/load/load-snapshot.tsx index 863f1896..ceebfe58 100644 --- a/app/web/src/nova/vi/load/load-snapshot.tsx +++ b/app/web/src/nova/vi/load/load-snapshot.tsx @@ -1,11 +1,12 @@ import { compress, decompress } from "wasm-gzip"; +import { apiProxy } from "../../../base/load/api/api-proxy"; 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 { evalCJS } from "../../ed/logic/ed-sync"; import { treeRebuild } from "../../ed/logic/tree/build"; -import { w } from "../../../utils/types/general"; -import { dbProxy } from "../../../base/load/db/db-proxy"; -import { apiProxy } from "../../../base/load/api/api-proxy"; +import { simpleHash } from "../utils/simple-hash"; const encoder = new TextEncoder(); export const viLoadSnapshot = async (p: PG) => { @@ -22,7 +23,8 @@ export const viLoadSnapshot = async (p: PG) => { api: api.apiTypes, prisma: api.prismaTypes, }); - const hash = hashCode(zip); + + const hash = simpleHash(zip); const res = await p.sync?.code.action({ type: "check-typings", 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; -}; diff --git a/app/web/src/nova/vi/utils/simple-hash.ts b/app/web/src/nova/vi/utils/simple-hash.ts new file mode 100644 index 00000000..bf2d5c67 --- /dev/null +++ b/app/web/src/nova/vi/utils/simple-hash.ts @@ -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); +}; diff --git a/bun.lockb b/bun.lockb index d1beb6b840b42c33c1dec3f38ed49f456674e7c4..94bce9610e00aea081d2ae5360bbdd3d95f0e13a 100755 GIT binary patch delta 6111 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 QLp^`Pp|s{BgDkiH4)ZsKQ2+n{ delta 5872 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 { + 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"); +}); diff --git a/pkgs/core/utils/diff.ts b/pkgs/core/utils/diff.ts new file mode 100644 index 00000000..274597c3 --- /dev/null +++ b/pkgs/core/utils/diff.ts @@ -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 = + | { + mode: "new"; + ts: string; + data: number[]; + } + | { + mode: "patch"; + ts: string; + diff: any; + }; + +export class Diff { + ts = ""; + mode: "client" | "server" = "server"; + + private _data: number[] = []; + private _history = {} as Record; + + constructor() {} + + get data() { + const _data = new Uint8Array(this._data); + return new Promise((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 { + 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) { + 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(str: T) { + const diff = new Diff(); + await diff.update(str); + return diff; + } + + static async client(patch: PATCH_RESULT) { + const diff = new Diff(); + 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((resolve, reject) => { + gzip(bin, (err, res) => { + if (err) { + reject(err); + } else { + resolve(res); + } + }); + }); +}; + +export const gunzipAsync = (bin: Uint8Array) => { + return new Promise((resolve, reject) => { + gunzip(bin, (err, result) => { + if (err) { + reject(err); + } else { + resolve(result); + } + }); + }); +};