sclass ConceptsShadowLogger { Concepts cc; L shadows; new HashSet changedIDs; bool fullChange; Writer writer; Lock lock = lock(); Lock flushLock = lock(); IVF1 changeHandler = change -> { lock lock; if (change cast ConceptChange) changedIDs.add(change.c.id); else if (change cast ConceptDelete) changedIDs.add(change.id); else if (change cast FullChange) fullChange = true; }; *() {} *(Concepts *cc) {} void install { makeShadows(); cc.onChange(changeHandler); } void uninstall { cc.removeChangeListener(changeHandler); } void makeShadows { shadows = allConceptShadows(cc); } void flush { Set changedIDs; { lock lock; if (fullChange) { changedIDs = allConceptIDs(cc); fullChange = false; } else { changedIDs = this.changedIDs; this.changedIDs = new HashSet; } } lock flushLock; // make new shadows (id, shadow) - shadow will be null when object was deleted LPair newShadows = lmap(sorted(changedIDs), id -> pair(id, conceptShadow(cc.getConcept(id)))); new L> diffs; new L updatedShadows; int i1 = 0, i2 = 0; L l1 = newShadows, l2 = shadows; while (i1 < l(l1) && i2 < l(l2)) { long id1, ConceptShadow s1 = unpair newShadows.get(i1); ConceptShadow s2 = shadows.get(i2); long id2 = s2.id(); if (id1 < id2) { diffs.add(new CreatedDeletedChanged.Created(s1)); updatedShadows.add(s1); ++i1; } else if (id1 > id2) { updatedShadows.add(s2); ++i2; } else { if (s1 == null) diffs.add(new CreatedDeletedChanged.Deleted(s2)); else { if (eq(s1, s2)) print("Unchanged concept: " + id1); else diffs.add(new CreatedDeletedChanged.Changed(s2, s1)); updatedShadows.add(s1); } ++i1; ++i2; } } while (i1 < l(l1)) { ConceptShadow s = l1.get(i1++); diffs.add(new CreatedDeletedChanged.Created(s)); updatedShadows.add(s); } updatedShadows.add(subList(l2, i2)); shadows = updatedShadows; pnl("DIFF", diffs); } }