2026-07-25 03:51:57 +00:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
#include "vde/brep/incremental_update.h"
|
|
|
|
|
#include "vde/brep/modeling.h"
|
2026-07-26 16:41:45 +08:00
|
|
|
#include "vde/brep/feature_tree.h"
|
2026-07-25 03:51:57 +00:00
|
|
|
|
|
|
|
|
using namespace vde::brep;
|
|
|
|
|
using namespace vde::core;
|
|
|
|
|
|
2026-07-26 16:41:45 +08:00
|
|
|
// Helper: create a FeatureHistory with an initial BrepModel
|
|
|
|
|
static FeatureHistory make_history() {
|
|
|
|
|
FeatureHistory history;
|
|
|
|
|
FeatureParams params;
|
|
|
|
|
params.name = "box";
|
|
|
|
|
params.values = {1.0, 1.0, 1.0};
|
|
|
|
|
history.apply(make_box(1, 1, 1), FeatureType::PrimitiveBox, params);
|
|
|
|
|
return history;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-25 03:51:57 +00:00
|
|
|
TEST(IncrementalUpdateTest, RegisterNode) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
EXPECT_EQ(engine.total_nodes(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(IncrementalUpdateTest, RegisterWithDeps) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
engine.register_node(2, {1});
|
|
|
|
|
engine.register_node(3, {1, 2});
|
|
|
|
|
EXPECT_EQ(engine.total_nodes(), 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(IncrementalUpdateTest, MarkDirtyPropagates) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
engine.register_node(2, {1});
|
|
|
|
|
engine.register_node(3, {2});
|
|
|
|
|
engine.mark_dirty(1);
|
|
|
|
|
EXPECT_EQ(engine.dirty_count(), 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(IncrementalUpdateTest, MarkDirtyNoUpstream) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
engine.register_node(2, {1});
|
|
|
|
|
engine.mark_dirty(2);
|
|
|
|
|
EXPECT_EQ(engine.dirty_count(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(IncrementalUpdateTest, CacheMissOnDirty) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
engine.mark_dirty(1);
|
|
|
|
|
EXPECT_TRUE(engine.get_cache(1) == nullptr);
|
|
|
|
|
EXPECT_EQ(engine.cache_misses(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(IncrementalUpdateTest, RebuildClearsDirty) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
engine.mark_dirty(1);
|
|
|
|
|
EXPECT_EQ(engine.dirty_count(), 1);
|
2026-07-26 16:41:45 +08:00
|
|
|
|
|
|
|
|
auto history = make_history();
|
2026-07-25 03:51:57 +00:00
|
|
|
engine.incremental_rebuild(history);
|
|
|
|
|
EXPECT_EQ(engine.dirty_count(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(IncrementalUpdateTest, RebuildWithCallback) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
engine.mark_dirty(1);
|
2026-07-26 16:41:45 +08:00
|
|
|
|
2026-07-25 03:51:57 +00:00
|
|
|
int cb = 0;
|
2026-07-26 16:41:45 +08:00
|
|
|
auto history = make_history();
|
2026-07-25 03:51:57 +00:00
|
|
|
engine.incremental_rebuild(history, [&](int nid, const BrepModel&) { cb++; });
|
|
|
|
|
EXPECT_EQ(cb, 1);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-26 16:41:45 +08:00
|
|
|
TEST(IncrementalUpdateTest, RebuildClearsCacheDirty) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
engine.mark_dirty(1);
|
|
|
|
|
|
|
|
|
|
auto history = make_history();
|
|
|
|
|
engine.incremental_rebuild(history);
|
|
|
|
|
|
|
|
|
|
const auto* cache = engine.get_cache(1);
|
|
|
|
|
ASSERT_TRUE(cache != nullptr);
|
|
|
|
|
EXPECT_FALSE(cache->dirty);
|
|
|
|
|
EXPECT_EQ(engine.cache_hits(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-25 03:51:57 +00:00
|
|
|
TEST(IncrementalUpdateTest, ChainDependencyOrder) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
engine.register_node(2, {1});
|
|
|
|
|
engine.mark_dirty(1);
|
2026-07-26 16:41:45 +08:00
|
|
|
|
2026-07-25 03:51:57 +00:00
|
|
|
std::vector<int> order;
|
2026-07-26 16:41:45 +08:00
|
|
|
auto history = make_history();
|
2026-07-25 03:51:57 +00:00
|
|
|
engine.incremental_rebuild(history, [&](int nid, const BrepModel&) { order.push_back(nid); });
|
|
|
|
|
ASSERT_GE(order.size(), 2u);
|
2026-07-26 16:41:45 +08:00
|
|
|
// Node 1 must be rebuilt before node 2 (upstream before downstream)
|
|
|
|
|
EXPECT_LT(std::find(order.begin(), order.end(), 1) - order.begin(),
|
|
|
|
|
std::find(order.begin(), order.end(), 2) - order.begin());
|
2026-07-25 03:51:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(IncrementalUpdateTest, IncrementalVsFull) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
for (int i = 1; i <= 10; ++i) {
|
|
|
|
|
std::vector<int> deps;
|
|
|
|
|
if (i > 1) deps.push_back(i - 1);
|
|
|
|
|
engine.register_node(i, deps);
|
|
|
|
|
}
|
2026-07-26 16:41:45 +08:00
|
|
|
|
|
|
|
|
auto history = make_history();
|
|
|
|
|
|
|
|
|
|
// Full rebuild (clear_all then rebuild)
|
2026-07-25 03:51:57 +00:00
|
|
|
engine.clear_all();
|
|
|
|
|
int fc = engine.incremental_rebuild(history);
|
|
|
|
|
EXPECT_EQ(fc, 10);
|
2026-07-26 16:41:45 +08:00
|
|
|
|
|
|
|
|
// Mark only node 5 dirty — should rebuild fewer nodes
|
2026-07-25 03:51:57 +00:00
|
|
|
engine.clear_all();
|
|
|
|
|
engine.incremental_rebuild(history);
|
|
|
|
|
engine.mark_dirty(5);
|
|
|
|
|
int ic = engine.incremental_rebuild(history);
|
|
|
|
|
EXPECT_LT(ic, fc);
|
|
|
|
|
EXPECT_GT(ic, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-26 16:41:45 +08:00
|
|
|
TEST(IncrementalUpdateTest, InvalidateCachePropagates) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
engine.register_node(2, {1});
|
|
|
|
|
engine.register_node(3, {2});
|
|
|
|
|
|
|
|
|
|
auto history = make_history();
|
|
|
|
|
engine.incremental_rebuild(history); // build all
|
|
|
|
|
EXPECT_EQ(engine.dirty_count(), 0);
|
|
|
|
|
|
|
|
|
|
engine.invalidate_cache(1);
|
|
|
|
|
// Node 1 and its dependents (2, 3) should all be dirty
|
|
|
|
|
EXPECT_EQ(engine.dirty_count(), 3);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-25 03:51:57 +00:00
|
|
|
TEST(IncrementalUpdateTest, HitRate) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
2026-07-26 16:41:45 +08:00
|
|
|
// No cache access yet → 0 hit rate
|
|
|
|
|
EXPECT_DOUBLE_EQ(engine.hit_rate(), 0.0);
|
|
|
|
|
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
// get_cache on dirty node → miss
|
|
|
|
|
engine.mark_dirty(1);
|
|
|
|
|
engine.get_cache(1);
|
|
|
|
|
EXPECT_DOUBLE_EQ(engine.hit_rate(), 0.0);
|
|
|
|
|
|
|
|
|
|
// After rebuild, get_cache should hit
|
|
|
|
|
auto history = make_history();
|
|
|
|
|
engine.incremental_rebuild(history);
|
|
|
|
|
engine.get_cache(1);
|
|
|
|
|
EXPECT_GT(engine.hit_rate(), 0.0);
|
2026-07-25 03:51:57 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-26 16:41:45 +08:00
|
|
|
TEST(IncrementalUpdateTest, ClearAllMarksAllDirty) {
|
2026-07-25 03:51:57 +00:00
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
engine.register_node(2);
|
2026-07-26 16:41:45 +08:00
|
|
|
|
|
|
|
|
auto history = make_history();
|
|
|
|
|
engine.incremental_rebuild(history);
|
|
|
|
|
EXPECT_EQ(engine.dirty_count(), 0);
|
|
|
|
|
|
2026-07-25 03:51:57 +00:00
|
|
|
engine.clear_all();
|
|
|
|
|
EXPECT_EQ(engine.dirty_count(), 2);
|
|
|
|
|
}
|
2026-07-26 16:41:45 +08:00
|
|
|
|
|
|
|
|
TEST(IncrementalUpdateTest, RebuildWithoutEvaluator) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1); // no evaluator registered (just topology)
|
|
|
|
|
engine.mark_dirty(1);
|
|
|
|
|
|
|
|
|
|
int cb = 0;
|
|
|
|
|
auto history = make_history();
|
|
|
|
|
engine.incremental_rebuild(history, [&](int, const BrepModel&) { cb++; });
|
|
|
|
|
EXPECT_EQ(cb, 1); // callback still fires
|
|
|
|
|
EXPECT_EQ(engine.dirty_count(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(IncrementalUpdateTest, FullRebuild) {
|
|
|
|
|
IncrementalUpdateEngine engine;
|
|
|
|
|
engine.register_node(1);
|
|
|
|
|
engine.register_node(2, {1});
|
|
|
|
|
engine.register_node(3, {2});
|
|
|
|
|
|
|
|
|
|
auto history = make_history();
|
|
|
|
|
int count = engine.full_rebuild(history);
|
|
|
|
|
EXPECT_EQ(count, 3);
|
|
|
|
|
EXPECT_EQ(engine.dirty_count(), 0);
|
|
|
|
|
}
|