lumiera_documentation/src/vault/mem/engine-buffer-manager.hpp
Ichthyostega 5a47b099be BufferProvider: find a solution for processing allocations
Implementing the `EngineBufferManager` the desired way,
with the ability to post allocation requests asynchronously,
raises the difficult question of who will ultimately perform
the allocation / deallocation and further management work.

The point is that this management effort takes up capacity
originally assigned for the render workers themselves, either
by implicitly pushing aside those threads, or as a dedicated
processing assigned to some worker.

In the current stage of development it seems impossible to decide
upon the proper mechanism to schedule this excess work explicitly.

The solution proposed hereby however circumvents this dilemma,
insofar it allows to implement the synchronous, fallback processing
in terms of the asynchronous processing, just performed exclusively
within the current thread. Unless some processing will be scheduled
explicitly (which can be added as performance optimisation later on),
the handling of a direct allocation request will ''fold down'' into
the current worker eventually, thereby using it's processing capability
without the need of any explicit scheduling.
2026-05-12 23:46:55 +02:00

243 lines
7 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
ENGINE-BUFFER-MANAGER.hpp - central buffer allocation coordinator
Copyright (C)
2026, Hermann Vosseler <Ichthyostega@web.de>
  **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.hpp
** Central hub of buffer management within the Lumiera Render Engine.
** Instantiated as global service and part of the Engine, this global
** allocation coordinator is responsible to provide pre-allocated blocks
** for render buffers. These allocations are categorised by their size,
** and will be sent down to LocalMemPool instances running in each
** worker thread. This involves asynchronous messaging to avoid
** any blocking of work jobs due to central resource management.
**
** Besides the interface of the EngineBufferManager, this header also
** defines some common structures, like the \ref mem::Alloc, and the
** baseclass \ref AllocReceiver, with a lock-free inbound queue
** so that allocations can be supplied to a local memory manager
** and picked up from within the worker thread later.
** @remark both the LocalMemPool (for each worker thread) and the
** EngineBufferManager, as global memory service, are based on
** such an input queue, allowing to transfer control over some
** allocation from the central allocator to a local memory pool.
**
** @see EnginePufferManager_test
** @see LocalBufferStore_test
** @see BufferProviderProtocol_test
** @see buffer-provider.hpp
*/
#ifndef VAULT_MEM_ENGINE_BUFFR_MANAGA_H
#define VAULT_MEM_ENGINE_BUFFR_MANAGA_H
//#include "lib/hash-value.h"
#include "vault/mem/buffhandle.hpp"
//#include "vault/mem/type-handler.hpp"
//#include "vault/mem/buffer-local-tag.hpp"
#include "lib/nocopy.hpp"
//#include "lib/util.hpp"
#include <boost/lockfree/queue.hpp>
namespace vault{
namespace mem {
// using lib::HashVal;
// using util::unConst;
namespace err = lumiera::error;
namespace { // config and internal helpers...
const size_t INQUEUE_SIZ = 30; ///< initial size of the lock-free provision queue
}
struct Alloc
{
Buff* mem;
size_t siz;
bool empty() const { return not bool(mem); }
};
/**
* Base building block: a memory handler
* that can receive memory allocations for further use.
*/
class AllocReceiver
: util::NonCopyable
{
protected:
using InQueue = boost::lockfree::queue<Alloc>;
InQueue inQueue_;
AllocReceiver()
: inQueue_{INQUEUE_SIZ}
{ }
public:
/** Transfer control over the given allocation.
* @remark the \ref Alloc is placed into a lock-free queue;
* implementation code from a subclass will have to
* receive and dispatch those entries */
void
supply (Alloc const& alloc)
{
inQueue_.push (alloc);
}
};
/*********************************************************//**
* Core service of the Render Engine : global buffer manager.
*/
class EngineBufferManager
: public AllocReceiver
{
struct AllocRequest
{
AllocReceiver* receiver;
size_t sizRequest;
};
using RequestQueue = boost::lockfree::queue<AllocRequest>;
RequestQueue requestQueue_;
/// diagnostic bookkeeping
size_t allocated_{0};
size_t leased_{0};
size_t cnt_{0};
public:
EngineBufferManager()
: AllocReceiver{}
, requestQueue_{INQUEUE_SIZ}
{ }
size_t size() const;
bool empty() const;
/****************************************************************//**
* Core function: satisfy a request for allocation
* @return an \ref Alloc struct representing the desired allocation,
* can be empty to signal that the request can not be satisfied.
* @warning whoever accepts a non-empty result from this call
* is **responsible** to [return](\ref AllocReceiver::supply)
* this allocation eventually, to prevent memory leakage.
*/
Alloc requestAllocation (size_t sizRequest);
/**
* Standard access point: request an allocation
* that will be dispatched asynchronously to the given receiver.
* @remark without any temporal guarantees, this request will be
* processed eventually, placing an \ref Alloc into the
* [receiver's inqueue](\ref AllocReceiver::supply)
* @warning when the returned Alloc is [non-empty](\ref Alloc::empty),
* the receiver is responsible for returning it after use.
*/
void async_requestAllocation (AllocRequest const&);
/**
* Handle all enqueued asynchronous requests
* and perform pending management and allocation work.
* @remark this function can be invoked as a batch job;
* it acquires a lock and then handles the backlog.
*/
void processPendingRequests();
private:
/** process all pending requests in the queue */
void handleAllocRequests();
/** performs the actual allocation work */
Alloc doPerformAllocation (size_t sizRequest);
void doRedeemAllocation (Alloc);
/** a »backdoor« for unit testing */
friend class BufferManagerDiagnostic;
};
inline void
EngineBufferManager::async_requestAllocation (AllocRequest const& req)
{
}
inline void
EngineBufferManager::processPendingRequests()
{
inQueue_.consume_all ([this](Alloc alloc){ doRedeemAllocation (alloc); });
handleAllocRequests();
}
/** wrapper to inspect internals from a unit test */
class BufferManagerDiagnostic
: util::MoveOnly
{
EngineBufferManager const& globalPool_;
public:
BufferManagerDiagnostic (EngineBufferManager const& manager)
: globalPool_{manager}
{ }
bool
isEmpty()
{
return globalPool_.empty();
}
size_t
size()
{
return globalPool_.size();
}
size_t
bytesAllocd()
{
UNIMPLEMENTED ("alloc state diagnostic");
}
size_t
bytesLeased()
{
UNIMPLEMENTED ("alloc state diagnostic");
}
/////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1430 : add pool diagnostics here if we actually switch to pooled allocations
};
/** entrance point to inspection for test */
inline BufferManagerDiagnostic
watch (EngineBufferManager const& manager)
{
return BufferManagerDiagnostic{manager};
}
}} // namespace vault::mem
#endif /*VAULT_MEM_ENGINE_BUFFR_MANAGA_H*/