Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions client/bin/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ e.addListener(
pos = 1;
}

c.set(pos, text.refAt(e.pos));
c.set(pos - 1, text.refAt(e.pos));
});
},
c.signal,
Expand Down Expand Up @@ -106,7 +106,7 @@ e.addListener(
}
const last = api.at(0); // we just inserted ourself
const update = typeof last === 'number' ? last + 1 : 1;
api.set(1, update);
api.set(0, update);
console.warn('! setting num', update);
// TODO: we're not announcing this in the client anywhere

Expand Down
2 changes: 1 addition & 1 deletion client/lib/gumnut/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class DataMapImpl extends DataMap {
for (let i = 0; i < d.length; i += 2) {
if (k === d[i]) {
// found it
ino.set(i + 2, v);
ino.set(i + 1, v);
outer.refreshCache(); // TODO: we can be more surgical?
return;
}
Expand Down
19 changes: 9 additions & 10 deletions client/lib/gumnut/floor.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import test from 'node:test';
import * as assert from 'node:assert';
import { GumnutFloor } from './floor.ts';
import { stringToArray } from '../ymodel/helper.ts';

test('floor', () => {
test('floor server changes', () => {
const f = new GumnutFloor();

const changes1 = f.update({
p: {
def: {
r: [-5, 0],
s: [5, 'hello'],
},
def: [[-5, 0], 0, 'h', 'ello'], // confirms we can decode strings + multiple parts
},
});
assert.deepStrictEqual(f.byIno('def').string(), 'hello');
Expand All @@ -25,10 +23,11 @@ test('floor', () => {

const changes3 = f.update({
p: {
def: {
r: [0, 2, 5], // delete "llo"
s: [2, 'i'],
},
def: [
[0, 2, 5], // delete "llo"
1,
stringToArray('i'),
],
},
});
assert.deepStrictEqual(f.byIno('def').string(), 'hi');
Expand All @@ -44,7 +43,7 @@ test('floor local changes', () => {
assert.deepStrictEqual(changes1, new Map([['abc', { lo: 0, hi: 0, length: 3 }]]));

const changes2 = f.perform(function* (t) {
t.byIno('abc').set(2, 200);
t.byIno('abc').set(1, 200);
});
assert.deepStrictEqual(changes2, new Map([['abc', { lo: 1, hi: 2, length: 1 }]]));
});
158 changes: 27 additions & 131 deletions client/lib/gumnut/floor.ts
Original file line number Diff line number Diff line change
@@ -1,128 +1,26 @@
import { mapRecord, newChannel } from 'thorish';
import { GumnutLow } from '../ymodel/api.ts';
import type { Id } from '../ymodel/internal/shared.ts';
import {
decodeOpRun,
decodeSetOp,
encodePatch,
type Op,
type SetPart,
type Patch,
type WirePatch,
} from '../ymodel/types.ts';
import { emptySymbol, InoPosRef, InoPosRefImpl, type GumnutData } from './types.ts';
import { encodePatch, type Patch, type WirePatch, decodePatch } from '../ymodel/types.ts';
import { InoPosRef, InoPosRefImpl, type GumnutData } from './types.ts';
import type { PatchApi } from '../ymodel/work-holder.ts';
import type { RangeUpdate } from '../ymodel/internal/range.ts';
import { arrayToString } from '../ymodel/helper.ts';
import { decodeGumnutDataArray, encodeGumnutDataArray } from './wire.ts';

type WireType = {
b?: string[];
p: Record<string, WirePatch>;
};

class GumnutFloorApi extends GumnutLow<GumnutData, WireType> {
private encodeGumnutData(lookupId: (inode: string, id: Id) => number, data: GumnutData) {
switch (typeof data) {
case 'string': // intern string
case 'boolean': // true/false
return data;

case 'bigint':
return { p: `b:${data.toString()}` };

case 'number':
if (!isFinite(data)) {
if (data === +Infinity) {
return { p: '+Inf' };
} else if (data === -Infinity) {
return { p: '-Inf' };
} else {
return { p: 'NaN' };
}
}
return data;

case 'undefined':
return { p: 'undefined' };

case 'object':
if (data === null) {
return null;
}
break;

case 'symbol':
break;

default:
throw new Error(`unexpected data: ${JSON.stringify(data)}`);
}

if (data instanceof InoPosRefImpl) {
// we need to deref the ID here - on the way up to the server
const pos = lookupId(data.target, data.id);
return { s: data.target, t: pos };
} else if (data === emptySymbol) {
return { e: true };
}

throw new Error(`unexpected data: ${JSON.stringify(data)}`);
}

public decodeGumnutData(resolvePos: (inode: string, pos: number) => Id, raw: any): GumnutData {
switch (typeof raw) {
case 'string': // intern string
case 'boolean': // true/false
case 'number':
return raw;

case 'object':
if (raw === null) {
return raw;
}
break;

default:
throw new Error(`unexpected raw: ${JSON.stringify(raw)}`);
}

if ('e' in raw) {
return emptySymbol;
} else if ('p' in raw) {
switch (raw.p) {
case '+Inf':
return +Infinity;
case '-Inf':
return -Infinity;
case 'NaN':
return NaN;
case 'undefined':
return undefined;
}
if (raw.p.startsWith('b:')) {
return BigInt(raw.p.slice(2));
}

// TODO: unknown special
return emptySymbol;
} else if ('s' in raw) {
if ('t' in raw) {
// we got a real-space pos, convert to ID
const id = resolvePos(raw.s, raw.t);
return new InoPosRefImpl(raw.s, id);
}
return raw.s;
}

throw new Error(`unexpected raw: ${JSON.stringify(raw)}`);
}

protected patchToServer(
protected patchToWire(
barrier: string[],
update: Record<string, Patch<GumnutData>>,
lookupId: (inode: string, id: Id) => number,
): WireType {
const encodeGumnutData = this.encodeGumnutData.bind(this, lookupId);
const wireUpdate = mapRecord(update, (ino, patch) => encodePatch(patch, encodeGumnutData));
const boundEncode = encodeGumnutDataArray.bind(this, lookupId);
const wireUpdate = mapRecord(update, (ino, patch) => encodePatch(patch, boundEncode));

const out: WireType = { p: wireUpdate };
if (barrier.length) {
Expand All @@ -131,20 +29,12 @@ class GumnutFloorApi extends GumnutLow<GumnutData, WireType> {
return out;
}

protected serverToRun(w: WireType): Record<string, Op[]> {
return mapRecord(w.p, (id, patch) => decodeOpRun(patch.r ?? []));
}

protected serverToSet(
protected wireToPatch(
w: WireType,
resolvePos: (inode: string, pos: number) => Id,
): Record<string, SetPart<GumnutData>[]> {
const decodeGumnutData = this.decodeGumnutData.bind(this, resolvePos);
return mapRecord(w.p, (id, patch) => decodeSetOp(patch.s ?? [], decodeGumnutData));
}

constructor() {
super(emptySymbol);
): Record<string, Patch<GumnutData>> {
const boundDecode = decodeGumnutDataArray.bind(this, resolvePos);
return mapRecord(w.p, (id, patch) => decodePatch(patch, boundDecode));
}
}

Expand Down Expand Up @@ -245,13 +135,14 @@ export class GumnutFloor {
if (index < 0) {
index = internal.length() + index;
}
return internal.readData(index, index + 1)[0];
return internal.read(index, index + 1)[0];
},
data(start, end) {
return internal.readData(start, end);
return internal.read(start, end);
},
string(start, end) {
return internal.readString(start, end);
const raw = internal.read(start, end);
return arrayToString(raw);
},
refAt(at: number): InoPosRef {
const id = internal.posToId(at);
Expand All @@ -272,13 +163,14 @@ export class GumnutFloor {
if (index < 0) {
index = internal.length() + index;
}
return internal.readData(index, index + 1)[0];
return internal.read(index, index + 1)[0];
},
data(start, end) {
return internal.readData(start, end);
return internal.read(start, end);
},
string(start, end) {
return internal.readString(start, end);
const raw = internal.read(start, end);
return arrayToString(raw);
},
refAt(at: number): InoPosRef {
const id = internal.posToId(at);
Expand All @@ -287,8 +179,8 @@ export class GumnutFloor {

// TODO: above this is the same as byIno

set(before: number, ...data: GumnutData[]): void {
internal.applySet({ before, body: [data] });
set(at: number, ...data: GumnutData[]): void {
internal.applySet({ skip: at, body: data });
},

dataUpdate(at, deleteCount, ...data) {
Expand All @@ -297,7 +189,7 @@ export class GumnutFloor {
}
if (data.length) {
internal.applyOp({ r: [at], length: data.length });
internal.applySet({ before: at + data.length, body: [data] });
internal.applySet({ skip: at, body: data });
}
},

Expand All @@ -306,8 +198,12 @@ export class GumnutFloor {
internal.applyOp({ r: [at, at + deleteCount] });
}
if (str) {
const uint = new Uint16Array(str.length);
for (let i = 0; i < str.length; ++i) {
uint[i] = str.charCodeAt(i);
}
internal.applyOp({ r: [at], length: str.length });
internal.applySet({ before: at + str.length, body: [str] });
internal.applySet({ skip: at, body: Array.from(uint) });
}
},
};
Expand Down
Loading
Loading