lumiera_documentation/src/vault/mem/engine-buffer-manager.hpp

258 lines
7.1 KiB
C++
Raw Normal View History

BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
/*
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.
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
**
** 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.
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
**
** @see EnginePufferManager_test
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
** @see LocalBufferStore_test
** @see BufferProviderProtocol_test
** @see buffer-provider.hpp
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
*/
#ifndef VAULT_MEM_ENGINE_BUFFR_MANAGA_H
#define VAULT_MEM_ENGINE_BUFFR_MANAGA_H
//#include "lib/hash-value.h"
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
#include "vault/mem/buffhandle.hpp"
//#include "vault/mem/type-handler.hpp"
//#include "vault/mem/buffer-local-tag.hpp"
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
#include "lib/nocopy.hpp"
#include "lib/sync.hpp"
//#include "lib/util.hpp"
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
#include <boost/lockfree/queue.hpp>
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
namespace vault{
namespace mem {
// using lib::HashVal;
// using util::unConst;
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
namespace err = lumiera::error;
namespace { // config and internal helpers...
const size_t INQUEUE_SIZ = 30; ///< initial size of the lock-free provision queue
}
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
using lib::Sync;
using lib::RecursiveLock_NoWait;
struct Alloc
{
Buff* mem;
size_t siz;
bool
empty() const
{
return not bool(mem);
}
void
discard()
{
mem = nullptr;
siz = 0;
}
};
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
/**
* 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& alloc)
{
inQueue_.push (alloc);
alloc.discard();
}
};
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
/*********************************************************//**
* Core service of the Render Engine : global buffer manager.
*/
class EngineBufferManager
: public AllocReceiver
, public Sync<RecursiveLock_NoWait>
{
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 (AllocReceiver&, size_t sizRequest);
/**
* 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:
/** 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 (AllocReceiver& receiver, size_t sizRequest)
{
REQUIRE (sizRequest);
requestQueue_.push ({&receiver, sizRequest});
}
/** 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()
{
return globalPool_.allocated_;
}
size_t
bytesLeased()
{
return globalPool_.leased_;
}
size_t
numAllocs()
{
return globalPool_.cnt_;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1430 : add pool diagnostics here if we actually switch to pooled allocations
};
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
/** entrance point to inspection for test */
inline BufferManagerDiagnostic
watch (EngineBufferManager const& manager)
{
return BufferManagerDiagnostic{manager};
}
BufferProvider: draft setup for engine-wide coordination hub What I am building step by step right now is the first draft of a production-grade render buffer management scheme. For this I need suitable implementations of `BufferStag` (and `BufferStore`) These will include a `LocalSlice` to hold thread-local »satelitte services«, which in turn need a connection to some central coordination hub. The thread-local instances can access the hub via our `lib::Depend` (I have just verified that this works as intended, with on-demand creation of the central instance on first access from some thread. This is important, since very likely we need to install a dedicated and speficically wired service instance, which must happen prior to first dependency-request via `lib::Depend`. And since the worker-threads are created by the Engine, we get ample opportunity to do so before starting those threads. Included in this changeset is a first rough draft of the central registry `EngineBufferMetadata` — I have modelled it analogous to the `SimpleBufferStateRegistry`, which is the test-level implementation of the `BufferStage` API. However, we do not want to expose direct buffer allocation operations on the central hub, and thus I have left out the respecive parts of the API. Furthermore, I have added an `abandon()` operation, without there beeing a need for such an operation right now. But since this operation is tricky and potentially dangerous, I prefer implementing it the correct way, since I have a clear understanding what this entails at the moment.
2026-04-19 18:02:38 +02:00
}} // namespace vault::mem
#endif /*VAULT_MEM_ENGINE_BUFFR_MANAGA_H*/