mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
954e28b051
Both preload functions now take an aggregate decoded-byte cap and a thread cap (runtime: 512 MiB, 4 threads). Each image is probed and its RGBA reserved against the cap before decoding; per-image limits follow the pack's decode budget, which is no longer grown to usage (pinned images sit outside it, and outside the Duck 512-entry cap). The pool (texture_pack_ preload.hpp) joins every started thread on unwinding, survives thread creation failure, and catches worker exceptions. Images not preloaded are left to the asynchronous budgeted path instead of being marked failed, and HdTexturePreloadResult reports loaded / over-cap / failed / worker errors with a complete-or-partial status. The Beetle async worker no longer lets allocation failures escape its thread entry. Tests cover both formats: complete preload, over-cap skip with on-demand fallback, malformed images, injected worker and thread-creation failures, and budget reservation bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
3.0 KiB
C++
97 lines
3.0 KiB
C++
#ifndef PSX_TEXTURE_PACK_PRELOAD_HPP
|
|
#define PSX_TEXTURE_PACK_PRELOAD_HPP
|
|
|
|
/* Shared machinery for the elective full texture-pack preload
|
|
* (hd_texture_pack_preload, duck_texture_pack_preload): an aggregate
|
|
* decoded-byte reservation and a bounded worker pool that can neither leak a
|
|
* joinable std::thread nor let an exception escape a thread entry. */
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <new>
|
|
#include <system_error>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
namespace psx_texpreload {
|
|
|
|
/* Test-only fault injection. 1: the first item's decode throws
|
|
* std::bad_alloc. 2: creating the second pool thread throws std::system_error. */
|
|
inline std::atomic<int>& fault_mode() {
|
|
static std::atomic<int> mode{0};
|
|
return mode;
|
|
}
|
|
|
|
/* Aggregate decoded bytes, reserved before an image is decoded. */
|
|
class Budget {
|
|
public:
|
|
explicit Budget(size_t cap) : cap_(cap) {}
|
|
bool reserve(size_t bytes) {
|
|
size_t cur = used_.load();
|
|
do {
|
|
if (bytes > cap_ || cur > cap_ - bytes) return false;
|
|
} while (!used_.compare_exchange_weak(cur, cur + bytes));
|
|
return true;
|
|
}
|
|
void release(size_t bytes) { used_.fetch_sub(bytes); }
|
|
private:
|
|
size_t cap_;
|
|
std::atomic<size_t> used_{0};
|
|
};
|
|
|
|
/* Per-item outcome, written by exactly one worker. */
|
|
enum ItemStatus : unsigned char { kPending = 0, kLoaded, kFailed, kOverBudget };
|
|
|
|
/* Joins every started thread when the pool goes out of scope, including while
|
|
* unwinding from a failed std::thread construction. */
|
|
struct Joiner {
|
|
std::vector<std::thread>& threads;
|
|
~Joiner() {
|
|
for (auto& t : threads)
|
|
if (t.joinable()) t.join();
|
|
}
|
|
};
|
|
|
|
/* Runs item(k) for every k < n on at most max_threads threads, the caller
|
|
* being one of them. An exception from item(k) is caught and counted, never
|
|
* propagated out of a thread entry. A failure to start a thread only lowers
|
|
* concurrency. Returns the number of items whose item(k) threw. */
|
|
template <class F>
|
|
size_t run(size_t n, unsigned max_threads, F&& item) {
|
|
std::atomic<size_t> next{0}, errors{0};
|
|
auto work = [&]() noexcept {
|
|
for (size_t k; (k = next.fetch_add(1)) < n;) {
|
|
try {
|
|
if (k == 0 && fault_mode().load() == 1) throw std::bad_alloc();
|
|
item(k);
|
|
} catch (...) {
|
|
errors.fetch_add(1);
|
|
}
|
|
}
|
|
};
|
|
unsigned hw = std::thread::hardware_concurrency();
|
|
if (hw == 0) hw = 1;
|
|
const unsigned threads = std::max(1u, std::min(max_threads ? max_threads : 1u, hw));
|
|
std::vector<std::thread> pool;
|
|
{
|
|
Joiner joiner{pool};
|
|
for (unsigned t = 1; t < threads && t < n; ++t) {
|
|
try {
|
|
if (t == 1 && fault_mode().load() == 2)
|
|
throw std::system_error(
|
|
std::make_error_code(std::errc::resource_unavailable_try_again));
|
|
pool.emplace_back(work);
|
|
} catch (...) {
|
|
break;
|
|
}
|
|
}
|
|
work();
|
|
}
|
|
return errors.load();
|
|
}
|
|
|
|
} // namespace psx_texpreload
|
|
|
|
#endif
|