diff options
| author | ruki <[email protected]> | 2025-03-21 23:05:26 +0800 |
|---|---|---|
| committer | ruki <[email protected]> | 2025-04-08 15:31:54 +0800 |
| commit | 43ea0c1fb1d9954e065f3dfdb7bd93493d5d9e38 (patch) | |
| tree | 2361d9c1779e3de3a1ea6b9eb44f115b25e7c1ef /xmake/core/base/graph.lua | |
| parent | 741da62196bcb64c88e386f1a769462211c63b4e (diff) | |
add partial topo test
Diffstat (limited to 'xmake/core/base/graph.lua')
| -rw-r--r-- | xmake/core/base/graph.lua | 125 |
1 files changed, 50 insertions, 75 deletions
diff --git a/xmake/core/base/graph.lua b/xmake/core/base/graph.lua index 448eebb88..496bffeba 100644 --- a/xmake/core/base/graph.lua +++ b/xmake/core/base/graph.lua @@ -122,15 +122,15 @@ function graph:remove_vertex(v) end -- reset partial topological sort state since graph structure changed - self:partial_topo_sort_reset() + self._partial_topo_dirty = true end end -- check if there's a cycle in the remaining unprocessed nodes function graph:_check_cycle_in_remaining() -- if all remaining nodes have in-degree > 0, we have a cycle - if self._topo_remaining_count > 0 and self._topo_remaining_count == self._topo_non_zero_indegree_count then - self._topo_has_cycle = true + if self._partial_topo_remaining_count > 0 and self._partial_topo_remaining_count == self._partial_topo_non_zero_indegree_count then + self._partial_topo_has_cycle = true return true end return false @@ -138,13 +138,14 @@ end -- reset partial topological sort state function graph:partial_topo_sort_reset() - self._topo_in_progress = false - self._topo_in_degree = nil - self._topo_queue = nil - self._topo_processed = nil - self._topo_has_cycle = nil - self._topo_remaining_count = nil - self._topo_non_zero_indegree_count = nil + self._partial_topo_in_progress = false + self._partial_topo_in_degree = nil + self._partial_topo_queue = nil + self._partial_topo_processed = nil + self._partial_topo_has_cycle = nil + self._partial_topo_remaining_count = nil + self._partial_topo_non_zero_indegree_count = nil + self._partial_topo_dirty = false end -- get next batch of nodes in topological order with limit @@ -168,19 +169,23 @@ function graph:partial_topo_sort_next(limit) return {}, false end + if self._partial_topo_dirty then + self:partial_topo_sort_reset() + end + limit = limit or math.huge -- check if we already detected a cycle - if self._topo_has_cycle then + if self._partial_topo_has_cycle then return {}, true end -- initialize topological sort state if not already in progress - if not self._topo_in_progress then + if not self._partial_topo_in_progress then -- calculate in-degree for each vertex - self._topo_in_degree = {} + self._partial_topo_in_degree = {} for _, v in ipairs(self:vertices()) do - self._topo_in_degree[v] = 0 + self._partial_topo_in_degree[v] = 0 end -- count incoming edges for each vertex @@ -190,56 +195,56 @@ function graph:partial_topo_sort_next(limit) for _, e in ipairs(edges) do if e:from() == v then local w = e:to() - self._topo_in_degree[w] = (self._topo_in_degree[w] or 0) + 1 + self._partial_topo_in_degree[w] = (self._partial_topo_in_degree[w] or 0) + 1 end end end end -- initialize queue with vertices that have no incoming edges - self._topo_queue = queue.new() + self._partial_topo_queue = queue.new() for _, v in ipairs(self:vertices()) do - if self._topo_in_degree[v] == 0 then - self._topo_queue:push(v) + if self._partial_topo_in_degree[v] == 0 then + self._partial_topo_queue:push(v) end end -- track processed vertices - self._topo_processed = hashset.new() - self._topo_in_progress = true + self._partial_topo_processed = hashset.new() + self._partial_topo_in_progress = true -- track counts for efficient cycle detection - self._topo_remaining_count = #self:vertices() - self._topo_non_zero_indegree_count = self._topo_remaining_count - self._topo_queue:size() + self._partial_topo_remaining_count = #self:vertices() + self._partial_topo_non_zero_indegree_count = self._partial_topo_remaining_count - self._partial_topo_queue:size() -- quick cycle detection: if no nodes have zero in-degree, we have a cycle - if self._topo_queue:empty() and self._topo_remaining_count > 0 then - self._topo_has_cycle = true + if self._partial_topo_queue:empty() and self._partial_topo_remaining_count > 0 then + self._partial_topo_has_cycle = true return {}, true end end -- return empty batch if queue is empty (all processed or cycle detected) - if self._topo_queue:empty() then + if self._partial_topo_queue:empty() then -- check if all vertices were processed - local processed_count = self._topo_processed:size() - self._topo_has_cycle = processed_count ~= #self:vertices() + local processed_count = self._partial_topo_processed:size() + self._partial_topo_has_cycle = processed_count ~= #self:vertices() -- if this is the first call and we detect a cycle, mark as complete if processed_count == 0 then - self._topo_in_progress = false + self._partial_topo_in_progress = false end - return {}, self._topo_has_cycle + return {}, self._partial_topo_has_cycle end -- collect up to 'limit' nodes with zero in-degree local batch = {} - while not self._topo_queue:empty() and #batch < limit do - local v = self._topo_queue:pop() + while not self._partial_topo_queue:empty() and #batch < limit do + local v = self._partial_topo_queue:pop() table.insert(batch, v) - self._topo_processed:insert(v) - self._topo_remaining_count = self._topo_remaining_count - 1 + self._partial_topo_processed:insert(v) + self._partial_topo_remaining_count = self._partial_topo_remaining_count - 1 end -- update in-degrees based on the nodes in this batch @@ -249,15 +254,15 @@ function graph:partial_topo_sort_next(limit) for _, e in ipairs(edges) do if e:from() == v then local w = e:to() - self._topo_in_degree[w] = self._topo_in_degree[w] - 1 + self._partial_topo_in_degree[w] = self._partial_topo_in_degree[w] - 1 -- update non-zero in-degree count - if self._topo_in_degree[w] == 0 then - self._topo_non_zero_indegree_count = self._topo_non_zero_indegree_count - 1 + if self._partial_topo_in_degree[w] == 0 then + self._partial_topo_non_zero_indegree_count = self._partial_topo_non_zero_indegree_count - 1 -- if in-degree becomes zero, add to queue for next batch - if not self._topo_processed:has(w) then - self._topo_queue:push(w) + if not self._partial_topo_processed:has(w) then + self._partial_topo_queue:push(w) end end end @@ -271,17 +276,17 @@ function graph:partial_topo_sort_next(limit) end -- if queue is now empty and all vertices processed, reset state - if self._topo_queue:empty() then - local processed_count = self._topo_processed:size() + if self._partial_topo_queue:empty() then + local processed_count = self._partial_topo_processed:size() if processed_count == #self:vertices() then - self._topo_in_progress = false + self._partial_topo_in_progress = false else -- if queue is empty but we still have unprocessed nodes, we have a cycle - self._topo_has_cycle = true + self._partial_topo_has_cycle = true end end - return batch, self._topo_has_cycle + return batch, self._partial_topo_has_cycle end -- topological sort, use kahn's algorithm @@ -292,34 +297,6 @@ end -- add_edge(b, c) -- b depend on c -- -- it will return {c, b, a} ---[[ -function graph:topo_sort() - if not self:is_directed() then - return - end - - -- reset partial sort state to ensure we start fresh - self:partial_topo_sort_reset() - - local order_vertices = {} - local batch_size = math.huge -- no limit, get all at once - - -- get all nodes in one go - local batch, has_cycle = self:partial_topo_sort_next(batch_size) - while #batch > 0 do - for _, v in ipairs(batch) do - table.insert(order_vertices, v) - end - batch, has_cycle = self:partial_topo_sort_next(batch_size) - - -- quick exit if cycle is detected - if has_cycle then - break - end - end - - return order_vertices, has_cycle -end]] function graph:topo_sort() if not self:is_directed() then return @@ -352,10 +329,8 @@ function graph:topo_sort() end end - -- result list for topologically sorted vertices - local order_vertices = {} - -- process queue + local order_vertices = {} while not queue:empty() do -- remove a vertex with no incoming edges local v = queue:pop() @@ -457,7 +432,7 @@ function graph:add_edge(from, to) table.insert(self._edges, e) -- reset partial topological sort state since graph structure changed - self:partial_topo_sort_reset() + self._partial_topo_dirty = true end -- has the given edge? |
