lumiera_documentation/tests/steam/play/diagnostic-output-slot.hpp
Ichthyostega cc9dbd8a40 BufferProvider: relocate TestFrame and dummy-hash computations
These are generic vehicles to verify computation structures.
Since they are in no way related to our models, they belong into »Vault« layer

Furthermore this reorganisation of test support components is prerequisite
to also move further generic facilities down into the Vault
2026-03-07 23:02:35 +01:00

443 lines
14 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.

/*
DIAGNOSTIC-OUTPUT-SLOT.hpp - helper for testing against the OutputSlot interface
Copyright (C)
2011, 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 diagnostic-output-slot.hpp
** An facility for writing unit-tests against the OutputSlot interface.
**
** @see output-slot-protocol-test.cpp
*/
#ifndef STEAM_PLAY_DIAGNOSTIC_OUTPUT_SLOT_H
#define STEAM_PLAY_DIAGNOSTIC_OUTPUT_SLOT_H
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "include/logging.h"
#include "steam/play/output-slot.hpp"
#include "steam/play/output-slot-connection.hpp"
#include "steam/asset/meta/time-grid.hpp"
#include "steam/engine/buffhandle.hpp"
#include "steam/engine/naive-buffer-setup.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/scoped-ptrvect.hpp"
#include "lib/iter-source.hpp"
#include "lib/symbol.hpp"
#include "lib/util.hpp"
#include "vessel/advice.hpp"
#include "test/test-frame.hpp"
//#include "lib/sync.hpp"
//#include <string>
//#include <vector>
#include <unordered_set>
#include <memory>
namespace steam {
namespace play {
//using std::string;
using lib::Symbol;
using lib::HashVal;
using util::unConst;
using util::contains;
using test::TestFrame;
using lib::time::FrameRate;
using steam::asset::meta::PGrid;
using steam::asset::meta::TimeGrid;
using steam::engine::BuffDescr;
using steam::engine::NaiveBufferSetup;
//using std::vector;
using std::shared_ptr;
namespace { // diagnostics & internals....
inline PGrid
getTestTimeGrid()
{
Symbol gridID("DiagnosticOutputSlot-buffer-grid");
vessel::advice::Request<PGrid> query4grid(gridID) ;
PGrid testGrid25 = query4grid.getAdvice();
if (!testGrid25)
testGrid25 = TimeGrid::build (gridID, FrameRate::PAL);
ENSURE (testGrid25);
return testGrid25;
}
}
/**
* Diagnostic output connection for a single channel,
* allowing to track generated frames and verify
* the processing protocol for output buffers.
*/
class TrackingInMemoryBlockSequence
: public OutputSlot::Connection
, util::NonCopyable
{
typedef std::unordered_set<FrameID> FrameTrackingInfo;
NaiveBufferSetup buffProvider_;
BuffDescr bufferType_;
FrameTrackingInfo frameTrackingIndex_;
PGrid frameGrid_;
bool closed_;
BuffHandle
trackFrame (FrameID frameNr, BuffHandle const& newBuffer)
{
TRACE (test, "Con=%p : track buffer %zu for frame-#%lu"
, this, HashVal(newBuffer), frameNr);
REQUIRE (!contains (frameTrackingIndex_,frameNr),
"attempt to lock already used frame %lu", frameNr);
frameTrackingIndex_.insert (frameNr);
return newBuffer;
}
TimeValue
deadlineFor (FrameID frameNr)
{
// a real world implementation
// would offset by a latency here
return frameGrid_->timeOf (frameNr);
}
/* === Connection API === */
BuffHandle
claimBufferFor(FrameID frameNr)
{
TRACE (test, "Con=%p : lock buffer for frame-#%lu", this, frameNr);
REQUIRE (!closed_);
return trackFrame (frameNr,
buffProvider_.lockBuffer (bufferType_));
}
bool
isTimely (FrameID frameNr, TimeValue currentTime)
{
TRACE (test, "Con=%p : timely? frame-#%lu"
, this, frameNr);
if (Time::ANYTIME == currentTime)
return true;
else
return currentTime < deadlineFor (frameNr);
}
void
transfer (BuffHandle & filledBuffer)
{
TRACE (test, "Con=%p : transfer buffer %zu"
, this, HashVal(filledBuffer));
REQUIRE (!closed_);
pushout (filledBuffer);
}
void
pushout (BuffHandle & data4output)
{
REQUIRE (!closed_);
buffProvider_.emitBuffer (data4output);
buffProvider_.releaseBuffer(data4output);
}
void
discard (BuffHandle & superseededData)
{
REQUIRE (!closed_);
buffProvider_.releaseBuffer (superseededData);
}
void
shutDown ()
{
closed_ = true;
}
public:
TrackingInMemoryBlockSequence()
: buffProvider_()
, bufferType_(buffProvider_.getDescriptor<TestFrame>())
, frameTrackingIndex_()
, frameGrid_(getTestTimeGrid()) /////////////TODO should rather pass that in as part of a "timings" definition
, closed_(false)
{
INFO (engine_dbg, "building in-memory diagnostic output sequence (at %p)", this);
}
virtual
~TrackingInMemoryBlockSequence()
{
INFO (engine_dbg, "releasing diagnostic output sequence (at %p)", this);
}
/* === Diagnostic API === */
TestFrame const *
accessEmittedFrame (uint frameNr) const
{
UNIMPLEMENTED ("suitable diagnostic API");
// if (frameNr < buffProvider_.emittedCnt()) ///////////////////////////////OOO provide suitable diagnostic API!
// return & accessFrame(frameNr);
// else
// return 0; ////////////////////////////////TICKET #856
}
#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code to disentangle BufferProvider implementation
diagn::Block const *
accessEmittedBuffer (uint bufferNr) const
{
UNIMPLEMENTED ("suitable diagnostic API");
// if (bufferNr < buffProvider_.emittedCnt()) ///////////////////////////////OOO provide suitable diagnostic API!
// return & accessBlock(bufferNr);
// else
// return 0;
}
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code
bool
wasAllocated (uint frameNr) const
{
TRACE (test, "query wasAllocated. Con=%p", this);
return contains (frameTrackingIndex_, frameNr);
}
private:
TestFrame const&
accessFrame (uint frameNr) const
{
UNIMPLEMENTED ("suitable diagnostic API");
// return unConst(this)->buffProvider_.accessAs<TestFrame> (frameNr); ///////////////////////////////OOO provide suitable diagnostic API!
}
#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code to disentangle BufferProvider implementation
diagn::Block const&
accessBlock (uint bufferNr) const
{
UNIMPLEMENTED ("suitable diagnostic API");
// return unConst(this)->buffProvider_.access_emitted (bufferNr); ///////////////////////////////OOO provide suitable diagnostic API!
}
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code
};
/****************************************************************//**
* Helper for unit tests: Mock output sink.
* Complete implementation of the OutputSlot interface, with some
* additional stipulations to support unit testing.
* - the implementation uses a special protocol output buffer,
* which stores each "frame" in memory for later investigation
* - the output data in the buffers handed over from client
* actually hold an TestFrame instance
* - the maximum number of channels and the maximum number
* of acceptable frames is limited to 5 and 100.
* @warning any Captured (test) data from all individual instances
* remains in memory until shutdown of the current executable
*/
class DiagnosticOutputSlot
: public OutputSlotImplBase
{
static const uint MAX_CHANNELS = 5;
/** @note a real OutputSlot implementation
* would rely on some kind of embedded
* configuration here */
uint
getOutputChannelCount()
{
return MAX_CHANNELS;
}
/**
* Special diagnostic connection state implementation,
* establishing diagnostic output connections for each channel,
* thus allowing to verify the handling of individual buffers
*/
class SimulatedOutputSequences
: public ConnectionManager<TrackingInMemoryBlockSequence>
{
typedef ConnectionManager<TrackingInMemoryBlockSequence> _Base;
void
buildConnection(ConnectionStorage storage)
{
storage.create<TrackingInMemoryBlockSequence>();
}
public:
SimulatedOutputSequences (uint numChannels)
: _Base(numChannels)
{
init();
}
};
/** hook into the OutputSlot frontend */
ConnectionState*
buildState()
{
return new SimulatedOutputSequences(
getOutputChannelCount());
}
/** @internal is self-managed and non-copyable.
* Clients use #build() to get an instance */
DiagnosticOutputSlot() { }
/** @internal access the implementation object
* representing a single stream connection
*/
TrackingInMemoryBlockSequence const&
accessSequence (uint channel)
{
REQUIRE (!isFree(), "diagnostic OutputSlot not (yet) connected");
REQUIRE (channel <= getOutputChannelCount());
return static_cast<TrackingInMemoryBlockSequence&> (state_->access (channel));
}
public:
/** build a new Diagnostic Output Slot instance,
* discard the existing one. Use the static query API
* for investigating collected data. */
static OutputSlot&
build()
{
static lib::ScopedPtrVect<OutputSlot> diagnosticSlots;
return diagnosticSlots.manage(new DiagnosticOutputSlot);
}
static DiagnosticOutputSlot&
access (OutputSlot& to_investigate)
{
return dynamic_cast<DiagnosticOutputSlot&> (to_investigate);
}
/* === diagnostics API === */
/**
* diagnostic facility to verify test data frames
* written to this Test/Dummy "output". It exposes
* the emitted Data as a sequence of TestFrame objects.
*/
class OutputFramesLog
: public lib::IterSource<const TestFrame>
, util::NonCopyable
{
TrackingInMemoryBlockSequence const& outSeq_;
uint currentFrame_;
virtual Pos //////////////////////////////////////////////TICKET #1125 : this API should use three control functions, similar to IterStateWrapper
firstResult () override
{
REQUIRE (0 == currentFrame_);
return outSeq_.accessEmittedFrame (currentFrame_);
}
virtual void
nextResult (Pos& pos) override
{
++currentFrame_;
pos = outSeq_.accessEmittedFrame(currentFrame_);
}
public:
OutputFramesLog (TrackingInMemoryBlockSequence const& bs)
: outSeq_(bs)
, currentFrame_(0)
{ }
};
typedef OutputFramesLog::iterator OutFrames;
OutFrames
getChannel (uint channel)
{
REQUIRE (channel < MAX_CHANNELS);
return OutputFramesLog::build(
new OutputFramesLog (
accessSequence(channel)));
}
bool
frame_was_allocated (uint channel, FrameID nominalFrame)
{
return accessSequence(channel)
.wasAllocated(nominalFrame);
}
bool
output_was_emitted (uint channel, FrameID outputFrame)
{
#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code to disentangle BufferProvider implementation
diagn::Block const *block = accessSequence(channel)
.accessEmittedBuffer(outputFrame);
return block
and block->was_used();
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code
}
bool
output_was_closed (uint channel, FrameID outputFrame)
{
#if false //////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : disabled code to disentangle BufferProvider implementation
diagn::Block const *block = accessSequence(channel)
.accessEmittedBuffer(outputFrame);
return block
and block->was_closed();
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1410 : (end) disabled code
}
};
}} // namespace steam::play
#endif