mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-22 15:09:19 +02:00
New Components/ Prepare to new scene loading system
This commit is contained in:
86
utilities/AsyncFilePreloader.cpp
Normal file
86
utilities/AsyncFilePreloader.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "stdafx.h"
|
||||
#include "AsyncFilePreloader.h"
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
AsyncFilePreloader GModelPreloader;
|
||||
|
||||
void AsyncFilePreloader::start(int thread_count) {
|
||||
if (m_running.load()) return;
|
||||
|
||||
int n = thread_count > 0 ? thread_count
|
||||
: (int)std::thread::hardware_concurrency();
|
||||
n = std::clamp(n, 1, 8);
|
||||
|
||||
m_running = true;
|
||||
m_workers.reserve(n);
|
||||
for (int i = 0; i < n; ++i)
|
||||
m_workers.emplace_back([this] { worker(); });
|
||||
}
|
||||
|
||||
void AsyncFilePreloader::stop() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
m_running = false;
|
||||
// drain the queue so workers don't pick up more work
|
||||
while (!m_pending.empty()) m_pending.pop();
|
||||
}
|
||||
m_cv.notify_all();
|
||||
for (auto& t : m_workers)
|
||||
if (t.joinable()) t.join();
|
||||
m_workers.clear();
|
||||
}
|
||||
|
||||
void AsyncFilePreloader::queue(std::string path) {
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
if (!m_running) return;
|
||||
if (!m_queued.insert(path).second) return; // already queued / cached
|
||||
m_pending.push(std::move(path));
|
||||
m_cv.notify_one();
|
||||
}
|
||||
|
||||
AsyncFilePreloader::Buffer AsyncFilePreloader::get(const std::string& path) const {
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
auto it = m_cache.find(path);
|
||||
return (it != m_cache.end()) ? it->second : nullptr;
|
||||
}
|
||||
|
||||
void AsyncFilePreloader::clear() {
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
m_cache.clear();
|
||||
m_queued.clear();
|
||||
}
|
||||
|
||||
void AsyncFilePreloader::worker() {
|
||||
while (true) {
|
||||
std::string path;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
m_cv.wait(lk, [this] {
|
||||
return !m_pending.empty() || !m_running.load(std::memory_order_relaxed);
|
||||
});
|
||||
if (!m_running && m_pending.empty()) return;
|
||||
if (m_pending.empty()) continue;
|
||||
path = std::move(m_pending.front());
|
||||
m_pending.pop();
|
||||
}
|
||||
|
||||
auto buf = std::make_shared<std::vector<char>>();
|
||||
{
|
||||
std::ifstream f(path, std::ios::binary | std::ios::ate);
|
||||
if (f) {
|
||||
auto sz = static_cast<std::streamsize>(f.tellg());
|
||||
f.seekg(0);
|
||||
buf->resize(static_cast<size_t>(sz));
|
||||
f.read(buf->data(), sz);
|
||||
if (!f) buf->clear(); // read error — fall back to sync load
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
m_cache[path] = std::move(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
utilities/AsyncFilePreloader.h
Normal file
57
utilities/AsyncFilePreloader.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
#include <atomic>
|
||||
|
||||
// Thread pool that reads files from disk into memory so the main thread can
|
||||
// skip disk I/O and parse directly from RAM. Only covers the binary path
|
||||
// (E3D); text models fall back to synchronous load.
|
||||
class AsyncFilePreloader {
|
||||
public:
|
||||
using Buffer = std::shared_ptr<std::vector<char>>;
|
||||
|
||||
AsyncFilePreloader() = default;
|
||||
~AsyncFilePreloader() { stop(); }
|
||||
|
||||
AsyncFilePreloader(const AsyncFilePreloader&) = delete;
|
||||
AsyncFilePreloader& operator=(const AsyncFilePreloader&) = delete;
|
||||
|
||||
// Start N worker threads (clamped to [1, hardware_concurrency]).
|
||||
void start(int thread_count = 0);
|
||||
|
||||
// Signal workers to stop and join them. Clears the queue but keeps
|
||||
// already-loaded buffers so late callers still get cache hits.
|
||||
void stop();
|
||||
|
||||
// Queue a file for background loading. Silently skips duplicates.
|
||||
void queue(std::string path);
|
||||
|
||||
// Return the preloaded buffer for `path`, or nullptr if not ready yet.
|
||||
// Does NOT block — callers must fall back to synchronous I/O if nullptr.
|
||||
Buffer get(const std::string& path) const;
|
||||
|
||||
// Drop all cached buffers (call after loading finishes to free memory).
|
||||
void clear();
|
||||
|
||||
bool is_running() const { return m_running.load(std::memory_order_relaxed); }
|
||||
|
||||
private:
|
||||
void worker();
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
std::queue<std::string> m_pending;
|
||||
std::unordered_set<std::string> m_queued; // prevents duplicate queuing
|
||||
std::unordered_map<std::string, Buffer> m_cache;
|
||||
std::vector<std::thread> m_workers;
|
||||
std::atomic<bool> m_running { false };
|
||||
};
|
||||
|
||||
extern AsyncFilePreloader GModelPreloader;
|
||||
Reference in New Issue
Block a user