内核优化
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Scene_Base_Test_Renderable : Renderable_Base {
|
||||
@@ -21,3 +26,192 @@ TEST(scene_base_test, publishes_renderable_collection_before_background_render)
|
||||
EXPECT_EQ(renderable->render_sequence, 1);
|
||||
EXPECT_EQ(scene.renderable_count(), 1);
|
||||
}
|
||||
struct Scene_Base_Throwing_Renderable : Renderable_Base {
|
||||
explicit Scene_Base_Throwing_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
|
||||
void render(const Scene_Render_Context&) override {
|
||||
throw std::runtime_error("render failed");
|
||||
}
|
||||
};
|
||||
TEST(scene_base_test, wait_for_render_propagates_background_render_failure) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
EXPECT_THROW(scene.wait_for_render(), std::runtime_error);
|
||||
}
|
||||
TEST(scene_base_test, scene_destruction_does_not_throw_after_background_render_failure) {
|
||||
{
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
}
|
||||
EXPECT_TRUE(true);
|
||||
}
|
||||
struct Scene_Base_Blocking_Renderable : Renderable_Base {
|
||||
explicit Scene_Base_Blocking_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
|
||||
void render(const Scene_Render_Context&) override {
|
||||
std::unique_lock lock(mutex);
|
||||
rendering = true;
|
||||
condition.notify_all();
|
||||
condition.wait(lock, [this] {
|
||||
return released;
|
||||
});
|
||||
}
|
||||
void wait_rendering() {
|
||||
std::unique_lock lock(mutex);
|
||||
condition.wait(lock, [this] {
|
||||
return rendering;
|
||||
});
|
||||
}
|
||||
void release() {
|
||||
std::lock_guard lock(mutex);
|
||||
released = true;
|
||||
condition.notify_all();
|
||||
}
|
||||
std::mutex mutex;
|
||||
std::condition_variable condition;
|
||||
bool rendering{};
|
||||
bool released{};
|
||||
};
|
||||
TEST(scene_base_test, scene_mutation_waits_until_background_render_is_idle) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Blocking_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
renderable->wait_rendering();
|
||||
std::atomic<bool> mutation_finished{};
|
||||
std::thread mutation([&] {
|
||||
scene.set_renderable_configuration(*renderable, {.cache_enabled = true});
|
||||
mutation_finished.store(true, std::memory_order_release);
|
||||
});
|
||||
std::this_thread::yield();
|
||||
EXPECT_FALSE(mutation_finished.load(std::memory_order_acquire));
|
||||
renderable->release();
|
||||
mutation.join();
|
||||
EXPECT_TRUE(mutation_finished.load(std::memory_order_acquire));
|
||||
EXPECT_TRUE(renderable->configuration().cache_enabled);
|
||||
}
|
||||
TEST(scene_base_test, concurrent_render_and_topology_updates_remain_serialized) {
|
||||
Scene2D_Context<> scene;
|
||||
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(parent);
|
||||
scene.attach_renderable(child);
|
||||
std::thread renderer([&] {
|
||||
for (int index = 0; index < 100; ++index) {
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
}
|
||||
});
|
||||
std::thread mutator([&] {
|
||||
for (int index = 0; index < 100; ++index) {
|
||||
Renderable_Base* value = index % 2 == 0 ? parent.get() : nullptr;
|
||||
scene.set_dependency_parent(*child, value);
|
||||
scene.set_display_parent(*child, value);
|
||||
scene.set_renderable_configuration(*child, {.cache_enabled = index % 2 == 0});
|
||||
}
|
||||
});
|
||||
renderer.join();
|
||||
mutator.join();
|
||||
EXPECT_EQ(scene.renderable_count(), 2);
|
||||
}
|
||||
TEST(scene_base_test, detach_releases_renderable_from_both_scene_buffers) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
std::weak_ptr<Scene_Base_Test_Renderable> weak = renderable;
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
scene.detach_renderable(*renderable);
|
||||
renderable.reset();
|
||||
EXPECT_TRUE(weak.expired());
|
||||
EXPECT_EQ(scene.renderable_count(), 0);
|
||||
}
|
||||
TEST(scene_base_test, attach_rejects_null_renderable) {
|
||||
Scene2D_Context<> scene;
|
||||
EXPECT_THROW(scene.attach_renderable({}), std::invalid_argument);
|
||||
}
|
||||
TEST(scene_base_test, topology_rejects_unattached_renderables) {
|
||||
Scene2D_Context<> scene;
|
||||
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(child);
|
||||
EXPECT_THROW(scene.set_dependency_parent(*child, parent.get()), std::invalid_argument);
|
||||
EXPECT_THROW(scene.set_display_parent(*child, parent.get()), std::invalid_argument);
|
||||
scene.attach_renderable(parent);
|
||||
auto detached_child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
EXPECT_THROW(scene.set_dependency_parent(*detached_child, parent.get()), std::invalid_argument);
|
||||
}
|
||||
TEST(scene_base_test, setting_same_topology_parent_is_a_noop) {
|
||||
Scene2D_Context<> scene;
|
||||
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(parent);
|
||||
scene.attach_renderable(child);
|
||||
scene.set_dependency_parent(*child, parent.get());
|
||||
const auto revision = child->cache_revision();
|
||||
EXPECT_NO_THROW(scene.set_dependency_parent(*child, parent.get()));
|
||||
EXPECT_EQ(child->cache_revision(), revision);
|
||||
const auto topology = scene.topology_snapshot();
|
||||
EXPECT_EQ(topology.dependency.size(), 2);
|
||||
bool found{};
|
||||
for (const auto& relation : topology.dependency) {
|
||||
if (relation.child.get() == child.get()) {
|
||||
EXPECT_EQ(relation.parent.get(), parent.get());
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found);
|
||||
}
|
||||
TEST(scene_base_test, topology_snapshot_retains_renderable_lifetime) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
std::weak_ptr<Scene_Base_Test_Renderable> weak = renderable;
|
||||
scene.attach_renderable(renderable);
|
||||
const auto snapshot = scene.topology_snapshot();
|
||||
scene.detach_renderable(*renderable);
|
||||
renderable.reset();
|
||||
EXPECT_FALSE(weak.expired());
|
||||
EXPECT_EQ(snapshot.renderables.size(), 1);
|
||||
EXPECT_EQ(snapshot.renderables.front().get(), weak.lock().get());
|
||||
}
|
||||
TEST(scene_base_test, topology_snapshot_is_safe_during_topology_updates) {
|
||||
Scene2D_Context<> scene;
|
||||
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
|
||||
scene.attach_renderable(parent);
|
||||
scene.attach_renderable(child);
|
||||
std::thread writer([&] {
|
||||
for (int index = 0; index < 500; ++index) {
|
||||
scene.set_dependency_parent(*child, index % 2 == 0 ? parent.get() : nullptr);
|
||||
scene.set_display_parent(*child, index % 2 == 0 ? parent.get() : nullptr);
|
||||
}
|
||||
});
|
||||
for (int index = 0; index < 500; ++index) {
|
||||
const auto snapshot = scene.topology_snapshot();
|
||||
EXPECT_EQ(snapshot.renderables.size(), 2);
|
||||
EXPECT_EQ(snapshot.display.size(), 2);
|
||||
EXPECT_EQ(snapshot.dependency.size(), 2);
|
||||
}
|
||||
writer.join();
|
||||
}
|
||||
TEST(scene_base_test, all_waiters_receive_the_same_render_failure) {
|
||||
Scene2D_Context<> scene;
|
||||
auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
std::atomic<int> failures{};
|
||||
auto waiter = [&] {
|
||||
try {
|
||||
scene.wait_for_render();
|
||||
} catch (const std::runtime_error&) {
|
||||
failures.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
std::thread first(waiter);
|
||||
std::thread second(waiter);
|
||||
first.join();
|
||||
second.join();
|
||||
EXPECT_EQ(failures.load(), 2);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user