* Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
431 lines
12 KiB
C++
431 lines
12 KiB
C++
/*
|
||
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/tracking-heap-block-provider.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 "common/advice.hpp"
|
||
#include "steam/engine/testframe.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 util::unConst;
|
||
using util::contains;
|
||
using lib::time::FrameRate;
|
||
using steam::asset::meta::PGrid;
|
||
using steam::asset::meta::TimeGrid;
|
||
using steam::engine::BuffDescr;
|
||
using steam::engine::test::TestFrame;
|
||
using steam::engine::TrackingHeapBlockProvider;
|
||
namespace diagn = steam::engine::diagn;
|
||
|
||
//using std::vector;
|
||
using std::shared_ptr;
|
||
|
||
namespace { // diagnostics & internals....
|
||
|
||
inline PGrid
|
||
getTestTimeGrid()
|
||
{
|
||
Symbol gridID("DiagnosticOutputSlot-buffer-grid");
|
||
lumiera::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;
|
||
|
||
|
||
TrackingHeapBlockProvider 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, newBuffer.entryID(), 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 const& filledBuffer)
|
||
{
|
||
TRACE (test, "Con=%p : transfer buffer %zu"
|
||
, this, filledBuffer.entryID());
|
||
REQUIRE (!closed_);
|
||
|
||
pushout (filledBuffer);
|
||
}
|
||
|
||
void
|
||
pushout (BuffHandle const& data4output)
|
||
{
|
||
REQUIRE (!closed_);
|
||
buffProvider_.emitBuffer (data4output);
|
||
buffProvider_.releaseBuffer(data4output);
|
||
}
|
||
|
||
void
|
||
discard (BuffHandle const& 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
|
||
{
|
||
if (frameNr < buffProvider_.emittedCnt())
|
||
return & accessFrame(frameNr);
|
||
else
|
||
return 0; ////////////////////////////////TICKET #856
|
||
}
|
||
|
||
diagn::Block const *
|
||
accessEmittedBuffer (uint bufferNr) const
|
||
{
|
||
if (bufferNr < buffProvider_.emittedCnt())
|
||
return & accessBlock(bufferNr);
|
||
else
|
||
return 0;
|
||
}
|
||
|
||
bool
|
||
wasAllocated (uint frameNr) const
|
||
{
|
||
TRACE (test, "query wasAllocated. Con=%p", this);
|
||
|
||
return contains (frameTrackingIndex_, frameNr);
|
||
}
|
||
|
||
private:
|
||
TestFrame const&
|
||
accessFrame (uint frameNr) const
|
||
{
|
||
return unConst(this)->buffProvider_.accessAs<TestFrame> (frameNr);
|
||
}
|
||
|
||
diagn::Block const&
|
||
accessBlock (uint bufferNr) const
|
||
{
|
||
return unConst(this)->buffProvider_.access_emitted (bufferNr);
|
||
}
|
||
};
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/****************************************************************//**
|
||
* 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)
|
||
{
|
||
diagn::Block const *block = accessSequence(channel)
|
||
.accessEmittedBuffer(outputFrame);
|
||
return block
|
||
&& block->was_used();
|
||
}
|
||
|
||
|
||
bool
|
||
output_was_closed (uint channel, FrameID outputFrame)
|
||
{
|
||
diagn::Block const *block = accessSequence(channel)
|
||
.accessEmittedBuffer(outputFrame);
|
||
return block
|
||
&& block->was_closed();
|
||
}
|
||
};
|
||
|
||
|
||
|
||
}} // namespace steam::play
|
||
#endif
|