2026-07-25 03:51:57 +00:00
|
|
|
#include "vde/brep/incremental_update.h"
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <queue>
|
|
|
|
|
#include <set>
|
|
|
|
|
|
|
|
|
|
namespace vde::brep {
|
|
|
|
|
|
|
|
|
|
void IncrementalUpdateEngine::register_node(int node_id, const std::vector<int>& deps) {
|
|
|
|
|
if (caches_.find(node_id) == caches_.end()) {
|
|
|
|
|
caches_[node_id] = NodeCache{};
|
|
|
|
|
}
|
|
|
|
|
deps_[node_id] = deps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IncrementalUpdateEngine::mark_dirty(int node_id) {
|
|
|
|
|
if (caches_.find(node_id) == caches_.end()) return;
|
|
|
|
|
|
|
|
|
|
// BFS propagation: mark node and all dependents
|
|
|
|
|
std::queue<int> q;
|
|
|
|
|
q.push(node_id);
|
|
|
|
|
|
|
|
|
|
while (!q.empty()) {
|
|
|
|
|
int nid = q.front(); q.pop();
|
|
|
|
|
if (caches_[nid].dirty) continue; // already marked
|
|
|
|
|
|
|
|
|
|
caches_[nid].dirty = true;
|
|
|
|
|
dirty_set_.insert(nid);
|
|
|
|
|
|
|
|
|
|
// Find all nodes that depend on nid
|
|
|
|
|
for (auto& [other, ndeps] : deps_) {
|
|
|
|
|
if (caches_[other].dirty) continue;
|
|
|
|
|
if (std::find(ndeps.begin(), ndeps.end(), nid) != ndeps.end()) {
|
|
|
|
|
q.push(other);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NodeCache* IncrementalUpdateEngine::get_cache(int node_id) const {
|
|
|
|
|
auto it = caches_.find(node_id);
|
|
|
|
|
if (it != caches_.end() && !it->second.dirty) {
|
|
|
|
|
const_cast<IncrementalUpdateEngine*>(this)->cache_hits_++;
|
|
|
|
|
return &it->second;
|
|
|
|
|
}
|
|
|
|
|
const_cast<IncrementalUpdateEngine*>(this)->cache_misses_++;
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IncrementalUpdateEngine::invalidate_cache(int node_id) {
|
|
|
|
|
auto it = caches_.find(node_id);
|
2026-07-26 16:41:45 +08:00
|
|
|
if (it == caches_.end()) return;
|
|
|
|
|
|
|
|
|
|
// Clear cached data but keep the entry
|
|
|
|
|
it->second.model.reset();
|
|
|
|
|
it->second.mesh.reset();
|
|
|
|
|
it->second.bounds.reset();
|
|
|
|
|
|
|
|
|
|
// Propagate dirty flag to this node and all downstream
|
|
|
|
|
mark_dirty(node_id);
|
2026-07-25 03:51:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IncrementalUpdateEngine::clear_all() {
|
|
|
|
|
dirty_set_.clear();
|
|
|
|
|
for (auto& [nid, cache] : caches_) {
|
|
|
|
|
cache = NodeCache{};
|
|
|
|
|
cache.dirty = true;
|
|
|
|
|
dirty_set_.insert(nid);
|
|
|
|
|
}
|
|
|
|
|
cache_hits_ = 0;
|
|
|
|
|
cache_misses_ = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int IncrementalUpdateEngine::dirty_count() const {
|
|
|
|
|
return static_cast<int>(dirty_set_.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double IncrementalUpdateEngine::hit_rate() const {
|
|
|
|
|
int total = cache_hits_ + cache_misses_;
|
|
|
|
|
return total > 0 ? static_cast<double>(cache_hits_) / total : 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<int> IncrementalUpdateEngine::topological_sort_dirty() {
|
|
|
|
|
std::vector<int> result;
|
|
|
|
|
std::unordered_map<int, int> in_degree;
|
|
|
|
|
|
|
|
|
|
for (int nid : dirty_set_) {
|
|
|
|
|
in_degree[nid] = 0;
|
|
|
|
|
}
|
|
|
|
|
for (int nid : dirty_set_) {
|
|
|
|
|
for (int dep : deps_[nid]) {
|
|
|
|
|
if (dirty_set_.count(dep)) {
|
|
|
|
|
in_degree[nid]++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::queue<int> q;
|
|
|
|
|
for (auto& [nid, deg] : in_degree) {
|
|
|
|
|
if (deg == 0) q.push(nid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (!q.empty()) {
|
|
|
|
|
int nid = q.front(); q.pop();
|
|
|
|
|
result.push_back(nid);
|
|
|
|
|
|
|
|
|
|
for (auto& [other, other_deps] : deps_) {
|
|
|
|
|
if (!dirty_set_.count(other)) continue;
|
|
|
|
|
if (std::find(other_deps.begin(), other_deps.end(), nid) != other_deps.end()) {
|
|
|
|
|
in_degree[other]--;
|
|
|
|
|
if (in_degree[other] == 0) q.push(other);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int IncrementalUpdateEngine::incremental_rebuild(
|
|
|
|
|
FeatureHistory&, RebuildCallback callback)
|
|
|
|
|
{
|
|
|
|
|
int rebuilt = 0;
|
|
|
|
|
auto sorted = topological_sort_dirty();
|
|
|
|
|
|
|
|
|
|
for (int nid : sorted) {
|
|
|
|
|
auto& cache = caches_[nid];
|
|
|
|
|
try {
|
|
|
|
|
if (callback) {
|
|
|
|
|
BrepModel dummy;
|
|
|
|
|
callback(nid, dummy);
|
|
|
|
|
}
|
|
|
|
|
cache.dirty = false;
|
|
|
|
|
dirty_set_.erase(nid);
|
|
|
|
|
rebuilt++;
|
|
|
|
|
} catch (...) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rebuilt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int IncrementalUpdateEngine::full_rebuild(
|
|
|
|
|
FeatureHistory& history, RebuildCallback callback)
|
|
|
|
|
{
|
|
|
|
|
clear_all();
|
|
|
|
|
return incremental_rebuild(history, callback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace vde::brep
|