Skip to content

Commit 52fed9b

Browse files
committed
AEDebugging_ Introduce DebuggingCache for change detection _ fix tests
SQUASHED: AUTO-COMMIT-src-client-reactive-active-expression-rewriting-active-expression-rewriting.js,AUTO-COMMIT-src-client-reactive-components-rewritten-aexpr-test-component.js,AUTO-COMMIT-src-components-tools-lively-editor.js,AUTO-COMMIT-src-components-widgets-lively-code-mirror.js,
1 parent 215a43a commit 52fed9b

File tree

4 files changed

+202
-100
lines changed

4 files changed

+202
-100
lines changed

src/client/reactive/active-expression-rewriting/active-expression-rewriting.js

Lines changed: 195 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import BidirectionalMultiMap from './bidirectional-multi-map.js';
1414
import { isFunction } from 'utils';
1515
import lively from "src/client/lively.js";
1616
import _ from 'src/external/lodash/lodash.js';
17+
import diff from 'src/external/diff-match-patch.js';
1718

1819
/*MD # Dependency Analysis MD*/
1920

@@ -213,36 +214,192 @@ class Dependency {
213214
}
214215

215216
}
217+
/*MD # Debugging Cache MD*/
218+
219+
export class AEDebuggingCache {
220+
221+
constructor() {
222+
this.registeredDebuggingViews = [];
223+
this.debouncedUpdateDebuggingViews = _.debounce(this.updateDebggingViews, 100);
224+
}
225+
/*MD ## Registration MD*/
226+
async registerFileForAEDebugging(url, context, triplesCallback) {
227+
const callback = async () => {
228+
if (context && (!context.valid || context.valid())) {
229+
triplesCallback((await this.getDependencyTriplesForFile(url)));
230+
return true;
231+
}
232+
return false;
233+
};
234+
this.registeredDebuggingViews.push(callback);
235+
236+
triplesCallback((await this.getDependencyTriplesForFile(url)));
237+
}
238+
239+
/*MD ## Caching MD*/
240+
241+
/*MD ## Code Change API MD*/
242+
async updateFile(url, oldCode, newCode) {
243+
try {
244+
const lineMapping = this.calculateMapping(oldCode, newCode);
245+
for (const ae of DependenciesToAExprs.getAEsInFile(url)) {
246+
const location = ae.meta().get("location");
247+
this.remapLocation(lineMapping, location);
248+
}
249+
for (const hook of await HooksToDependencies.getHooksInFile(url)) {
250+
hook.getLocations().then(locations => {
251+
for (const location of locations) {
252+
this.remapLocation(lineMapping, location);
253+
}
254+
});
255+
}
256+
} catch (e) {
257+
console.error(e);
258+
}
259+
}
260+
261+
remapLocation(lineMapping, location) {
262+
let [diff, lineChanged] = lineMapping[location.start.line];
263+
264+
if (diff === -1) {
265+
location.start.line = 0;
266+
} else {
267+
location.start.line = diff;
268+
}
269+
270+
let [diff2, lineChanged2] = lineMapping[location.end.line];
271+
272+
if (diff2 === -1) {
273+
location.end.line = 0;
274+
} else {
275+
location.end.line = diff2;
276+
}
277+
if (lineChanged || lineChanged2) {
278+
lively.notify("Changed AE code for existing AE. There are outdated expressions in the system");
279+
//ToDo: Check if the content is similar enough, mark AEs as outdated
280+
}
281+
}
282+
283+
calculateMapping(oldCode, newCode) {
284+
const mapping = []; //For each line of oldCode: new Line in newCode if existing, changedContent if the line no longer exists but can be mapped to a newCode line
285+
var dmp = new diff.diff_match_patch();
286+
var a = dmp.diff_linesToChars_(oldCode, newCode);
287+
var lineText1 = a.chars1;
288+
var lineText2 = a.chars2;
289+
var lineArray = a.lineArray;
290+
var diffs = dmp.diff_main(lineText1, lineText2, false);
291+
292+
let originalLine = 0;
293+
let newLine = 0;
294+
let recentDeletions = 0;
295+
let recentAdditions = 0;
296+
for (let [type, data] of diffs) {
297+
if (type === 0) {
298+
recentDeletions = 0;
299+
recentAdditions = 0;
300+
for (let i = 0; i < data.length; i++) {
301+
mapping[originalLine] = [newLine, false];
302+
originalLine++;
303+
newLine++;
304+
}
305+
} else if (type === 1) {
306+
if (recentDeletions > 0) {
307+
const matchingLines = Math.max(recentDeletions, data.length);
308+
for(let i = 0; i < matchingLines; i++) {
309+
mapping[originalLine - matchingLines + i] = [newLine, true];
310+
newLine++;
311+
}
312+
data = data.substring(matchingLines)
313+
}
314+
if(data.length > 0) {
315+
newLine += data.length;
316+
recentAdditions += data.length;
317+
}
318+
} else {
319+
if (recentAdditions > 0) {
320+
const matchingLines = Math.max(recentAdditions, data.length);
321+
for(let i = 0; i < matchingLines; i++) {
322+
mapping[originalLine] = [newLine - matchingLines + i, true];
323+
originalLine++;
324+
}
325+
data = data.substring(matchingLines)
326+
}
327+
recentDeletions += data.length;
328+
for (let i = 0; i < data.length; i++) {
329+
mapping[originalLine] = [-1, false];
330+
originalLine++;
331+
}
332+
}
333+
}
334+
335+
dmp.diff_charsToLines_(diffs, lineArray);
336+
337+
return mapping;
338+
}
339+
/*MD ## Rewriting API MD*/
340+
async updateDebggingViews() {
341+
for (let i = 0; i < this.registeredDebuggingViews.length; i++) {
342+
if (!(await this.registeredDebuggingViews[i]())) {
343+
this.registeredDebuggingViews.splice(i, 1);
344+
i--;
345+
}
346+
}
347+
}
348+
349+
async getDependencyTriplesForFile(url) {
350+
const result = [];
351+
for (const ae of DependenciesToAExprs.getAEsInFile(url)) {
352+
for (const dependency of DependenciesToAExprs.getDepsForAExpr(ae)) {
353+
for (const hook of HooksToDependencies.getHooksForDep(dependency)) {
354+
result.push({ hook, dependency, ae });
355+
}
356+
}
357+
}
358+
for (const hook of await HooksToDependencies.getHooksInFile(url)) {
359+
for (const dependency of HooksToDependencies.getDepsForHook(hook)) {
360+
for (const ae of DependenciesToAExprs.getAExprsForDep(dependency)) {
361+
const location = ae.meta().get("location").file;
362+
// if the AE is also in this file, we already covered it with the previous loop
363+
if (!location.includes(url)) {
364+
result.push({ hook, dependency, ae });
365+
}
366+
}
367+
}
368+
}
369+
return result;
370+
}
371+
}
372+
export const DebuggingCache = new AEDebuggingCache();
216373

217374
const DependenciesToAExprs = {
218375
_depsToAExprs: new BidirectionalMultiMap(),
219376
_AEsPerFile: new Map(),
220377

221378
associate(dep, aexpr) {
222-
const location = aexpr.meta().get("location").file;
223-
if(!this._AEsPerFile.has(location)) {
379+
const location = aexpr.meta().get("location");
380+
if (location && location.file && !this._AEsPerFile.has(location.file)) {
224381
this._AEsPerFile.set(location, new Set());
225382
}
226383
this._AEsPerFile.get(location).add(aexpr);
227384
this._depsToAExprs.associate(dep, aexpr);
228385
dep.updateTracking();
229-
debouncedUpdateDebuggingViews();
386+
DebuggingCache.debouncedUpdateDebuggingViews();
230387
},
231388

232389
disconnectAllForAExpr(aexpr) {
233-
const location = aexpr.meta().get("location").file;
234-
if(this._AEsPerFile.has(location)) {
390+
const location = aexpr.meta().get("location");
391+
if (location && location.file && this._AEsPerFile.has(location.file)) {
235392
this._AEsPerFile.get(location).delete(aexpr);
236393
}
237394
const deps = this.getDepsForAExpr(aexpr);
238395
this._depsToAExprs.removeAllLeftFor(aexpr);
239396
deps.forEach(dep => dep.updateTracking());
240-
debouncedUpdateDebuggingViews();
397+
DebuggingCache.debouncedUpdateDebuggingViews();
241398
},
242399

243400
getAEsInFile(url) {
244-
for(const [location, aes] of this._AEsPerFile.entries()) {
245-
if(location.includes(url)) return aes;
401+
for (const [location, aes] of this._AEsPerFile.entries()) {
402+
if (location.includes(url)) return aes;
246403
}
247404
return [];
248405
},
@@ -275,27 +432,29 @@ const HooksToDependencies = {
275432

276433
associate(hook, dep) {
277434
this._hooksToDeps.associate(hook, dep);
278-
debouncedUpdateDebuggingViews();
435+
DebuggingCache.debouncedUpdateDebuggingViews();
279436
},
280-
437+
281438
remove(hook, dep) {
282439
this._hooksToDeps.remove(hook, dep);
283-
debouncedUpdateDebuggingViews();
440+
DebuggingCache.debouncedUpdateDebuggingViews();
284441
},
285442

286443
async getHooksInFile(url) {
287444
const hooksWithLocations = await Promise.all(this._hooksToDeps.getAllLeft().map(hook => {
288-
return hook.getLocations().then(locations => {return {hook, locations}});
289-
}))
290-
return hooksWithLocations.filter(({hook, locations}) => {
291-
const location = locations.find(loc => loc && loc.source);
292-
return location && locations.source.includes(url);
293-
}).map(({hook, locations}) => hook);
445+
return hook.getLocations().then(locations => {
446+
return { hook, locations };
447+
});
448+
}));
449+
return hooksWithLocations.filter(({ hook, locations }) => {
450+
const location = locations.find(loc => loc && loc.file);
451+
return location && location.file.includes(url);
452+
}).map(({ hook, locations }) => hook);
294453
},
295454

296455
disconnectAllForDependency(dep) {
297456
this._hooksToDeps.removeAllLeftFor(dep);
298-
debouncedUpdateDebuggingViews();
457+
DebuggingCache.debouncedUpdateDebuggingViews();
299458
},
300459

301460
getDepsForHook(hook) {
@@ -358,14 +517,15 @@ class Hook {
358517
}
359518

360519
addLocation(location) {
361-
if(!this.locations.some(loc => _.isEqual(loc, location))) {
520+
if (!this.locations.some(loc => _.isEqual(loc, location))) {
362521
this.locations.push(location);
363522
}
364523
//Todo: Promises get added multiple times... Also, use a Set?
365524
}
366525

367526
async getLocations() {
368-
return await Promise.all(this.locations);
527+
this.locations = await Promise.all(this.locations);
528+
return this.locations;
369529
}
370530

371531
notifyDependencies() {
@@ -378,12 +538,12 @@ class SourceCodeHook extends Hook {
378538
const compKey = ContextAndIdentifierCompositeKey.for(context, identifier);
379539
return CompositeKeyToSourceCodeHook.getOrCreateRightFor(compKey, key => new SourceCodeHook());
380540
}
381-
541+
382542
static get(context, identifier) {
383543
const compKey = ContextAndIdentifierCompositeKey.for(context, identifier);
384-
return CompositeKeyToSourceCodeHook.getRightFor(compKey);
544+
return CompositeKeyToSourceCodeHook.getRightFor(compKey);
385545
}
386-
546+
387547
constructor(context, identifier) {
388548
super();
389549

@@ -713,28 +873,28 @@ class TracingHandler {
713873
* ********************** update ********************************
714874
* **************************************************************
715875
*/
716-
static memberUpdated(obj, prop, location) {
876+
static memberUpdated(obj, prop, location) {
717877
const hook = SourceCodeHook.get(obj, prop);
718-
if(!hook) return;
878+
if (!hook) return;
719879
hook.addLocation(location || TracingHandler.findRegistrationLocation());
720880
hook.notifyDependencies();
721-
debouncedUpdateDebuggingViews();
881+
DebuggingCache.debouncedUpdateDebuggingViews();
722882
}
723883

724884
static globalUpdated(globalName, location) {
725885
const hook = SourceCodeHook.get(globalRef, globalName);
726-
if(!hook) return;
886+
if (!hook) return;
727887
hook.addLocation(location || TracingHandler.findRegistrationLocation());
728888
hook.notifyDependencies();
729-
debouncedUpdateDebuggingViews();
889+
DebuggingCache.debouncedUpdateDebuggingViews();
730890
}
731891

732892
static localUpdated(scope, varName, location) {
733893
const hook = SourceCodeHook.get(scope, varName);
734-
if(!hook) return;
894+
if (!hook) return;
735895
hook.addLocation(location || TracingHandler.findRegistrationLocation());
736896
hook.notifyDependencies();
737-
debouncedUpdateDebuggingViews();
897+
DebuggingCache.debouncedUpdateDebuggingViews();
738898
}
739899

740900
static async findRegistrationLocation() {
@@ -746,11 +906,11 @@ class TracingHandler {
746906
if (!frame.file.includes("active-expression")) {
747907
const loc = await frame.getSourceLoc();
748908
return {
749-
start: {line: loc.line, column: loc.column},
750-
end: {line: loc.line, column: loc.column},
751-
file: loc.source,
752-
}
753-
}
909+
start: { line: loc.line, column: loc.column },
910+
end: { line: loc.line, column: loc.column },
911+
file: loc.source
912+
};
913+
}
754914
}
755915
return undefined;
756916
}
@@ -902,63 +1062,6 @@ export function getGlobal(globalName) {
9021062
}
9031063
}
9041064

905-
const registeredDebuggingViews = [];
906-
907-
const debouncedUpdateDebuggingViews = _.debounce(updateDebggingViews, 100);
908-
909-
async function updateDebggingViews() {
910-
for(let i = 0; i < registeredDebuggingViews.length; i++) {
911-
if(!await registeredDebuggingViews[i]()) {
912-
registeredDebuggingViews.splice(i, 1);
913-
i--;
914-
}
915-
}
916-
}
917-
918-
export async function registerFileForAEDebugging(url, context, triplesCallback) {
919-
const callback = async () => {
920-
if(context && (!context.valid || context.valid())) {
921-
triplesCallback(await getDependencyTriplesForFile(url));
922-
return true;
923-
}
924-
return false;
925-
}
926-
registeredDebuggingViews.push(callback);
927-
928-
triplesCallback(await getDependencyTriplesForFile(url));
929-
}
930-
931-
export async function getDependencyTriplesForFile(url) {
932-
const result = [];
933-
for (const ae of DependenciesToAExprs.getAEsInFile(url)) {
934-
for (const dependency of DependenciesToAExprs.getDepsForAExpr(ae)) {
935-
for(const hook of HooksToDependencies.getHooksForDep(dependency)) {
936-
result.push({ hook, dependency, ae });
937-
}
938-
}
939-
}
940-
for (const hook of await HooksToDependencies.getHooksInFile(url)) {
941-
for (const dependency of HooksToDependencies.getDepsForHook(hook)) {
942-
for(const ae of DependenciesToAExprs.getAExprsForDep(dependency)) {
943-
const location = ae.meta().get("location").file;
944-
// if the AE is also in this file, we already covered it with the previous loop
945-
if (!location.includes(url)){
946-
result.push({ hook, dependency, ae });
947-
}
948-
}
949-
}
950-
}
951-
return result;
952-
}
953-
954-
export async function getHookTriplesForFile(url) {
955-
return HooksToDependencies.getHookTriplesForFile(url);
956-
}
957-
958-
export async function getAETriplesForFile(url) {
959-
return DependenciesToAExprs.getAETriplesForFile(url);
960-
}
961-
9621065
export function setGlobal(globalName, location) {
9631066
TracingHandler.globalUpdated(globalName, location);
9641067
}

0 commit comments

Comments
 (0)