Add deferred async scene asset loading
This commit is contained in:
158
tests/core/Asset/test_resource_manager.cpp
Normal file
158
tests/core/Asset/test_resource_manager.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/IO/IResourceLoader.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace XCEngine::Resources;
|
||||
|
||||
namespace {
|
||||
|
||||
class BlockingMeshLoader : public IResourceLoader {
|
||||
public:
|
||||
ResourceType GetResourceType() const override { return ResourceType::Mesh; }
|
||||
|
||||
XCEngine::Containers::Array<XCEngine::Containers::String> GetSupportedExtensions() const override {
|
||||
XCEngine::Containers::Array<XCEngine::Containers::String> extensions;
|
||||
extensions.PushBack("mesh");
|
||||
return extensions;
|
||||
}
|
||||
|
||||
bool CanLoad(const XCEngine::Containers::String& path) const override {
|
||||
(void)path;
|
||||
return true;
|
||||
}
|
||||
|
||||
LoadResult Load(const XCEngine::Containers::String& path,
|
||||
const ImportSettings* settings = nullptr) override {
|
||||
(void)settings;
|
||||
|
||||
++m_loadCalls;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_started = true;
|
||||
}
|
||||
m_condition.notify_all();
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_condition.wait(lock, [this]() { return m_allowCompletion; });
|
||||
}
|
||||
|
||||
auto* mesh = new Mesh();
|
||||
IResource::ConstructParams params = {};
|
||||
params.name = "BlockingMesh";
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
mesh->Initialize(params);
|
||||
|
||||
const StaticMeshVertex vertices[3] = {};
|
||||
const XCEngine::Core::uint32 indices[3] = {0, 1, 2};
|
||||
mesh->SetVertexData(vertices, sizeof(vertices), 3, sizeof(StaticMeshVertex), VertexAttribute::Position);
|
||||
mesh->SetIndexData(indices, sizeof(indices), 3, true);
|
||||
return LoadResult(mesh);
|
||||
}
|
||||
|
||||
ImportSettings* GetDefaultSettings() const override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool WaitForStart(std::chrono::milliseconds timeout) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
return m_condition.wait_for(lock, timeout, [this]() { return m_started; });
|
||||
}
|
||||
|
||||
void AllowCompletion() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_allowCompletion = true;
|
||||
}
|
||||
m_condition.notify_all();
|
||||
}
|
||||
|
||||
int GetLoadCalls() const {
|
||||
return m_loadCalls.load();
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<int> m_loadCalls{0};
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
bool m_started = false;
|
||||
bool m_allowCompletion = false;
|
||||
};
|
||||
|
||||
bool PumpAsyncLoadsUntil(ResourceManager& manager,
|
||||
const std::function<bool()>& condition,
|
||||
std::chrono::milliseconds timeout = std::chrono::milliseconds(2000)) {
|
||||
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||
while (!condition() && std::chrono::steady_clock::now() < deadline) {
|
||||
manager.UpdateAsyncLoads();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
|
||||
manager.UpdateAsyncLoads();
|
||||
return condition();
|
||||
}
|
||||
|
||||
TEST(ResourceManager_Test, ConcurrentAsyncLoadsCoalesceSameMeshPath) {
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
|
||||
struct LoaderGuard {
|
||||
ResourceManager* manager = nullptr;
|
||||
IResourceLoader* loader = nullptr;
|
||||
~LoaderGuard() {
|
||||
if (manager != nullptr && loader != nullptr) {
|
||||
manager->RegisterLoader(loader);
|
||||
}
|
||||
}
|
||||
} loaderGuard{ &manager, manager.GetLoader(ResourceType::Mesh) };
|
||||
|
||||
BlockingMeshLoader blockingLoader;
|
||||
manager.RegisterLoader(&blockingLoader);
|
||||
|
||||
std::mutex resultsMutex;
|
||||
std::vector<IResource*> callbackResources;
|
||||
std::atomic<int> callbackCount{0};
|
||||
|
||||
const auto callback = [&](LoadResult result) {
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_NE(result.resource, nullptr);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(resultsMutex);
|
||||
callbackResources.push_back(result.resource);
|
||||
}
|
||||
++callbackCount;
|
||||
};
|
||||
|
||||
manager.LoadAsync("Meshes/concurrent.mesh", ResourceType::Mesh, callback);
|
||||
manager.LoadAsync("Meshes/concurrent.mesh", ResourceType::Mesh, callback);
|
||||
|
||||
ASSERT_TRUE(blockingLoader.WaitForStart(std::chrono::milliseconds(1000)));
|
||||
EXPECT_EQ(blockingLoader.GetLoadCalls(), 1);
|
||||
|
||||
blockingLoader.AllowCompletion();
|
||||
|
||||
ASSERT_TRUE(PumpAsyncLoadsUntil(
|
||||
manager,
|
||||
[&]() { return callbackCount.load() == 2 && manager.GetAsyncPendingCount() == 0; },
|
||||
std::chrono::milliseconds(2000)));
|
||||
|
||||
EXPECT_EQ(blockingLoader.GetLoadCalls(), 1);
|
||||
|
||||
ASSERT_EQ(callbackResources.size(), 2u);
|
||||
EXPECT_EQ(callbackResources[0], callbackResources[1]);
|
||||
|
||||
manager.Shutdown();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user