Implement diff for SurfaceFlinger and WindowManager hierarchy
Test: N/A Change-Id: I3dea4f29de74a8754cf3164d41c7eb3d76c239a8
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jszip": "^3.5.0",
|
"jszip": "^3.5.0",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"vue": "^2.3.3",
|
"vue": "^2.3.3",
|
||||||
"vue-material": "^1.0.0-beta-11",
|
"vue-material": "^1.0.0-beta-11",
|
||||||
"vuex": "^3.4.0"
|
"vuex": "^3.4.0"
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import Timeline from './Timeline.vue'
|
|||||||
import Rects from './Rects.vue'
|
import Rects from './Rects.vue'
|
||||||
|
|
||||||
import { ObjectTransformer } from './transform.js'
|
import { ObjectTransformer } from './transform.js'
|
||||||
|
import { DiffGenerator, defaultModifiedCheck } from './utils/diff.js'
|
||||||
import { format_transform_type, is_simple_transform } from './matrix_utils.js'
|
import { format_transform_type, is_simple_transform } from './matrix_utils.js'
|
||||||
import { DATA_TYPES } from './decode.js'
|
import { DATA_TYPES } from './decode.js'
|
||||||
import { stableIdCompatibilityFixup } from './utils/utils.js'
|
import { stableIdCompatibilityFixup } from './utils/utils.js'
|
||||||
@@ -173,6 +174,28 @@ export default {
|
|||||||
},
|
},
|
||||||
setData(item) {
|
setData(item) {
|
||||||
this.tree = item;
|
this.tree = item;
|
||||||
|
|
||||||
|
// TODO: Add flag to toggle diffs or not
|
||||||
|
// TODO: Disable if proto are not the latest
|
||||||
|
if (true) {
|
||||||
|
// Required pre-processing to match algo
|
||||||
|
// TODO: Clean this up somehow
|
||||||
|
if (this.file.type == DATA_TYPES.SURFACE_FLINGER) {
|
||||||
|
item.obj.id = -1; // TODO: Make sure this ID can never be used by other objects
|
||||||
|
item.children[0].obj.id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tree = new DiffGenerator(item)
|
||||||
|
.compareWith(this.getDataWithOffset(-1))
|
||||||
|
.withUniqueNodeId(node => {
|
||||||
|
return node.stableId;
|
||||||
|
})
|
||||||
|
.withModifiedCheck(defaultModifiedCheck)
|
||||||
|
.generateDiffTree();
|
||||||
|
} else {
|
||||||
|
this.tree = item;
|
||||||
|
}
|
||||||
|
|
||||||
this.rects = [...item.rects].reverse();
|
this.rects = [...item.rects].reverse();
|
||||||
this.bounds = item.bounds;
|
this.bounds = item.bounds;
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ export default {
|
|||||||
[DiffType.ADDED]: "+",
|
[DiffType.ADDED]: "+",
|
||||||
[DiffType.DELETED]: "-",
|
[DiffType.DELETED]: "-",
|
||||||
[DiffType.MODIFIED]: ".",
|
[DiffType.MODIFIED]: ".",
|
||||||
|
[DiffType.MOVED]: ".",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -350,7 +351,7 @@ export default {
|
|||||||
background: chartreuse;
|
background: chartreuse;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-view .node:not(.selected).removed {
|
.tree-view .node:not(.selected).deleted {
|
||||||
background: coral;
|
background: coral;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,6 +359,11 @@ export default {
|
|||||||
background: cyan;
|
background: cyan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tree-view .node:not(.selected).addedMove,
|
||||||
|
.tree-view .node:not(.selected).deletedMove {
|
||||||
|
background: slateblue;
|
||||||
|
}
|
||||||
|
|
||||||
.children {
|
.children {
|
||||||
/* Aligns border with collapse arrows */
|
/* Aligns border with collapse arrows */
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
|
|
||||||
const DiffType = Object.freeze({
|
const DiffType = Object.freeze({
|
||||||
NONE: 'none',
|
NONE: 'none',
|
||||||
ADDED: 'added',
|
ADDED: 'added',
|
||||||
@@ -7,4 +9,227 @@ const DiffType = Object.freeze({
|
|||||||
MODIFIED: 'modified',
|
MODIFIED: 'modified',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { DiffType };
|
function defaultModifiedCheck(newNode, oldNode) {
|
||||||
|
if (!newNode && !oldNode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((newNode && !oldNode) || (!newNode && oldNode)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(newNode.obj) !== JSON.stringify(oldNode.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrimitive(test) {
|
||||||
|
return test !== Object(test);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DiffGenerator {
|
||||||
|
constructor(tree) {
|
||||||
|
this.tree = tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
compareWith(tree) {
|
||||||
|
this.diffWithTree = tree;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withUniqueNodeId(getNodeId) {
|
||||||
|
this.getNodeId = node => {
|
||||||
|
const id = getNodeId(node);
|
||||||
|
if (id === null || id === undefined) {
|
||||||
|
throw new Error("Node ID can't be null or undefined");
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withModifiedCheck(isModified) {
|
||||||
|
this.isModified = isModified;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateDiffTree() {
|
||||||
|
this.newMapping = this._generateIdToNodeMapping(this.tree);
|
||||||
|
this.oldMapping = this.diffWithTree ? this._generateIdToNodeMapping(this.diffWithTree) : {};
|
||||||
|
|
||||||
|
const diffTrees =
|
||||||
|
this._generateDiffTree(this.tree, this.diffWithTree, [], []);
|
||||||
|
|
||||||
|
let diffTree;
|
||||||
|
if (diffTrees.length > 1) {
|
||||||
|
diffTree = {
|
||||||
|
kind: "",
|
||||||
|
name: "DiffTree",
|
||||||
|
children: diffTrees,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
diffTree = diffTrees[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.freeze(diffTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
_generateIdToNodeMapping(node, acc) {
|
||||||
|
acc = acc || {};
|
||||||
|
|
||||||
|
const nodeId = this.getNodeId(node);
|
||||||
|
|
||||||
|
if (acc[nodeId]) {
|
||||||
|
throw new Error(`Duplicate node id '${nodeId}' detected...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[this.getNodeId(node)] = node;
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
for (const child of node.children) {
|
||||||
|
this._generateIdToNodeMapping(child, acc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
_objIsEmpty(obj) {
|
||||||
|
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cloneNodeWithoutChildren(node) {
|
||||||
|
const clone = {};
|
||||||
|
|
||||||
|
for (const key in node) {
|
||||||
|
if (key !== 'children') {
|
||||||
|
const val = node[key];
|
||||||
|
clone[key] = isPrimitive(val) ? val : cloneDeep(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
_generateDiffTree(newTree, oldTree, newTreeSiblings, oldTreeSiblings) {
|
||||||
|
const diffTrees = [];
|
||||||
|
|
||||||
|
// NOTE: A null ID represents a non existent node.
|
||||||
|
const newId = newTree ? this.getNodeId(newTree) : null;
|
||||||
|
const oldId = oldTree ? this.getNodeId(oldTree) : null;
|
||||||
|
|
||||||
|
const newTreeSiblingIds = newTreeSiblings.map(this.getNodeId);
|
||||||
|
const oldTreeSiblingIds = oldTreeSiblings.map(this.getNodeId);
|
||||||
|
|
||||||
|
if (newTree) {
|
||||||
|
// Deep clone newTree omitting children field
|
||||||
|
// Clone is required because trees are frozen objects — we can't
|
||||||
|
// modify the original tree object. Also means there is no side effect.
|
||||||
|
const diffTree = this._cloneNodeWithoutChildren(newTree);
|
||||||
|
|
||||||
|
// Default to no changes
|
||||||
|
diffTree.diff = { type: DiffType.NONE };
|
||||||
|
|
||||||
|
if (newId !== oldId) {
|
||||||
|
// A move, addition, or deletion has occurred
|
||||||
|
let nextOldTree;
|
||||||
|
|
||||||
|
// Check if newTree has been added or moved
|
||||||
|
if (!oldTreeSiblingIds.includes(newId)) {
|
||||||
|
if (this.oldMapping[newId]) {
|
||||||
|
// Objected existed in old tree, the counter party
|
||||||
|
// DELETED_MOVE will be/has been flagged and added to the
|
||||||
|
// diffTree when visiting it in the oldTree.
|
||||||
|
diffTree.diff = { type: DiffType.ADDED_MOVE };
|
||||||
|
|
||||||
|
// TODO: Figure out if oldTree is ever visited now...
|
||||||
|
|
||||||
|
// Switch out oldTree for new one to compare against
|
||||||
|
nextOldTree = this.oldMapping[newId];
|
||||||
|
} else {
|
||||||
|
diffTree.diff = { type: DiffType.ADDED };
|
||||||
|
|
||||||
|
// TODO: Figure out if oldTree is ever visited now...
|
||||||
|
|
||||||
|
// Stop comparing against oldTree
|
||||||
|
nextOldTree = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if oldTree has been deleted of moved
|
||||||
|
if (oldTree && !newTreeSiblingIds.includes(oldId)) {
|
||||||
|
const deletedTreeDiff = this._cloneNodeWithoutChildren(oldTree);
|
||||||
|
|
||||||
|
if (this.newMapping[oldId]) {
|
||||||
|
deletedTreeDiff.diff = { type: DiffType.DELETED_MOVE };
|
||||||
|
|
||||||
|
// Stop comparing against oldTree, will be/has been
|
||||||
|
// visited when object is seen in newTree
|
||||||
|
nextOldTree = null;
|
||||||
|
} else {
|
||||||
|
deletedTreeDiff.diff = { type: DiffType.DELETED };
|
||||||
|
|
||||||
|
// Visit all children to check if they have been deleted or moved
|
||||||
|
deletedTreeDiff.children = this._visitChildren(null, oldTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
diffTrees.push(deletedTreeDiff);
|
||||||
|
}
|
||||||
|
|
||||||
|
oldTree = nextOldTree;
|
||||||
|
} else {
|
||||||
|
// TODO: Always have modified check and add modified tags on top of others
|
||||||
|
// NOTE: Doesn't check for moved and modified at the same time
|
||||||
|
if (this.isModified && this.isModified(newTree, oldTree)) {
|
||||||
|
diffTree.diff = { type: DiffType.MODIFIED };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diffTree.children = this._visitChildren(newTree, oldTree);
|
||||||
|
diffTrees.push(diffTree);
|
||||||
|
} else if (oldTree) {
|
||||||
|
if (!newTreeSiblingIds.includes(oldId)) {
|
||||||
|
// Deep clone oldTree omitting children field
|
||||||
|
const diffTree = this._cloneNodeWithoutChildren(oldTree);
|
||||||
|
|
||||||
|
// newTree doesn't exists, oldTree has either been moved or deleted.
|
||||||
|
if (this.newMapping[oldId]) {
|
||||||
|
diffTree.diff = { type: DiffType.DELETED_MOVE };
|
||||||
|
} else {
|
||||||
|
diffTree.diff = { type: DiffType.DELETED };
|
||||||
|
}
|
||||||
|
|
||||||
|
diffTree.children = this._visitChildren(null, oldTree);
|
||||||
|
diffTrees.push(diffTree);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error("Both newTree and oldTree are undefined...");
|
||||||
|
}
|
||||||
|
|
||||||
|
return diffTrees;
|
||||||
|
}
|
||||||
|
|
||||||
|
_visitChildren(newTree, oldTree) {
|
||||||
|
// Recursively traverse all children of new and old tree.
|
||||||
|
const diffChildren = [];
|
||||||
|
|
||||||
|
// TODO: Try replacing this with some sort of zipWith.
|
||||||
|
const numOfChildren = Math.max(
|
||||||
|
newTree?.children.length ?? 0, oldTree?.children.length ?? 0);
|
||||||
|
for (let i = 0; i < numOfChildren; i++) {
|
||||||
|
const newChild = i < newTree?.children.length ?
|
||||||
|
newTree.children[i] : null;
|
||||||
|
|
||||||
|
const oldChild = i < oldTree?.children.length ?
|
||||||
|
oldTree.children[i] : null;
|
||||||
|
|
||||||
|
const childDiffTrees = this._generateDiffTree(
|
||||||
|
newChild, oldChild,
|
||||||
|
newTree?.children ?? [], oldTree?.children ?? []
|
||||||
|
);
|
||||||
|
diffChildren.push(...childDiffTrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
return diffChildren;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DiffGenerator, DiffType, defaultModifiedCheck }
|
||||||
|
|||||||
@@ -3502,6 +3502,11 @@ lodash.camelcase@^3.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash._createcompounder "^3.0.0"
|
lodash._createcompounder "^3.0.0"
|
||||||
|
|
||||||
|
lodash.clonedeep@^4.5.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||||
|
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
||||||
|
|
||||||
lodash.deburr@^3.0.0:
|
lodash.deburr@^3.0.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-3.2.0.tgz#6da8f54334a366a7cf4c4c76ef8d80aa1b365ed5"
|
resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-3.2.0.tgz#6da8f54334a366a7cf4c4c76ef8d80aa1b365ed5"
|
||||||
|
|||||||
Reference in New Issue
Block a user