From 90bc7d0eb4ae9faa338b2be96073564e7f31c86d Mon Sep 17 00:00:00 2001 From: Damiano Di Vincenzo Date: Wed, 18 Oct 2023 19:41:36 +0200 Subject: [PATCH 1/2] engine remove action update --- src/common/U.ts | 2 +- src/joiner/classes.ts | 39 ++++++++---- src/joiner/proxy.ts | 3 + src/redux/reducer/reducer.ts | 112 +++++++++++++++++++++-------------- 4 files changed, 100 insertions(+), 56 deletions(-) diff --git a/src/common/U.ts b/src/common/U.ts index d4d5d5c9f..7cd4796d3 100644 --- a/src/common/U.ts +++ b/src/common/U.ts @@ -768,7 +768,7 @@ export class U { } // returns true only if parameter is already a number by type. UU.isNumber('3') will return false - static isNumber(o: any): boolean { return +o === o && !isNaN(o); } + static isNumber(o: any): o is number { return typeof o === "number" && !isNaN(o); } public static getAllPrototypes(constructor: Constructor, chainoutoutrecursive: GObject[] = [], currentRecursion = 0, maxRecursion = 20, cache: boolean = true): GObject[] { // console.log('getAllPrototypes:', {name: constructor.name, currentRecursion, constructor, chainoutoutrecursive}); diff --git a/src/joiner/classes.ts b/src/joiner/classes.ts index 33ed3586d..883b5bb79 100644 --- a/src/joiner/classes.ts +++ b/src/joiner/classes.ts @@ -421,7 +421,8 @@ export class Constructors{ this.callbacks = []; if (this.thiss.hasOwnProperty("father")) { (this.thiss as any).father = father; - persist && father && SetFieldAction.new(father, "pointedBy", PointedBy.fromID(t.id, "father" as any), '+='); + // id still is not assigned here + persist && father && this.callbacks.push(()=>SetFieldAction.new(father, "pointedBy", PointedBy.fromID(t.id, "father" as any), '+=')); } this.fatherType = fatherType as any; if (this.persist) BEGIN() @@ -1256,24 +1257,30 @@ export class PointedBy{ - public static remove(oldValue: Pointer | undefined, action: ParsedAction, state: DState, casee: "+=" | "-=" | undefined = undefined): DState { + public static remove(oldValue: Pointer | undefined, action: ParsedAction, state: DState, casee: "+=" | "-=" | undefined = undefined, oldState?:DState): DState { if (!oldValue) return state; - let oldtarget: DPointerTargetable = state.idlookup[oldValue];// todo: if += -= + let oldtarget: DPointerTargetable = state.idlookup[oldValue]; if (!oldtarget) return state; let index = -1; let actionpath: string = action.path.substring(0, action.path.length -(casee?.length || 0)) - for (let i = 0; i < oldtarget.pointedBy.length; i++) { if (oldtarget.pointedBy[i].source === actionpath) {index = i; break; } } - if (index >= 0) { - state = {...state} as DState; - state.idlookup = {...state.idlookup}; - state.idlookup[oldValue] = {...oldtarget, pointedBy: [...oldtarget.pointedBy]} as any; - state.idlookup[oldValue].pointedBy.splice(index, 1) // in-place edit + for (let i = 0; i < oldtarget.pointedBy.length; i++) { if (oldtarget.pointedBy[i].source === actionpath) { index = i; break; } } + if (index < 0) return state; + + if (oldState === state) state = {...state} as DState; + if (oldState?.idlookup === state.idlookup) state.idlookup = {...state.idlookup}; + if (oldState?.idlookup[oldValue] === state.idlookup[oldValue]) { + state.idlookup[oldValue] = {...oldtarget} as any; + } + else { + // no need } + state.idlookup[oldValue].pointedBy.splice(index, 1) // in-place edit + // console.warn('pointedby remove:', {from: oldtarget.pointedBy, to: state.idlookup[oldValue].pointedBy, obj: state.idlookup[oldValue], index, oldValue, actionpath}); return state; } - public static add(newtargetptr: Pointer | undefined, action: ParsedAction, state: DState, casee: "+=" | "-=" | undefined = undefined): DState { + public static add(newtargetptr: Pointer | undefined, action: ParsedAction, state: DState, casee: "+=" | "-=" | undefined = undefined, oldState?:DState): DState { if (!newtargetptr) return state; // todo: if can't be done because newtarget doesn't exist, build an action from this and set it pending. let newtarget: DPointerTargetable = state.idlookup[newtargetptr]; @@ -1281,10 +1288,18 @@ export class PointedBy{ PendingPointedByPaths.new(action, state).saveForLater(); // {from: action.path, field: action.field, to: target}); return state; } - let oldtarget = {...newtarget, pointedBy: [...newtarget.pointedBy]} + /* simpler version but does unnecessary shallow copies state = {...state} as DState; state.idlookup = {...state.idlookup}; - state.idlookup[newtargetptr] = {...newtarget, pointedBy: [...newtarget.pointedBy, PointedBy.new(action.path, casee)]} as any; + state.idlookup[newtargetptr] = {...newtarget, pointedBy: [...newtarget.pointedBy, PointedBy.new(action.path, casee)]} as any;*/ + if (oldState === state) state = {...state} as DState; + if (oldState?.idlookup === state.idlookup) state.idlookup = {...state.idlookup}; + if (oldState?.idlookup[newtargetptr] === state.idlookup[newtargetptr]) { + state.idlookup[newtargetptr] = {...newtarget, pointedBy: [...newtarget.pointedBy, PointedBy.new(action.path, casee)]} as any; + } + else { + state.idlookup[newtargetptr].pointedBy = [...newtarget.pointedBy, PointedBy.new(action.path, casee)]; + } // console.warn('pointedby add:', {from: oldtarget.pointedBy, to: state.idlookup[newtargetptr].pointedBy, obj: state.idlookup[newtargetptr]}); return state; } diff --git a/src/joiner/proxy.ts b/src/joiner/proxy.ts index 0b6e46c88..ea00a67f6 100644 --- a/src/joiner/proxy.ts +++ b/src/joiner/proxy.ts @@ -239,6 +239,9 @@ export class TargetableProxyHandler remove a from y.pointedby const elementsThatChangedIndex: DPointerTargetable[] = current[key].slice(index); - todo: problema: se ho [dobj1, dobj2]... e li swappo, cambia un indice nel percorso "pointedby" e non me ne accorgo mai e un oggetto risulta "pointedby" da oggetti che non lo puntano o non esistono più a quell'indice + for (let j = 0; j < elementsThatChangedIndex.length; j++) { let newindex = index + j - 1; let oldFullpathTrimmed = action.pathArray.join('.'); se realizzi "pointedby" qui è to do: remove old paths and re-add them with updated index - }*/ - //unpointedElement = newRoot.idlookup[oldValue]; + } + unpointedElement = newRoot.idlookup[oldValue]; + */ } - } - else if (current[key] !== newVal) { - // todo: caso in cui setto manualmente classes.1 = pointer; // the latest element is array and not DPointerTargetable, so might need to buffer upper level in the tree? or instead of "current" keep an array of sub-objects encountered navigating the path in state. + } else + if (action.type === DeleteElementAction.type ? !(key in current) : current[key] === newVal) { + gotChanged = false; + // value not changed + } else { + // todo: caso in cui setto manualmente classes.1 = pointer; + // the latest element is array and not DPointerTargetable, so might need to buffer upper level in the tree? or instead of "current" keep an array of sub-objects encountered navigating the path in state. oldValue = current[key]; gotChanged = true; - unpointedElement = newRoot.idlookup[oldValue]; + // unpointedElement = newRoot.idlookup[oldValue]; // NB: se elimino un oggetto che contiene array di puntatori, o resetto l'array di puntatori kinda like store.arr= [ptr1, ptr2, ...]; store.arr = []; // i puntati dall'array hanno i loro pointedBY non aggiornati, non voglio fare un deep check di tutto l'oggetto a cercare puntatori per efficienza. - if (newVal === undefined) delete current[key]; + // if (newVal === undefined) delete current[key]; + if (action.type === DeleteElementAction.type) delete current[key]; else current[key] = newVal; - if (action.isPointer) { - if (Array.isArray(action.value)) { - let oldpointerdestinations: Pointer[] = oldValue; - let difference = U.arrayDifference(oldpointerdestinations, current[key]); // : {added: Pointer[], removed: Pointer[], starting: Pointer[], final: Pointer[]} - for (let rem of difference.removed) { newRoot = PointedBy.remove(rem as Pointer, action, newRoot); } - for (let add of difference.added) { newRoot = PointedBy.add(add as Pointer, action, newRoot); } - // a.pointsto = [a, b, c]; a.pointsto = [a, b, x]; ------> c.pointedby.remove(a) & x.pointedby.add(a) - // idlookup.somelongid.pointsto = [...b]; + // update pointedBy's + // NB: even if the current action have isPointer=true, it doesn't mean the old value is a pointer as well for sure. so need to check old values. + // also what if old val is pointer, and new one isn't? will it be just removed without updating pointedBy's? + // already fixed: might need to evaluate this if block always regardless of action.isPointer, + // and do checks every time both on old and new value if they actually are ptrs. + if (true || action.isPointer) { + let oldpointerdestinations: unknown[]; + let newpointerdestinations: unknown[]; + if (Array.isArray(newVal)) { + newpointerdestinations = newVal; + if (Array.isArray(oldValue)) { // case: path.array = array; + oldpointerdestinations = oldValue; + } + else { // case: path.object = array; + case: path.value = array; + oldpointerdestinations = [oldValue]; + } } else { - // a.pointsto = b; a.pointsto = c; ------> b.pointedby.remove(a) - newRoot = PointedBy.remove(oldValue as Pointer, action, newRoot); - newRoot = PointedBy.add(current[key] as Pointer, action, newRoot); + // case: path.array = object; + case: path.array = value; + newpointerdestinations = [newVal]; + if (Array.isArray(oldValue)) { + oldpointerdestinations = oldValue; + } else { + // case: path.object = object; and all other cases without arrays involved + oldpointerdestinations = [oldValue]; + } } + // after i mapped all cases to path.array = array; i solve it for that case. + let difference = U.arrayDifference(oldpointerdestinations, newpointerdestinations); // : {added: Pointer[], removed: Pointer[], starting: Pointer[], final: Pointer[]} + for (let rem of difference.removed) {if (Pointers.isPointer(rem)) + newRoot = PointedBy.remove(rem, action, newRoot, undefined, oldStateDoNotModify); } + for (let add of difference.added) { if (Pointers.isPointer(add)) + newRoot = PointedBy.add(add, action, newRoot, undefined, oldStateDoNotModify); } + // a.pointsto = [a, b, c]; a.pointsto = [a, b, x]; ------> c.pointedby.remove(a) & x.pointedby.add(a) + // idlookup.somelongid.pointsto = [...b]; } - } else { - gotChanged = false; - // value not changed - } - - let fullpathTrimmed = action.pathArray.join('.'); - /*if (unpointedElement) { - if (isArrayAppend || isArrayAppend) fullpathTrimmed.substr(0, fullpathTrimmed.length - 2); - U.arrayRemoveAll(unpointedElement.pointedBy, fullpathTrimmed); // todo: se faccio una insert in mezzo ad un array devo aggiustare tutti i path di pointedby... } - let newlyPointedElement = newRoot.idlookup[newVal]; - if (newlyPointedElement) { - U.ArrayAdd(newlyPointedElement.pointedBy, fullpathTrimmed); - }*/ - // console.log('deepCopyButOnlyFollowingPath final', {current, i, imax:action.pathArray.length, key, isArrayAppend, gotChanged, alreadyPastDivergencePoint}); break; } Log.exDevv('should not reach here: reducer'); From 73126dbbb35d63795998478e9594cb68b4f2d306 Mon Sep 17 00:00:00 2001 From: Damiano Di Vincenzo Date: Wed, 18 Oct 2023 20:09:30 +0200 Subject: [PATCH 2/2] Update reducer.ts --- src/redux/reducer/reducer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/redux/reducer/reducer.ts b/src/redux/reducer/reducer.ts index 95f55b4ba..0564fdaa4 100644 --- a/src/redux/reducer/reducer.ts +++ b/src/redux/reducer/reducer.ts @@ -129,7 +129,7 @@ function deepCopyButOnlyFollowingPath(oldStateDoNotModify: DState, action: Parse // NB: se elimino un oggetto che contiene array di puntatori, o resetto l'array di puntatori kinda like store.arr= [ptr1, ptr2, ...]; store.arr = []; // i puntati dall'array hanno i loro pointedBY non aggiornati, non voglio fare un deep check di tutto l'oggetto a cercare puntatori per efficienza. // if (newVal === undefined) delete current[key]; - if (action.type === DeleteElementAction.type) delete current[key]; + if ((newVal === undefined) || false && action.type === DeleteElementAction.type) delete current[key]; else current[key] = newVal; // update pointedBy's