BufferProvider: remove rest of the old implementation

__note__: regarding hash-standard.hpp see Ticket #1422
Mostly this hack worked -- but it is very likely not needed any more.
Today I had to re-wire a partial specialisation for hashing pointers,
that was added somewhere in the past into the STL.
This commit is contained in:
Fischlurch 2026-04-11 23:52:11 +02:00
parent 7652ca05cd
commit 5686694999
4 changed files with 51 additions and 365 deletions

View file

@ -44,9 +44,11 @@
** Please ensure it is always included _before_ the latter. Failing to do so will result
** in mysterious failures.
**
** @todo 4/2014 doesn't work as expected. My suspicion is that in the actual use case (PlacementIndex),
** the type providing the hasher is mixed in through inheritance, and the template specialisation
** for this base type is not considered on lookup. ///////TICKET #722
** @todo 4/2026 this shady trickery works without problems since many years now,
** while it is not clear if we actually need to do these horrible things any more.
** Even while we may retain that automatic bridge to boost-functional hash, doing so
** should be possible with the current STDLIB without having to patch and rewire
** the library implementation. //////////////////////////////////////////////////////////////////////TICKET #1422 : re-assess the situation !
**
** @see HashIndexed
** @see LUID
@ -106,10 +108,6 @@ namespace std {
{
static_assert (sizeof(TY) < 0, "No hash implementation found. "
"Either specialise std::hash or provide a boost-style hash_value via ADL.");
// the default provides *no* hash implementation
// and adds a marker type for metaprogramming
typedef int NotHashable;
};
/**
@ -129,8 +127,8 @@ namespace std {
/**
* Primary class template for std::hash.
* We provide no default implementation, but a marker type
* to allow detection of custom implementation through metaprogramming
* We provide no default implementation, but bridge automatically
* when a boost-style hash function can be picked up via ADL.
*
* @note this definition has been \em hijacked by Lumiera
* to add an automatic bridge to use boost::hash functions
@ -140,12 +138,14 @@ namespace std {
: public _HashImplementationSelector<TY>
{ };
}
}// namespace std
/* ==== Evil Evil ==== */
/* ==== Evil Evil ==== */ ////////////////////////////////////////////////////////////////TICKET #1422 : very likely we do not need this manipulation any more !!!
#define hash hash_HIDDEN
#define _Hash_impl _Hash_impl_HIDDEN
@ -204,6 +204,12 @@ namespace std {
STD_HASH_IMPL (long double)
#undef STD_HASH_IMPL
}
/** rewire the partial specialisation for pointer types */
template<typename TY>
struct hash<TY*> : public hash_HIDDEN<TY*> { };
}// namespace std
#endif /*LIB_HASH_STANDARD_H*/

View file

@ -27,8 +27,9 @@
** are configured as part of starting the steam::engine::RenderEnvironment.
** @todo who is responsible for setup of the services for the actual render engine?
** Might be closely related to brining up façade interfaces.
** @todo WIP-WIP 1/2026 provide a default implementation for test
** and find a way to populate the Facilities with the
** @todo WIP-WIP 1/2026
** - ✔ provide a default implementation for test
** - 🔁 find a way to populate the Facilities with the
** production variant of the services...
**
** @see engine-ctx-test.cpp

View file

@ -14,262 +14,38 @@
/** @file heap-mem-buffer-store.cpp
** Implementation details of simple heap based engine::BufferProvider storage.
** This BufferStore implementation is configured by default and thus used primarily
** from the unit tests; for that reasons, some additional sanity checks were added,
** and the implementation is wasteful and not performance oriented. Furthermore,
** aspects of concurrency safety were not considered.
**
** For each allocation, a new vault::mem::HeapMemBufferStore::Alloc entry is attached
** to the index table, which allows follow-up calls to rediscover this management entry
** just based on the memory address of the allocation. These entries manage the actual
** allocations, and **will be retained** for the lifespan of the BufferStore instance.
** This allows diagnostic code to investigate the contents of the buffers
** after invoking the code subject to testing.
*/
#include "lib/error.hpp"
#include "lib/integral.hpp"
#include "include/logging.h"
#include "lib/scoped-ptrvect.hpp"
#include "vault/mem/buffer-metadata.hpp"
#include "lib/format-string.hpp"
#include "lib/format-obj.hpp"
#include "lib/util-foreach.hpp"
#include "lib/nocopy.hpp"
#include "vault/mem/heap-mem-buffer-store.hpp"
#include <algorithm>
#include <vector>
#include <array>
using util::and_all;
using std::vector;
using std::array;
using lib::ScopedPtrVect;
using util::contains;
using util::_Fmt;
namespace vault {
namespace mem {
namespace err = lumiera::error;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET 1410 : need to question what implementation structures are needed, after »tracking« was extracted...
/**
* Simplistic implementation of buffer storage.
* Allocates a block of heap memory for buffers and never deallocates.
* This strange behaviour can be used to investigate and demonstrate
* buffer usage from a unit test setup.
* @see DiagnosticBufferProvider
*/
class HeapMemBufferStore::Block
: util::NonCopyable
{
unique_ptr<char[]> storage_;
bool was_released_;
public:
explicit
Block(size_t bufferSize)
: storage_(bufferSize? new char[bufferSize] : NULL)
, was_released_(false)
{ }
bool
was_used() const
{
return bool(storage_);
}
bool
was_closed() const
{
return was_released_;
}
void*
accessMemory() const
{
REQUIRE (storage_, "Block was never prepared for use");
return storage_.get();
}
void
markReleased()
{
was_released_ = true;
}
};
namespace { // implementation helpers...
inline Buff*
asBuffer(void* mem)
{// type tag to mark memory address as Buffer
return static_cast<Buff*> (mem);
}
using Block = HeapMemBufferStore::Block;
/** helper to find Block entries
* based on their raw memory address */
inline bool
identifyBlock (Block const& inQuestion, void* storage)
{
return storage == &inQuestion;
}
/** build a searching predicate */
inline function<bool(Block const&)>
search_for_block_using_this_storage (void* storage)
{
return bind (identifyBlock, _1, storage);
}
template<class VEC>
inline Block*
pick_Block_by_storage (VEC& vec, void* blockLocation)
{
typename VEC::iterator pos
= std::find_if (vec.begin(),vec.end()
,search_for_block_using_this_storage(blockLocation));
if (pos!=vec.end())
return &(*pos);
else
return NULL;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET 1410 : need to question what implementation structures are needed, after »tracking« was extracted...
using PoolBlocks = ScopedPtrVect<HeapMemBufferStore::Block>;
/**
* @internal Pool of allocated buffer Blocks of a specific size.
* Helper for implementing a Diagnostic BufferProvider; actually does
* just heap allocations for the Blocks, but keeps a collection of
* allocated Blocks around. Individual entries can be retrieved
* and thus removed from the responsibility of BlockPool.
*
* The idea is that each buffer starts its lifecycle within some pool
* and later gets "emitted" to an output sequence, where it remains for
* later investigation and diagnostics.
*/
class HeapMemBufferStore::BlockPool
{
uint maxAllocCount_;
size_t memBlockSize_;
using PoolBlocks = ScopedPtrVect<Block>;
PoolBlocks blockList_;
public:
BlockPool()
: maxAllocCount_(0) // unlimited by default
, memBlockSize_(0)
, blockList_{}
{ }
void
initialise (size_t blockSize)
{
memBlockSize_ = blockSize;
}
// standard copy operations are valid, but will
// raise an runtime error, once BlockPool is initialised.
~BlockPool()
{
if (!verify_all_children_idle())
ERROR (test, "Block actively in use while shutting down BufferProvider "
"allocation pool. This might lead to Segfault and memory leaks.");
}
/** mark all managed blocks as disposed */
void
discard()
{
for (Block& block : blockList_)
block.markReleased();
}
uint
prepare_for (uint number_of_expected_buffers)
{
if (maxAllocCount_ &&
maxAllocCount_ < blockList_.size() + number_of_expected_buffers)
{
ASSERT (maxAllocCount_ >= blockList_.size());
return maxAllocCount_ - blockList_.size();
}
// currently no hard limit imposed
return number_of_expected_buffers;
}
Block&
createBlock()
{
return blockList_.manage (new Block(memBlockSize_));
}
Block*
find (void* blockLocation)
{
return pick_Block_by_storage (blockList_, blockLocation);
}
Block*
transferResponsibility (Block* allocatedBlock)
{
return blockList_.detach (allocatedBlock);
}
size_t
size() const
{
return blockList_.size();
}
bool
isValid() const
{
return not blockList_.empty();
}
explicit
operator bool() const
{
return isValid();
}
private:
bool
verify_all_children_idle()
{
try {
return and_all (blockList_, is_in_sane_state);
}
ERROR_LOG_AND_IGNORE (test, "State verification of diagnostic BufferProvider allocation pool");
return true;
}
static bool
is_in_sane_state (Block const& block)
{
return not block.was_used() or block.was_closed();
}
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET 1410 : need to question what implementation structures are needed, after »tracking« was extracted...
namespace { // Details of allocation and accounting
const uint MAX_BUFFERS = 50;
Block emptyPlaceholder(0);
namespace { // internal config and helpers...
using StorageWord = uint64_t;
constexpr inline size_t WORD_SIZ = sizeof(StorageWord);
@ -285,19 +61,21 @@ namespace mem {
{
return ceilDiv (sizeRequest, WORD_SIZ);
}
static_assert (0 == wordCnt(0));
static_assert (1 == wordCnt(1));
static_assert (1 == wordCnt(WORD_SIZ));
inline void
zeroFill (void* buf, size_t cnt)
{
auto begin = static_cast<StorageWord*> (buf);
std::fill (begin, begin+cnt, StorageWord(0));
}
} // (END) Details of allocation and accounting
} //(END) Internals
/**
@ -313,7 +91,7 @@ namespace mem {
Buff* mem_{nullptr};
size_t siz_{0};
BufferState state_{NIL};
void
__requireState (BufferState expected)
{
@ -324,7 +102,7 @@ namespace mem {
,LERR_(LIFECYCLE)
};
}
void
allocate (size_t cnt)
{
@ -412,13 +190,10 @@ namespace mem {
/**
* @internal create a memory tracking BufferProvider,
*/
HeapMemBufferStore::HeapMemBufferStore()
: allocIdx_{}
, pool_(new PoolTable)
, outSeq_()
{ }
HeapMemBufferStore::~HeapMemBufferStore() { /* dtor of index table -> de-allocation here */ }
@ -427,6 +202,7 @@ namespace mem {
/* ==== Implementation of the BufferProvider interface ==== */
/** @note pre-anouncement is ignored */
uint
HeapMemBufferStore::prepareBuffers (HashVal, uint numBuffers, size_t)
{
@ -434,6 +210,7 @@ namespace mem {
}
/** API: create a new buffer allocation */
BuffAlloc
HeapMemBufferStore::provideBuffer (HashVal, size_t buffSiz, LocalTag specifics, int64_t)
{
@ -442,6 +219,7 @@ namespace mem {
}
/** API: mark a buffer as _emitted_ */
void
HeapMemBufferStore::mark_emitted (HashVal, BuffAlloc storageSlot)
{
@ -457,7 +235,7 @@ namespace mem {
}
/** mark a buffer as officially discarded */
/** API: mark a buffer as officially discarded */
void
HeapMemBufferStore::detachBuffer (HashVal, BuffAlloc storageSlot)
{
@ -473,68 +251,4 @@ namespace mem {
}
/* ==== Implementation details ==== */
#if false ////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : this additional tracking API is obsolete and need to be removed
size_t
HeapMemBufferStore::emittedCnt() const
{
return outSeq_.size();
}
void
HeapMemBufferStore::markAllEmitted()
{
for (auto& [_, blockPool] : *pool_)
blockPool.discard();
}
HeapMemBufferStore::Block&
HeapMemBufferStore::access_emitted (uint bufferID)
{
if (!withinOutputSequence (bufferID))
return emptyPlaceholder; ////////////////////////////////TICKET #856
else
return outSeq_[bufferID];
}
#endif ////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (End) obsoleted API
bool
HeapMemBufferStore::withinOutputSequence (uint bufferID) const
{
if (bufferID >= MAX_BUFFERS)
throw err::Fatal ("hardwired internal limit for test buffers exceeded");
return bufferID < outSeq_.size();
}
HeapMemBufferStore::BlockPool&
HeapMemBufferStore::getBlockPoolFor (size_t buffSiz, HashVal typeID)
{
BlockPool& pool = (*pool_)[typeID];
if (not pool)
pool.initialise (buffSiz);
return pool;
}
HeapMemBufferStore::Block*
HeapMemBufferStore::locateBlock (size_t buffSiz, HashVal typeID, void* storage)
{
BlockPool& pool = getBlockPoolFor (buffSiz, typeID);
Block* block4buffer = pool.find (storage); ////////////////////////////////TICKET #856
return block4buffer? block4buffer
: searchInOutSeqeuence (storage);
}
HeapMemBufferStore::Block*
HeapMemBufferStore::searchInOutSeqeuence (void* blockLocation)
{
return pick_Block_by_storage (outSeq_, blockLocation); ////////////////////////////////TICKET #856
}
}} // namespace vault::mem

View file

@ -12,8 +12,8 @@
*/
/** @file heap-mem-provider.hpp
** Dummy engine::BufferProvider storage implementation, for sake of conceptual
** demonstration and unit testing. This BufferProvider implementation is notably straight forward,
** Dummy engine::BufferProvider BufferStore implementation, for sake of conceptual
** demonstration and unit testing. Notably this buffer allocator implementation is straight forward,
** if not outright silly: it happily claims more and more heap blocks and never releases any memory
** dynamically. This both demonstrates the simplest possible implementation of storage handling, and
** allows to investigate additional tracking status flags for each allocated block after the fact.
@ -21,33 +21,22 @@
** @see DiagnosticOutputSlot
** @see DiagnosticBufferProvider
** @see buffer-provider-protocol-test.cpp
** @see engine-ctx.hpp
** @see naive-buffer-setup.hpp
*/
#ifndef VAULT_MEM_HEAP_MEM_BUFFER_STORE_H
#define VAULT_MEM_HEAP_MEM_BUFFER_STORE_H
#include "lib/hash-value.h"
#include "vault/mem/buffer-provider-setup.hpp"
#include "lib/scoped-ptrvect.hpp"
#include <unordered_map>
#include <memory>
namespace std {
/// Partial specializations for pointer types.
template<typename _Tp>
struct hash<_Tp*> : public hash_HIDDEN<_Tp*> { };
}
namespace vault {
namespace mem {
using std::unique_ptr;
using lib::ScopedPtrVect;
using lib::HashVal;
/**
* Naive demo implementation of the BufferProvider::BufferStore.
@ -65,15 +54,8 @@ namespace mem {
using Index = std::unordered_map<Buff*,Alloc>;
Index allocIdx_;
public: /////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : looks like I'll re-implement the storage from scratch, since »tracking« is no longer required here
class Block;
class BlockPool;
private:
using PoolTable = std::unordered_map<HashVal,BlockPool>;
unique_ptr<PoolTable> pool_;
ScopedPtrVect<Block> outSeq_;
public:
/* === BufferStore interface === */
uint prepareBuffers (HashVal typeID, uint cnt, size_t) override;
@ -84,23 +66,6 @@ namespace mem {
public:
~HeapMemBufferStore();
HeapMemBufferStore();
#if false ////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : this additional tracking API is obsolete and need to be removed
size_t emittedCnt() const;
Block& access_emitted (uint bufferID);
template<typename TY>
TY& accessAs (uint bufferID);
void markAllEmitted();
#endif ////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (End) obsoleted API
private:
bool withinOutputSequence (uint bufferID) const;
BlockPool& getBlockPoolFor (size_t,HashVal typeID);
Block* locateBlock (size_t,HashVal typeID, void*);
Block* searchInOutSeqeuence (void* storage);
};