2023-04-17 17:10:53 +02:00
/*
2023-05-24 03:38:12 +02:00
MOCK - DISPATCHER . hpp - test scaffolding to verify render job planning and dispatch
2023-04-17 17:10:53 +02:00
Copyright ( C ) Lumiera . org
2023 , Hermann Vosseler < Ichthyostega @ web . de >
This program 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 .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
2023-05-24 03:38:12 +02:00
/** @file mock-dispatcher.hpp
* * Mock data structures to support implementation testing of render job
2023-06-13 20:23:33 +02:00
* * planning and frame dispatch . This specifically rigged test setup allows to
* * investigate and verify designated functionality in isolation , without backing
* * by the actual render engine and low - level - Model implementation .
* * - a MockJob is a render Job , wired to a DummyFunctor , which does nothing ,
* * but records any invocation into an internal diagnostics Map .
* * - MockJobTicket is a builder / adapter on top of the actual steam : : engine : : JobTicket ,
* * allowing to generate simple JobTicket instances with an embedded ExitNode and
* * a ( configurable ) pipelineID . From this setup , » mock jobs « can be generated ,
* * which use the MockJob functor and thus record any invocation without performing
* * actual work . The internal connection to the MockJobTicket can then be verified .
2023-05-24 03:38:12 +02:00
* * - MockSegmentation is a mocked variant of the » Segmentation « datastructure ,
* * which forms the backbone of the Fixture and is the top - level attachment
2023-06-13 20:23:33 +02:00
* * point for the » low - level - Model « ( the render nodes network ) . It can be
* * configured with a test specification of ExitNode ( s ) defined by a
* * GenNode tree , and defining Segments of the timeline and prerequisites .
* * - finally , the MockDispatcher combines all these facilities to emulate
* * frame dispatch from the Fixture without actually using any data model .
* * Similar to MockSegmentation , a GenNode - based specification is used .
* *
2023-05-24 03:38:12 +02:00
* * @ remark in spring 2023 , this setup was created as a means to define and
* * then build the actual implementation of frame dispatch and scheduling .
* * @ see MockSupport_test
2023-04-17 17:10:53 +02:00
*/
2023-05-24 03:38:12 +02:00
# ifndef STEAM_ENGINE_TEST_MOCK_DISPATCHER_H
# define STEAM_ENGINE_TEST_MOCK_DISPATCHER_H
2023-04-17 17:10:53 +02:00
2023-05-24 03:38:12 +02:00
# include "lib/test/test-helper.hpp"
2023-06-02 02:31:34 +02:00
# include "steam/play/dummy-play-connection.hpp"
2023-06-07 04:03:00 +02:00
# include "steam/fixture/node-graph-attachment.hpp"
2023-04-25 18:27:16 +02:00
# include "steam/fixture/segmentation.hpp"
2023-04-17 17:10:53 +02:00
# include "steam/mobject/model-port.hpp"
# include "steam/engine/dispatcher.hpp"
2023-04-18 20:02:36 +02:00
# include "steam/engine/job-ticket.hpp"
2023-05-24 03:38:12 +02:00
# include "vault/engine/job.h"
# include "vault/real-clock.hpp"
2023-06-11 04:37:38 +02:00
# include "lib/allocator-handle.hpp"
2023-04-20 23:55:02 +02:00
# include "lib/time/timevalue.hpp"
2023-04-25 13:40:20 +02:00
# include "lib/diff/gen-node.hpp"
2023-05-23 06:40:18 +02:00
# include "lib/linked-elements.hpp"
2023-04-20 18:53:17 +02:00
# include "lib/itertools.hpp"
2023-05-24 03:38:12 +02:00
# include "lib/depend.hpp"
2023-06-13 03:47:42 +02:00
# include "lib/util.hpp"
2023-04-17 17:10:53 +02:00
2023-04-20 23:55:02 +02:00
# include <tuple>
2023-06-13 03:47:42 +02:00
# include <map>
2023-04-17 17:10:53 +02:00
namespace steam {
namespace engine {
namespace test {
2023-05-24 03:38:12 +02:00
using std : : make_tuple ;
2023-04-25 13:40:20 +02:00
using lib : : diff : : GenNode ;
using lib : : diff : : MakeRec ;
2023-04-20 23:55:02 +02:00
using lib : : time : : TimeValue ;
2023-05-02 04:16:39 +02:00
using lib : : time : : Time ;
2023-04-30 02:18:56 +02:00
using lib : : HashVal ;
2023-06-13 03:47:42 +02:00
using util : : isnil ;
using util : : isSameObject ;
2023-04-25 18:27:16 +02:00
using fixture : : Segmentation ;
2023-06-16 04:09:38 +02:00
using vault : : RealClock ;
2023-06-13 20:23:33 +02:00
using vault : : engine : : Job ;
using vault : : engine : : JobClosure ;
2023-04-17 17:10:53 +02:00
2023-06-13 20:23:33 +02:00
/**
* Mock setup for a render Job with NO action but built - in diagnostics .
* Each invocation of such a MockJob will be logged internally
* and can be investigated and verified afterwards .
*/
class MockJob
: public Job
2023-05-01 17:02:11 +02:00
{
2023-06-13 20:23:33 +02:00
static Job build ( ) ; ///< uses random job definition values
static Job build ( Time nominalTime , int additionalKey ) ;
public :
MockJob ( )
: Job { build ( ) }
{ }
MockJob ( Time nominalTime , int additionalKey )
: Job { build ( nominalTime , additionalKey ) }
{ }
static bool was_invoked ( Job const & job ) ;
static Time invocationTime ( Job const & job ) ;
static Time invocationNominalTime ( Job const & job ) ;
static int invocationAdditionalKey ( Job const & job ) ;
static bool isNopJob ( Job const & ) ;
static JobClosure & getFunctor ( ) ;
} ;
2023-06-12 19:21:14 +02:00
2023-05-24 03:38:12 +02:00
2023-04-17 17:10:53 +02:00
/**
2023-06-13 20:23:33 +02:00
* Mock setup for a JobTicket to generate dummy render Job invocations .
2023-05-24 03:38:12 +02:00
* Implemented as subclass , it provides a specification DSL for tests ,
* and is able to probe some otherwise opaque internals of JobTicket .
* Beyond that , MockJobTicket has the same storage size ; and behaves
* like the regular JobTicket after construction - - but any Job
* created by JobTicket : : createJobFor ( FrameCoord ) will be wired
2023-06-13 20:23:33 +02:00
* with the MockJob functor and can thus be related back to
2023-05-24 03:38:12 +02:00
* the test specification setup .
2023-06-17 03:10:57 +02:00
* @ see JobPlanningPipeline_test
2023-04-17 17:10:53 +02:00
* @ see DispatcherInterface_test
*/
class MockJobTicket
2023-06-11 04:37:38 +02:00
: private lib : : AllocatorHandle < JobTicket >
, public JobTicket
2023-04-17 17:10:53 +02:00
{
2023-06-11 04:37:38 +02:00
auto &
allocator ( )
{
return static_cast < lib : : AllocatorHandle < JobTicket > & > ( * this ) ;
}
2023-06-13 20:23:33 +02:00
/** provide a test specification wired to MockJob */
static ExitNode
defineSimpleSpec ( HashVal seed = 1 + rand ( ) )
{
return ExitNode { seed
, ExitNodes { }
, & MockJob : : getFunctor ( ) } ;
}
2023-04-17 17:10:53 +02:00
public :
2023-04-20 23:55:02 +02:00
MockJobTicket ( )
2023-06-11 04:37:38 +02:00
: JobTicket { defineSimpleSpec ( ) , allocator ( ) }
2023-05-01 17:02:11 +02:00
{ }
MockJobTicket ( HashVal seed )
2023-06-11 04:37:38 +02:00
: JobTicket { defineSimpleSpec ( seed ) , allocator ( ) }
2023-05-01 17:02:11 +02:00
{ }
2023-04-17 17:10:53 +02:00
2023-05-11 22:47:56 +02:00
2023-05-24 03:38:12 +02:00
/* ===== Diagnostics ===== */
2023-04-30 02:18:56 +02:00
bool verify_associated ( Job const & ) const ;
2023-05-01 17:02:11 +02:00
static bool isAssociated ( Job const & , JobTicket const & ) ;
2023-04-17 17:10:53 +02:00
} ;
2023-05-24 03:38:12 +02:00
/**
* Mock setup for a complete Segmentation to emulate the structure
* of the actual fixture , without the need of building a low - level Model .
* MockSegmentation instances can be instantiated directly within the
* test , by passing a test specification in » GenNode « notation to the
* constructor . This specification defines the segments to create
* and allows to associate a marker number , which can later be
2023-06-13 20:23:33 +02:00
* verified from the actual DummyClosure invocation .
2023-05-24 03:38:12 +02:00
* - the ctor accepts a sequence of GenNode elements ,
* each corresponding to a segment to created
* - optionally , attributes " start " and " after " can be defined
* to provide the lib : : time : : Time values of segment start / end
* - in addition , optionally a " mark " attribute can be defined ;
* the given integer number will be " hidden " in the job instance
2023-06-13 20:23:33 +02:00
* hash , and can be [ verified ] ( \ ref MockJob : : invocationAdditionalKey )
2023-05-24 03:38:12 +02:00
* - the _scope_ of each top - level GenNode may hold a sequence of
* nested nodes corresponding to _prerequisite_ JobTicket instances
* - these can in turn hold further nested prerequisites , and so on
* @ see MockSetup_test : : verify_MockSegmentation
*/
2023-04-25 13:40:20 +02:00
class MockSegmentation
: public Segmentation
{
public :
MockSegmentation ( )
2023-04-27 22:30:49 +02:00
: Segmentation { }
2023-04-25 13:40:20 +02:00
{ }
MockSegmentation ( std : : initializer_list < GenNode > specs )
2023-04-27 19:38:37 +02:00
: MockSegmentation { }
2023-04-25 13:40:20 +02:00
{
2023-05-01 17:02:11 +02:00
for ( auto & spec : specs )
{
2023-05-02 04:16:39 +02:00
auto start = spec . retrieveAttribute < Time > ( " start " ) ;
auto after = spec . retrieveAttribute < Time > ( " after " ) ;
2023-06-10 04:52:40 +02:00
Segmentation : : splitSplice ( start , after
, ExitNodes { buildExitNodeFromSpec ( spec ) }
) ;
2023-05-01 17:02:11 +02:00
}
2023-04-25 13:40:20 +02:00
}
2023-05-11 22:47:56 +02:00
2023-05-24 03:38:12 +02:00
2023-06-07 04:03:00 +02:00
ExitNode
buildExitNodeFromSpec ( GenNode const & spec )
{
2023-06-07 17:21:31 +02:00
return ExitNode { buildSeed ( spec )
2023-06-10 04:52:40 +02:00
, buildPrerequisites ( spec )
2023-06-13 20:23:33 +02:00
, & MockJob : : getFunctor ( ) } ;
2023-06-10 04:52:40 +02:00
}
2023-06-07 04:03:00 +02:00
2023-06-13 03:47:42 +02:00
/** @internal helper for MockDispatcher */
void duplicateExitNodeSpec ( uint times ) ;
2023-06-07 04:03:00 +02:00
2023-06-07 17:21:31 +02:00
private : /* ======== Implementation: build fake ExitNodes from test specification ==== */
2023-05-24 03:38:12 +02:00
2023-05-11 22:47:56 +02:00
HashVal
buildSeed ( GenNode const & spec )
{
auto seed = spec . retrieveAttribute < int > ( " mark " ) ;
return seed ? HashVal ( * seed ) : HashVal ( rand ( ) % 1000 ) ;
}
2023-06-07 17:21:31 +02:00
ExitNodes
2023-05-11 22:47:56 +02:00
buildPrerequisites ( GenNode const & spec )
{
2023-06-07 17:21:31 +02:00
ExitNodes prerequisites ;
for ( auto & child : spec . getChildren ( ) )
prerequisites . emplace_back (
buildExitNodeFromSpec ( child ) ) ;
return prerequisites ;
2023-05-11 22:47:56 +02:00
}
2023-04-25 13:40:20 +02:00
} ;
2023-04-17 17:10:53 +02:00
2023-05-24 03:38:12 +02:00
2023-06-13 03:47:42 +02:00
/**
* This is some trickery to allow handling of multiple ModelPort ( s ) in MockDispatcher ;
* actually the code using this mock setup does not need any elaborate differentiation
* of the ExitNodes structure per port , thus the first entry of the existing configuration
* is just duplicated for the given number of further ModelPorts .
* @ warning this manipulation must be done prior to generating any JobTicket
*/
inline void
MockSegmentation : : duplicateExitNodeSpec ( uint times )
{
2023-06-13 20:23:33 +02:00
using Spec = fixture : : NodeGraphAttachment ;
Segmentation : : adaptSpecification ( [ times ] ( Spec const & spec )
{
2023-06-14 04:20:50 +02:00
return Spec { ExitNodes ( times , spec [ 0 ] ) } ;
} ) ; // vector with <times> copies of spec[0]
} // (Warning: use parens, not braces for this ctor...)
2023-06-13 03:47:42 +02:00
2023-04-30 02:18:56 +02:00
/**
* verify the given job instance was actually generated from this JobTicket .
2023-05-24 03:38:12 +02:00
* @ remark this test support function relies on some specific rigging ,
2023-04-30 02:18:56 +02:00
* which typically is prepared by setup of a MockJobTicket .
*/
inline bool
MockJobTicket : : verify_associated ( Job const & job ) const
{
JobFunctor & functor = dynamic_cast < JobFunctor & > ( static_cast < JobClosure & > ( * job . jobClosure ) ) ;
Time nominalTime { TimeValue { job . parameter . nominalTime } } ;
2023-05-01 14:07:21 +02:00
InvocationInstanceID const & invoKey = job . parameter . invoKey ;
2023-04-30 02:18:56 +02:00
return this - > isValid ( )
2023-05-01 14:07:21 +02:00
and this - > verifyInstance ( functor , invoKey , nominalTime ) ;
2023-04-30 02:18:56 +02:00
}
2023-04-17 17:10:53 +02:00
2023-05-01 17:02:11 +02:00
/**
* convenience shortcut to perform [ this test ] ( \ ref MockJobTicket : : verify_associated )
* on arbitrary JobTicket and Job instances .
* @ warning a positive test result however relies on some casting trickery and there is no
* guarantee this test works if the JobTicket was not created from this mock framework .
*/
inline bool
MockJobTicket : : isAssociated ( Job const & job , JobTicket const & ticket )
2023-05-24 03:38:12 +02:00
{ // should work always, since storage is the same
2023-05-01 17:02:11 +02:00
MockJobTicket const & backdoor = static_cast < MockJobTicket const & > ( ticket ) ;
return backdoor . verify_associated ( job ) ;
}
2023-04-17 17:10:53 +02:00
2023-06-02 02:31:34 +02:00
2023-06-13 20:23:33 +02:00
namespace { // used internally by MockDispatcher....
using play : : test : : ModelPorts ;
using play : : test : : PlayTestFrames_Strategy ;
using DummyPlaybackSetup = play : : test : : DummyPlayConnection < PlayTestFrames_Strategy > ;
}
/**
* A mocked frame Dispatcher setup without any backing model .
* Instantiating such a MockDispatcher will automatically create some fake
* model structures and some ModelPort and DisplaySink handles ( and thereby
* push aside and shadow any existing ModelPort registry ) .
*
* The configuration is similar to MockSegmentation , using a test spec
* given as GenNode - tree to define Segments of the timeline and possibly
* pipeline - IDs and prerequisites . One notable difference is that here
* the default ctor always creates a single Segment covering the whole
* time axis , and that the ExitNode specification is automatically
* duplicated for all faked ModelPort ( s ) .
*/
2023-06-02 02:31:34 +02:00
class MockDispatcher
: public Dispatcher
{
DummyPlaybackSetup dummySetup_ ;
MockSegmentation mockSeg_ ;
2023-06-13 03:47:42 +02:00
using PortIdxMap = std : : map < ModelPort , size_t > ;
const PortIdxMap portIdx_ ;
2023-06-02 02:31:34 +02:00
2023-06-12 23:15:39 +02:00
public :
2023-06-13 20:23:33 +02:00
/* == mock implementation of the Dispatcher interface == */
2023-06-02 02:31:34 +02:00
2023-06-18 03:50:48 +02:00
# if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : obsoleted by rework of Dispatcher-Pipeline
2023-06-02 02:31:34 +02:00
FrameCoord
2023-06-12 19:21:14 +02:00
locateRelative ( FrameCoord const & , FrameCnt frameOffset ) override
2023-06-02 02:31:34 +02:00
{
UNIMPLEMENTED ( " dummy implementation of the core dispatch operation " ) ;
}
bool
2023-06-12 19:21:14 +02:00
isEndOfChunk ( FrameCnt , ModelPort port ) override
2023-06-02 02:31:34 +02:00
{
UNIMPLEMENTED ( " determine when to finish a planning chunk " ) ;
}
2023-06-18 03:50:48 +02:00
# endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1301 : obsoleted by rework of Dispatcher-Pipeline
2023-06-13 03:47:42 +02:00
2023-06-12 19:21:14 +02:00
size_t
resolveModelPort ( ModelPort modelPort ) override
{
2023-06-13 03:47:42 +02:00
auto entry = portIdx_ . find ( modelPort ) ;
if ( entry = = portIdx_ . end ( ) )
throw error : : Logic { " Invalid ModelPort for this Dispatcher " } ;
else
return entry - > second ;
2023-06-12 19:21:14 +02:00
}
2023-06-13 03:47:42 +02:00
2023-06-02 02:31:34 +02:00
JobTicket &
2023-06-18 03:50:48 +02:00
getJobTicketFor ( size_t portIDX , TimeValue nominalTime ) override
2023-06-02 02:31:34 +02:00
{
2023-06-13 03:47:42 +02:00
auto & seg = mockSeg_ [ nominalTime ] ;
return seg . jobTicket ( portIDX ) ;
2023-06-02 02:31:34 +02:00
}
2023-06-13 20:23:33 +02:00
public :
2023-06-13 03:47:42 +02:00
MockDispatcher ( )
: dummySetup_ { }
, mockSeg_ { MakeRec ( ) . genNode ( ) } // Node: generate a single active Segment to cover all
, portIdx_ { buildPortIndex ( ) }
{
mockSeg_ . duplicateExitNodeSpec ( portIdx_ . size ( ) ) ;
}
2023-06-02 02:31:34 +02:00
MockDispatcher ( std : : initializer_list < GenNode > specs )
2023-06-13 03:47:42 +02:00
: dummySetup_ { }
, mockSeg_ ( specs )
, portIdx_ { buildPortIndex ( ) }
{
mockSeg_ . duplicateExitNodeSpec ( portIdx_ . size ( ) ) ;
}
2023-06-02 02:31:34 +02:00
2023-06-12 19:21:14 +02:00
2023-06-02 02:31:34 +02:00
ModelPort
provideMockModelPort ( )
{
ModelPorts mockModelPorts = dummySetup_ . getAllModelPorts ( ) ;
return * mockModelPorts ; // using just the first dummy port
}
/**
* The faked builder / playback setup provides some preconfigured ModelPort and
* corresponding DataSink handles . These are stored into a dummy registry and only available
* during the lifetime of the DummyPlaybackSetup instance .
* @ param index number of the distinct port / connection
* @ return a ` std : : pair < ModelPort , DataSink > `
* @ warning as of 5 / 2023 , there are two preconfigured " slots " ,
2023-06-13 03:47:42 +02:00
* and they are not usable in any way other then referring to their identity
2023-06-02 02:31:34 +02:00
*/
play : : test : : DummyOutputLink
getDummyConnection ( uint index )
{
return dummySetup_ . getModelPort ( index ) ;
}
2023-06-06 04:25:12 +02:00
/**
* Test support : verify the given Job is consistent with this Dispatcher .
*/
bool verify ( Job const & job , ModelPort const & port , play : : DataSink const & sink )
{
2023-06-13 03:47:42 +02:00
if ( not dummySetup_ . isSupported ( port , sink ) ) return false ;
TimeValue nominalTime { job . parameter . nominalTime } ;
size_t portIDX = resolveModelPort ( port ) ;
2023-06-18 03:50:48 +02:00
JobTicket & ticket = getJobTicketFor ( portIDX , nominalTime ) ;
2023-06-13 20:23:33 +02:00
return isnil ( ticket ) ? MockJob : : isNopJob ( job )
2023-06-13 03:47:42 +02:00
: MockJobTicket : : isAssociated ( job , ticket ) ;
}
private :
PortIdxMap
buildPortIndex ( )
{
PortIdxMap newIndex ;
uint i { 0 } ;
for ( auto it = dummySetup_ . getAllModelPorts ( )
; bool { it }
; + + it , + + i
)
newIndex [ * it ] = i ;
return newIndex ;
2023-06-06 04:25:12 +02:00
}
2023-06-02 02:31:34 +02:00
} ;
2023-04-17 17:10:53 +02:00
} } } // namespace steam::engine::test
2023-05-24 03:38:12 +02:00
# endif /*STEAM_ENGINE_TEST_MOCK_DISPATCHER_H*/