/* EngineBufferManager(Test) - cover the interface to a central buffer manager Copyright (C) 2026, Hermann Vosseler   **Lumiera** is free software; you can redistribute it and/or modify it   under the terms of the GNU General Public License as published by the   Free Software Foundation; either version 2 of the License, or (at your   option) any later version. See the file COPYING for further details. * *****************************************************************/ /** @file engine-buffer-manager-test.cpp ** unit test \ref EngineBufferManager_test */ #include "test/run.hpp" #include "test/test-helper.hpp" //#include "vault/mem/engine-buffer-metadata.hpp" #include "vault/mem/engine-buffer-manager.hpp" #include "vault/mem/engine-buffer-allocator.hpp" #include "lib/scoped-collection.hpp" #include "lib/iter-explorer.hpp" //#include "lib/depend-inject.hpp" #include "lib/thread.hpp" //#include "lib/error.hpp" #include "lib/symbol.hpp" #include "test/diagnostic-output.hpp"////////////TODO #include //#include #include using lib::Thread; using lib::Literal; using lib::explore; //using lib::ScopedCollection; using std::this_thread::yield; using std::this_thread::sleep_for; using std::chrono::microseconds; using std::chrono_literals::operator ""ms; namespace vault { namespace mem { namespace test { // using LERR_(LOGIC); // using LERR_(LIFECYCLE); namespace { // Test helper const size_t ALLOC_REQ = 1024*1024; const size_t NUM_THREADS = 100; // template // Buff* // mark_as_Buffer(X& something) // { // return reinterpret_cast (std::addressof(something)); // } // } /*****************************************************************************//** * @test Show how the central buffer manager of the Render Engine can be accessed. */ class EngineBufferManager_test : public Test { virtual void run (Arg) { seedRand(); demonstrate_AllocatorInterface(); verify_syncRequest(); verify_asyncRequest(); verify_lulz(); } /** @test show how to allocate buffer memory * with the underlying base allocator. * @remark 4/2026 for the first integration of the Render Engine, * a simplified implementation is used that just delegates * to the regular heap allocator (which actually performs * quite well on modern C++ STDLib). The plan is to * use some kind of tiling pool allocator eventually, * contingent on the actual performance observations. */ void demonstrate_AllocatorInterface() { using Allo = HeapBufferAllocator; CHECK (lib::allo::is_Stateless_v); Allo allo; auto a1 = allo.allocate (55); auto a2 = allo.allocate (555); CHECK (a1.mem); CHECK (a2.mem); CHECK (a1.siz == Allo::TILE_SIZ); CHECK (a2.siz % Allo::TILE_SIZ == 0); CHECK (a2.siz > 555); // can use that memory without blowing up.... auto doooh = new(a1.mem) f128{std::numeric_limits::max()}; *doooh /= 2; CHECK (2 == std::numeric_limits::max() / *doooh); allo.deallocate (a1); allo.deallocate (a2); } /** @test show that a direct request for allocation is honored * - request some memory * - read and write to that memory * - return all leased allocations * - verify the diagnostic meshes up with the allocation * @remark in principle it is not possible to prove that some * memory allocation actually works, other than checking * that access to the given memory is possible, because * the fact that no memory corruption took place is * only semi-decidable (you can show a corruption event, * but not the absence of any such an event). */ void verify_syncRequest() { EngineBufferManager manager; CHECK (watch(manager).isEmpty()); Alloc alloc = manager.requestAllocation (555); CHECK (not alloc.empty()); CHECK (alloc.siz >= 555); CHECK (alloc.siz == watch(manager).bytesAllocd()); CHECK (alloc.siz == watch(manager).bytesLeased()); CHECK (alloc.siz % HeapBufferAllocator::TILE_SIZ == 0); // can use that memory char* buf = reinterpret_cast (alloc.mem); string ranS{lib::randStr(555-1)}; auto* p = std::copy (ranS.begin(), ranS.end(), buf); CHECK (ranS.length() == size_t(p - buf)); *p = '\0'; CHECK (ranS == buf); CHECK (1 == watch(manager).numAllocs()); Alloc a2 = manager.requestAllocation (1'000'000); CHECK (not a2.empty()); CHECK (a2.siz >= 1e6); CHECK (2 == watch(manager).numAllocs()); CHECK (1e6+555 < watch(manager).bytesLeased()); manager.supply (a2); manager.processPendingRequests(); CHECK (1 == watch(manager).numAllocs()); CHECK (1e6 > watch(manager).bytesLeased()); manager.supply (alloc); manager.processPendingRequests(); CHECK (0 == watch(manager).numAllocs()); CHECK (0 == watch(manager).bytesLeased()); } /** @test show that an working allocation request passes thread boundaries */ void verify_asyncRequest() { struct LocalTestPool : AllocReceiver { Alloc retrieveAlloc() { Alloc received{nullptr, 0}; inQueue_.pop (received); // Note: no-op if queue is empty return received; } bool empty() const { return inQueue_.empty(); } }; EngineBufferManager manager; using WorkBuff = std::array; std::atomic_int cnt{0}; auto workSeq = [&, gen = makeRandGen()]// local random generator per thread () mutable {// sequence of actions within a worker thread int i=++cnt; LocalTestPool myPool; CHECK (myPool.empty()); manager.async_requestAllocation (myPool, ALLOC_REQ); // meanwhile the worker does some other "work" uint delay = 100 + gen.i(800); sleep_for (microseconds(delay)); SHOW_EXPR(delay) Alloc alloc = myPool.retrieveAlloc(); if (alloc.empty()) // they let us down ... // insist harder to be serviced thusly alloc = manager.requestAllocation (ALLOC_REQ); CHECK (not alloc.empty()); CHECK (ALLOC_REQ <= alloc.siz); // can use that allocation for some "serious work"... auto content = new(alloc.mem) WorkBuff; std::ranges::generate (*content, [&]{ return gen.u64(); }); sleep_for (10ms); // pass allocation back to the central hub manager.supply (alloc); SHOW_EXPR(i) }; using Threads = lib::ScopedCollection; auto startThread = [&](auto& storage){ storage.template create (workSeq); }; // Start a collection of workers.... Threads threads{NUM_THREADS, startThread}; // wait for all threads to finish... while (explore(threads).has_any()) yield(); // all the allocations sent back by the workers // should sit in the EngineBufferManager's input queue SHOW_EXPR(watch(manager).numAllocs()); // CHECK (NUM_THREADS == watch(manager).numAllocs()); SHOW_EXPR(watch(manager).bytesLeased()); // CHECK (NUM_THREADS*ALLOC_REQ <= watch(manager).bytesLeased()); // process in-queue and retrieve all allocations.. manager.processPendingRequests(); SHOW_EXPR(watch(manager).numAllocs()); SHOW_EXPR(watch(manager).bytesLeased()); // CHECK (0 == watch(manager).bytesLeased()); } /** @test */ void verify_lulz() { } }; /** Register this test class... */ LAUNCHER (EngineBufferManager_test, "unit engine"); }}} // namespace vault::mem::test