lumiera_documentation/tests/steam/play/dummy-builder-context.hpp
Ichthyostega ce40b83bb6 Establish DataSink identity and fix remaining tests
The test suite is now **green** again.

The design rework and clean-up of unnecessary test-driven functionality
in the `DiagnosticBufferProvider` and `DiagnosticOutputSlot` required
to break some very old test-setup, using fake instances of the relevant
context facilities. This setup however was used by the newer family
of test support facilities, notable the `MockJob`, `MockSegmentation`
and `MockDispatcher`. These required a set of `DataSink` handles to be
present (even while they are not used currently).

This could be fixed easily now, relying on the simplified internal structure
of the `OutputSlot`: by re-arranging the types, the implied identity of the
`DataSink` handles could be made explicit, allowing for equality comparison.

__Remark__: even while `DataSink` with only a single abstracted function,
using function notation turned out to be rather confusing at usage site.
Naming this function as `lockFrame(frameNo)` clarifies its purpose nicely.
That call is effectively abstracted by means of the ''buffer type'',
relying on the VTable in the `BufferProvider` implementation.
2026-06-01 23:55:10 +02:00

296 lines
11 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.

/*
DUMMY-BUILDER-CONTEXT.hpp - fake simulated builder result for player tests
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 dummy-builder-context.hpp
** Simulated result of a builder run for test purposes, without actual session and model content.
** Integration tests for the render engine or the player have to deal with a lot of prerequisites,
** since both an external environment and actual content form the Session is required to start any
** processing. Effectively the complete core application is required — while in fact the engine is
** built in an abstracted way and does not need to know much about the rest of the application.
** Thus, as a remedy, it is possible to establish a _faked context,_ exposing just those interfaces
** used by the engine for processing. As additional benefit, dedicated test operations can be rigged
** and verification routines can be used in place of actual media output.
**
** This faked builder context provides a selection of consistent media element IDs with sensible
** properties and type markers, plus the attachment points for a simulated model structure, as
** if generated from an actual session model by a real Builder run.
** - a set of ModelPort elements
** - a set of DataSink handles, as if an actual playback connection has been established
** @todo 5/2023 add more elements as needed for test driven integration of Player and engine
** @todo this was invented in 2012 -- as an act of desperation, since everything to build upon
** seemed to evade my grip. My hope at that time was so somehow _bootstrap_ an explorative
** design in order to get away from that top-down reasoning, that seemed to lead nowhere.
** However, work on the player had to be postponed to care for the GUI first; meanwhile
** I follow a similar idea, relying on »vertical integration slices«
** @deprecated 3/2026 not sure if we need this henceforth...
** It might be useful though in case we consider to build some kind of
** _component integration test_ -- yet for this to work, we'd need
** - a fake calculation
** - a fake OutputSlot
** @todo 5/2026 now I have beefed up the OutputSlot so that it can receive
** two feeds with an arbitrary number of frames. Still not sure if it's needed;
** however it should be noted that the MockDispatcher relies on the DummyPlayConnection,
** which in turn uses SimulatedBuilderContext, mostly to get _just some model ports_.
**
** @see mock-dispatcher.hpp
** @see JobPlanningPipeline_test
*/
#ifndef STEAM_PLAY_DUMMY_BUILDER_CONTEXT_H
#define STEAM_PLAY_DUMMY_BUILDER_CONTEXT_H
#include "lib/error.hpp"
#include "lib/format-string.hpp"
#include "steam/mobject/model-port.hpp"
#include "steam/fixture/model-port-registry.hpp"
#include "vault/out/output-slot-connection.hpp"
#include "steam/play/output-manager.hpp"
#include "steam/asset/timeline.hpp"
#include "steam/asset/pipe.hpp"
#include "vessel/query.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/iter-source.hpp"
#include "lib/nocopy.hpp"
#include "lib/util.hpp"
#include <vector>
#include <deque>
#include <array>
namespace steam{
namespace play {
namespace test {
namespace err = lumiera::error;
using fixture::ModelPortRegistry;
using vault::mem::Buff;
using vault::mem::BuffHandle;
using vault::out::FrameID;
using lib::time::TimeValue;
using util::_Fmt;
using DataSink = vault::out::OutputSlot::DataSink;
/**
* A placeholder/dummy OutputSlot that captures all
* delivered output data into heap-allocated memory.
* @todo 5/2026 it can now be created and allocates a fixed layout
* of buffer storage, which implies it should not be copied
* and is thus used differently than a real OutputSlot;
* should reconsider what is actually needed here!
* @warning this used to be placeholder code, and now it was adapted
* to store frames, yet this new functionality is untested as of 5/2026
*/
struct FakeOutput
: OutputSlot
, util::NonCopyable
{
static constexpr uint NUM_FEEDS = 2;
static constexpr bool SINGLE_THREADED_TEST = true;
using DummyDat = uint64_t;
static constexpr size_t DUMMY_BUFF_SIZ = 1_KiB * sizeof(DummyDat);
using DummyBuff = std::array<DummyDat, DUMMY_BUFF_SIZ>;
using DummyBuffSeq = std::deque<DummyBuff>;
/**
* @todo 5/2026 quick-n-dirty placeholder code
* to expose some output buffers and provide
* the corresponding DataSink handles
*/
class DummyOutputConnection
: public vault::out::OutputSlot::Connection
{
DummyBuffSeq& buffers_;
size_t getBufferSize() const override { return DUMMY_BUFF_SIZ; }
Buff* claimBufferFor (FrameID) override;
void publish (Buff*) override { /* fake "publish buffer content" */ }
void release (Buff*) override { /* fake "complete buffer cycle" */ }
void shutDown () override { /* fake "shutDown() Connection" */ }
public:
~DummyOutputConnection();
DummyOutputConnection (DummyBuffSeq& buffers)
: buffers_{buffers}
{ }
// OutputSlot::Connection is non-copyable
};
using Connections = lib::ScopedCollection<DummyOutputConnection>;
using MockConnectState = OutputSlot::AllocState<DummyOutputConnection, SINGLE_THREADED_TEST>;
using OutBuffs = std::array<DummyBuffSeq, NUM_FEEDS>;
OutBuffs buffers_{};
FakeOutput()
: OutputSlot{std::make_unique<MockConnectState> (NUM_FEEDS
,[&,i=0](auto& storage) mutable
{
storage.template create<DummyOutputConnection> (buffers_[i++]);
})}
{ }
};
inline Buff*
FakeOutput::DummyOutputConnection::claimBufferFor (FrameID fra)
{
REQUIRE (fra >= 0);
if (uint64_t(fra) >= buffers_.size())
buffers_.resize(fra+10);
return reinterpret_cast<Buff*> (& buffers_[fra]);
}
using asset::Pipe;
using asset::PPipe;
using asset::Struct;
using asset::Timeline;
using asset::PTimeline;
using mobject::ModelPort;
using fixture::ModelPortRegistry;
using util::contains;
using vessel::Query;
using PID = asset::ID<Pipe>;
using TID = asset::ID<Struct>;
using ModelPorts = lib::IterSource<mobject::ModelPort>::iterator;
using DummyOutputLink = std::pair<mobject::ModelPort, DataSink>;
inline PID
getPipe (string id)
{
return Pipe::query ("id("+id+")");
}
inline TID
getTimeline (string id)
{
return asset::Struct::retrieve (Query<Timeline> ("id("+id+")"))->getID();
}
const uint NUMBER_OF_PORTS = 2;
const string namePortA("bus-A");
const string namePortB("bus-B");
/**
* helper for dummy render engine:
* Simulate the result of a build process,
* without actually running the builder.
* Produces some mock pipes, model ports etc.
*/
struct SimulatedBuilderContext
{
ModelPortRegistry registry_;
ModelPortRegistry* existingRegistry_;
std::vector<ModelPort> modelPorts_;
std::vector<DataSink> dataSinks_;
FakeOutput fakeOutput_;
/** setup */
SimulatedBuilderContext()
: registry_()
, existingRegistry_(ModelPortRegistry::setActiveInstance (registry_))
{
performMockBuild();
}
/** tear-down */
~SimulatedBuilderContext()
{
if (existingRegistry_)
ModelPortRegistry::setActiveInstance (*existingRegistry_);
else
ModelPortRegistry::shutdown();
}
void
performMockBuild()
{
PID pipeA = getPipe (namePortA);
PID pipeB = getPipe (namePortB);
TID someTimeline = getTimeline ("dummy_Timeline");
// start out with defining some new model ports......
registry_.definePort (pipeA, someTimeline);
registry_.definePort (pipeB, someTimeline);
registry_.commit();
// now "bus-A" and "bus-B" are known as model ports
modelPorts_.push_back (ModelPort(pipeA));
modelPorts_.push_back (ModelPort(pipeB));
// prepare corresponding placeholder DataSink (a fake active output connection)
dataSinks_.clear();
lib::explore(fakeOutput_.getOpenedSinks())
.effuse (dataSinks_);
}
ModelPorts
getAllModelPorts()
{
return lib::iter_source::eachEntry (modelPorts_.begin(), modelPorts_.end());
}
DummyOutputLink
getModelPort (uint index)
{
REQUIRE (index < modelPorts_.size());
return {modelPorts_[index]
,dataSinks_[index]
};
}
/**
* Access a generated output frame.
* @remark FakeOutput maintains Deque-Storage for each feed.
* @tparam X data type to assume as content of the frame (only checked for size)
* @param feed number of the output feed to access
* @param frame frame numer within the output feed
*/
template<typename X>
X&
accessOutputBufferAs (uint feed, FrameID frame)
{
static_assert (sizeof(X) <= FakeOutput::DUMMY_BUFF_SIZ
,"desired data type would not fit into FakeOutput buffer");
if (feed >= FakeOutput::NUM_FEEDS)
throw err::Invalid{_Fmt{"Feed #%i > %i allowed"}
% feed % (FakeOutput::NUM_FEEDS-1)};
auto availFrames = fakeOutput_.buffers_[feed].size();
if (frame >= availFrames)
throw err::Invalid{_Fmt{"Frame #%02i > %02i available frames"}
% frame % availFrames};
return reinterpret_cast<X&> (fakeOutput_.buffers_[feed][frame]);
}
};
}}} // namespace steam::play::test
#endif /*STEAM_PLAY_DUMMY_BUILDER_CONTEXT_H*/