From 0c25989104bef9785bf112490c73541bd4bc1764 Mon Sep 17 00:00:00 2001 From: James Hu <51237606+jameshu111@users.noreply.github.com> Date: Thu, 4 May 2023 10:38:40 -0400 Subject: First attempt at remfromlsit --- src/fields/List.ts | 8 ++++++- src/fields/util.ts | 3 ++- src/server/websocket.ts | 62 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/fields/List.ts b/src/fields/List.ts index 9c7794813..eb735dec2 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -77,10 +77,16 @@ const listHandlers: any = { item[OnUpdate] = updateFunction(list, i + start, item, this); } } + let hintArray: {val : any, index : number}[] = []; + for(let i = start; i < start + deleteCount; i++) { + hintArray.push({val : list.__fields[i], index : i}); + } const res = list.__fields.splice(start, deleteCount, ...items); + // console.log(hintArray); + // console.log(list.__fields); this[Update]( items.length === 0 && deleteCount - ? { op: '$remFromSet', items: removed, length: list.__fields.length } + ? { op: '$remFromSet', items: removed, hint : { start : start, deleteCount : deleteCount, hintArray : hintArray}, length: list.__fields.length } : items.length && !deleteCount && start === list.__fields.length ? { op: '$addToSet', items, length: list.__fields.length } : undefined diff --git a/src/fields/util.ts b/src/fields/util.ts index 92f3a69eb..254b70fa7 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -37,6 +37,7 @@ import { RichTextField } from './RichTextField'; import { SchemaHeaderField } from './SchemaHeaderField'; import { ComputedField } from './ScriptField'; import { ScriptCast, StrCast } from './Types'; +import { blue } from 'colors'; function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); @@ -377,7 +378,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any diff?.op === '$addToSet' ? { $addToSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List(diff.items)) } } : diff?.op === '$remFromSet' - ? { $remFromSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List(diff.items)) } } + ? { $remFromSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List(diff.items)), hint : diff.hint } } : { $set: { ['fields.' + prop]: SerializationHelper.Serialize(value) } }; !op.$set && ((op as any).length = diff.length); const prevValue = ObjectField.MakeCopy(lastValue as List); diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 2acdaa5a3..74b5dacaa 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -317,21 +317,77 @@ export namespace WebSocket { ); } + function findClosestIndex(list: any, indexesToDelete: number[], value: any, hintIndex : number) { + let closestIndex = -1; + for(let i = 0; i < list.length; i++) { + if(list[i] == value && !indexesToDelete.includes(i)) { + if(Math.abs(i - hintIndex) < Math.abs(closestIndex - hintIndex)) { + closestIndex = i; + } + } + } + return closestIndex; + } + function remFromListField(socket: Socket, diff: Diff, curListItems?: Transferable): void { + // console.log("##### %O", diff); diff.diff.$set = diff.diff.$remFromSet; delete diff.diff.$remFromSet; + // console.log(JSON.stringify(diff)); const updatefield = Array.from(Object.keys(diff.diff.$set))[0]; + // console.log("websocket/remfromlist: UPDATE FIELD: " + updatefield); const remListItems = diff.diff.$set[updatefield].fields; const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((f: any) => f !== null) || []; - diff.diff.$set[updatefield].fields = curList?.filter( - (curItem: any) => !remListItems.some((remItem: any) => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem)) - ); + const hint = diff.diff.$set.hint; + // console.log(remListItems); + // console.log(curList); + + + if(hint) { + // console.log("websocket/remfromlist: %O", hint); + // console.log(findClosestIndex([5, 5, 3], [], 5, 0)); + // console.log(findClosestIndex([5, 5, 3], [0], 5, 1)); + let indexesToRemove: number[] = []; + for(let i = 0; i < hint.deleteCount; i++) { + // console.log("VALUE: %d", remListItems[i]); + // console.log("HINT INDEX: %d", i + hint.start); + if(curList[i + hint.start] == remListItems[i]) { + indexesToRemove.push(i + hint.start); + continue; + } + + let closestIndex = findClosestIndex(curList, indexesToRemove, remListItems[i], i + hint.start); + if(closestIndex != -1) { + indexesToRemove.push(closestIndex); + } else { + console.log("Item to delete was not found - index = -1"); + } + } + + // console.log("INDEXES TO REMOVE: ", indexesToRemove); + diff.diff.$set[updatefield].fields = curList?.filter( + (curItem: any, index : number) => !(indexesToRemove.includes(index)) + ); + // console.log("websocket/remfromlist: newdiff: %O", diff.diff.$set[updatefield].fields); + } else { + diff.diff.$set[updatefield].fields = curList?.filter( + (curItem: any) => !remListItems.some((remItem: any) => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : + remItem.heading ? remItem.heading === curItem.heading : remItem === curItem)) + ); + // console.log("websocket/remfromlist: newdiff: %O", diff.diff.$set[updatefield].fields); + } + + const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; + // console.log("websocket/remfromlist: DIFFY " + JSON.stringify(diff.diff.$set)); + // console.log("websocket/remfromlist: DIFF LENGTH " + diff.diff.length); + // console.log("websocket/remfromlist: DIFF $SET " + diff.diff.$set[updatefield].fields); delete diff.diff.length; Database.Instance.update( diff.id, diff.diff, () => { + // console.log("websocket/remfromlist: lambda: %s", JSON.stringify(diff)) if (sendBack) { console.log('SEND BACK'); const id = socket.id; -- cgit v1.2.3-70-g09d2 From 488a3af4ffd9cac870a33a1ae358cf90fd957117 Mon Sep 17 00:00:00 2001 From: James Hu <51237606+jameshu111@users.noreply.github.com> Date: Sat, 6 May 2023 16:01:38 -0400 Subject: clean up and comment remFromList --- package-lock.json | 39 --------------------------------------- src/fields/List.ts | 6 +++--- src/server/websocket.ts | 49 +++++++++++++++++++++++++++++++------------------ 3 files changed, 34 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/package-lock.json b/package-lock.json index 7d2ef9509..5491e8c6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5580,16 +5580,6 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "d3": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.4.tgz", @@ -6970,28 +6960,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -7003,7 +6971,6 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { - "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -22676,12 +22643,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/src/fields/List.ts b/src/fields/List.ts index eb735dec2..325b36597 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -82,11 +82,11 @@ const listHandlers: any = { hintArray.push({val : list.__fields[i], index : i}); } const res = list.__fields.splice(start, deleteCount, ...items); - // console.log(hintArray); - // console.log(list.__fields); + // the hint object sends the starting index of the slice and the number + // of elements to delete. this[Update]( items.length === 0 && deleteCount - ? { op: '$remFromSet', items: removed, hint : { start : start, deleteCount : deleteCount, hintArray : hintArray}, length: list.__fields.length } + ? { op: '$remFromSet', items: removed, hint : { start : start, deleteCount : deleteCount }, length: list.__fields.length } : items.length && !deleteCount && start === list.__fields.length ? { op: '$addToSet', items, length: list.__fields.length } : undefined diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 74b5dacaa..e94b48f4f 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -317,6 +317,18 @@ export namespace WebSocket { ); } + /** + * findClosestIndex() is a helper function that will try to find + * the closest index of a list that has the same value as + * a specified argument/index pair. + * @param list the list to search through + * @param indexesToDelete a list of indexes that are already marked for deletion + * so they will be ignored + * @param value the value of the item to remove + * @param hintIndex the index that the element was at on the client's copy of + * the data + * @returns the closest index with the same value or -1 if the element was not found. + */ function findClosestIndex(list: any, indexesToDelete: number[], value: any, hintIndex : number) { let closestIndex = -1; for(let i = 0; i < list.length; i++) { @@ -329,28 +341,31 @@ export namespace WebSocket { return closestIndex; } + /** + * remFromListField() receives the items to remove and a hint + * from the client, and attempts to make the modification to the + * server's copy of the data. If server's copy does not match + * the client's after removal, the server will SEND BACk + * its version to the client. + * @param socket the socket that the client is connected on + * @param diff an object containing the items to remove and a hint + * (the hint contains start index and deleteCount, the number of + * items to delete) + * @param curListItems the server's current copy of the data + */ function remFromListField(socket: Socket, diff: Diff, curListItems?: Transferable): void { - // console.log("##### %O", diff); diff.diff.$set = diff.diff.$remFromSet; delete diff.diff.$remFromSet; - // console.log(JSON.stringify(diff)); const updatefield = Array.from(Object.keys(diff.diff.$set))[0]; - // console.log("websocket/remfromlist: UPDATE FIELD: " + updatefield); const remListItems = diff.diff.$set[updatefield].fields; const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((f: any) => f !== null) || []; const hint = diff.diff.$set.hint; - // console.log(remListItems); - // console.log(curList); if(hint) { - // console.log("websocket/remfromlist: %O", hint); - // console.log(findClosestIndex([5, 5, 3], [], 5, 0)); - // console.log(findClosestIndex([5, 5, 3], [0], 5, 1)); + // indexesToRemove stores the indexes that we mark for deletion, which is later used to filter the list (delete the elements) let indexesToRemove: number[] = []; for(let i = 0; i < hint.deleteCount; i++) { - // console.log("VALUE: %d", remListItems[i]); - // console.log("HINT INDEX: %d", i + hint.start); if(curList[i + hint.start] == remListItems[i]) { indexesToRemove.push(i + hint.start); continue; @@ -364,31 +379,29 @@ export namespace WebSocket { } } - // console.log("INDEXES TO REMOVE: ", indexesToRemove); diff.diff.$set[updatefield].fields = curList?.filter( (curItem: any, index : number) => !(indexesToRemove.includes(index)) ); - // console.log("websocket/remfromlist: newdiff: %O", diff.diff.$set[updatefield].fields); } else { + // go back to the original way to delete if we didn't receive + // a hint from the client diff.diff.$set[updatefield].fields = curList?.filter( (curItem: any) => !remListItems.some((remItem: any) => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem)) ); - // console.log("websocket/remfromlist: newdiff: %O", diff.diff.$set[updatefield].fields); } - + // if the client and server have different versions of the data after + // deletion, they will have different lengths and the server will + // send its version of the data to the client const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; - // console.log("websocket/remfromlist: DIFFY " + JSON.stringify(diff.diff.$set)); - // console.log("websocket/remfromlist: DIFF LENGTH " + diff.diff.length); - // console.log("websocket/remfromlist: DIFF $SET " + diff.diff.$set[updatefield].fields); delete diff.diff.length; Database.Instance.update( diff.id, diff.diff, () => { - // console.log("websocket/remfromlist: lambda: %s", JSON.stringify(diff)) if (sendBack) { + // the two copies are different, so the server sends its copy. console.log('SEND BACK'); const id = socket.id; socket.id = ''; -- cgit v1.2.3-70-g09d2 From b9d00178df75e1052bf26964bea37dcd0021ba63 Mon Sep 17 00:00:00 2001 From: James Hu <51237606+jameshu111@users.noreply.github.com> Date: Sat, 6 May 2023 16:02:36 -0400 Subject: remove unused import from utils.ts --- src/fields/util.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/fields/util.ts b/src/fields/util.ts index 254b70fa7..d5b55867e 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -37,7 +37,6 @@ import { RichTextField } from './RichTextField'; import { SchemaHeaderField } from './SchemaHeaderField'; import { ComputedField } from './ScriptField'; import { ScriptCast, StrCast } from './Types'; -import { blue } from 'colors'; function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); -- cgit v1.2.3-70-g09d2