Fix PatchTree performance issues (#755)

When building the tree, I've implemented a few improvements:

- We no longer traverse the full ancestry for every leaf node- we exit
early when we find a node that already exists
- We no longer search the entire tree to see if a node id exists before
creating one with that id, we just check if is in the map
This commit is contained in:
boatbomber
2023-07-31 22:26:16 -07:00
committed by GitHub
parent ecc31dea15
commit 34024d8524
2 changed files with 90 additions and 57 deletions

View File

@@ -22,6 +22,7 @@
* Added better support for `Font` properties ([#731]) * Added better support for `Font` properties ([#731])
* Add new plugin template to the `init` command ([#738]) * Add new plugin template to the `init` command ([#738])
* Added rich Source diffs in patch visualizer ([#748]) * Added rich Source diffs in patch visualizer ([#748])
* Fix PatchTree performance issues ([#755])
[#745]: https://github.com/rojo-rbx/rojo/pull/745 [#745]: https://github.com/rojo-rbx/rojo/pull/745
[#668]: https://github.com/rojo-rbx/rojo/pull/668 [#668]: https://github.com/rojo-rbx/rojo/pull/668
@@ -44,6 +45,7 @@
[#731]: https://github.com/rojo-rbx/rojo/pull/731 [#731]: https://github.com/rojo-rbx/rojo/pull/731
[#738]: https://github.com/rojo-rbx/rojo/pull/738 [#738]: https://github.com/rojo-rbx/rojo/pull/738
[#748]: https://github.com/rojo-rbx/rojo/pull/748 [#748]: https://github.com/rojo-rbx/rojo/pull/748
[#755]: https://github.com/rojo-rbx/rojo/pull/755
## [7.3.0] - April 22, 2023 ## [7.3.0] - April 22, 2023
* Added `$attributes` to project format. ([#574]) * Added `$attributes` to project format. ([#574])

View File

@@ -113,6 +113,10 @@ function Tree:getNode(id, searchNode)
return nil return nil
end end
function Tree:doesNodeExist(id)
return self.idToNode[id] ~= nil
end
-- Adds a node to the tree as a child of the node with id == parent -- Adds a node to the tree as a child of the node with id == parent
-- If parent is nil, it defaults to root -- If parent is nil, it defaults to root
-- props must contain id, and cannot contain children or parentId -- props must contain id, and cannot contain children or parentId
@@ -122,16 +126,16 @@ function Tree:addNode(parent, props)
parent = parent or "ROOT" parent = parent or "ROOT"
local node = self:getNode(props.id) if self:doesNodeExist(props.id) then
if node then
-- Update existing node -- Update existing node
local node = self:getNode(props.id)
for k, v in props do for k, v in props do
node[k] = v node[k] = v
end end
return node return node
end end
node = table.clone(props) local node = table.clone(props)
node.children = {} node.children = {}
node.parentId = parent node.parentId = parent
@@ -149,9 +153,10 @@ end
-- Given a list of ancestor ids in descending order, builds the nodes for them -- Given a list of ancestor ids in descending order, builds the nodes for them
-- using the patch and instanceMap info -- using the patch and instanceMap info
function Tree:buildAncestryNodes(ancestryIds: { string }, patch, instanceMap) function Tree:buildAncestryNodes(previousId: string?, ancestryIds: { string }, patch, instanceMap)
-- Build nodes for ancestry by going up the tree -- Build nodes for ancestry by going up the tree
local previousId = "ROOT" previousId = previousId or "ROOT"
for _, ancestorId in ancestryIds do for _, ancestorId in ancestryIds do
local value = instanceMap.fromIds[ancestorId] or patch.added[ancestorId] local value = instanceMap.fromIds[ancestorId] or patch.added[ancestorId]
if not value then if not value then
@@ -175,6 +180,8 @@ local PatchTree = {}
function PatchTree.build(patch, instanceMap, changeListHeaders) function PatchTree.build(patch, instanceMap, changeListHeaders)
local tree = Tree.new() local tree = Tree.new()
local knownAncestors = {}
for _, change in patch.updated do for _, change in patch.updated do
local instance = instanceMap.fromIds[change.id] local instance = instanceMap.fromIds[change.id]
if not instance then if not instance then
@@ -185,13 +192,21 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
local ancestryIds = {} local ancestryIds = {}
local parentObject = instance.Parent local parentObject = instance.Parent
local parentId = instanceMap.fromInstances[parentObject] local parentId = instanceMap.fromInstances[parentObject]
local previousId = nil
while parentObject do while parentObject do
if knownAncestors[parentId] then
-- We've already added this ancestor
previousId = parentId
break
end
table.insert(ancestryIds, 1, parentId) table.insert(ancestryIds, 1, parentId)
knownAncestors[parentId] = true
parentObject = parentObject.Parent parentObject = parentObject.Parent
parentId = instanceMap.fromInstances[parentObject] parentId = instanceMap.fromInstances[parentObject]
end end
tree:buildAncestryNodes(ancestryIds, patch, instanceMap) tree:buildAncestryNodes(previousId, ancestryIds, patch, instanceMap)
-- Gather detail text -- Gather detail text
local changeList, hint = nil, nil local changeList, hint = nil, nil
@@ -268,14 +283,22 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
local ancestryIds = {} local ancestryIds = {}
local parentObject = instance.Parent local parentObject = instance.Parent
local parentId = instanceMap.fromInstances[parentObject] or HttpService:GenerateGUID(false) local parentId = instanceMap.fromInstances[parentObject] or HttpService:GenerateGUID(false)
local previousId = nil
while parentObject do while parentObject do
if knownAncestors[parentId] then
-- We've already added this ancestor
previousId = parentId
break
end
instanceMap:insert(parentId, parentObject) -- This ensures we can find the parent later instanceMap:insert(parentId, parentObject) -- This ensures we can find the parent later
table.insert(ancestryIds, 1, parentId) table.insert(ancestryIds, 1, parentId)
knownAncestors[parentId] = true
parentObject = parentObject.Parent parentObject = parentObject.Parent
parentId = instanceMap.fromInstances[parentObject] or HttpService:GenerateGUID(false) parentId = instanceMap.fromInstances[parentObject] or HttpService:GenerateGUID(false)
end end
tree:buildAncestryNodes(ancestryIds, patch, instanceMap) tree:buildAncestryNodes(previousId, ancestryIds, patch, instanceMap)
-- Add this node to tree -- Add this node to tree
local nodeId = instanceMap.fromInstances[instance] or HttpService:GenerateGUID(false) local nodeId = instanceMap.fromInstances[instance] or HttpService:GenerateGUID(false)
@@ -295,8 +318,16 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
local parentId = change.Parent local parentId = change.Parent
local parentData = patch.added[parentId] local parentData = patch.added[parentId]
local parentObject = instanceMap.fromIds[parentId] local parentObject = instanceMap.fromIds[parentId]
local previousId = nil
while parentId do while parentId do
if knownAncestors[parentId] then
-- We've already added this ancestor
previousId = parentId
break
end
table.insert(ancestryIds, 1, parentId) table.insert(ancestryIds, 1, parentId)
knownAncestors[parentId] = true
parentId = nil parentId = nil
if parentData then if parentData then
@@ -312,7 +343,7 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
end end
end end
tree:buildAncestryNodes(ancestryIds, patch, instanceMap) tree:buildAncestryNodes(previousId, ancestryIds, patch, instanceMap)
-- Gather detail text -- Gather detail text
local changeList, hint = nil, nil local changeList, hint = nil, nil