lumiera_/src/steam/mobject/output-mapping.hpp
Ichthyostega b6a39fa831 Upgrade: simplify comparisons
Now able to remove most complicated comparison operators and most usages of boost::operators...
In most cases it is sufficient just to define one ''spaceship operator'',
and often even that one can be synthesised.

However — we still use boost::operators for arithmetic types,
notably the `lib::time::TimeValue`, which is addable and mutipliable
2025-07-04 03:37:54 +02:00

409 lines
15 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.

/*
OUTPUT-MAPPING.hpp - generic interface for translation of output designations
Copyright (C)
2010, 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 output-mapping.hpp
** Translating and wiring output designations.
** OutputMapping is a complement to the OutputDesignation handles
** used at various places in the high-level model. It is used when
** translating a given output spec into another connection target
** - when connecting a model port to a concrete external output
** - when connecting a timeline to a viewer element
** - for implementing the viewer input selection "switchboard"
** - for translating output designation of virtual clips
** OutputMapping is to be used as value object, holding concrete
** connections and wiring. For each of the mentioned usage situations,
** it needs to be adapted specifically, which is achieved by template
** (generic) programming: The usage situation provides a definition
** context DEF to fill in the variable parts of the implementation.
** This definition context is actually instantiated (as base class).
** The mapping table actually just stores an association of hash
** values, which typically are interpreted as asset::ID<Pipe>.
** But the actual mapping result is retrieved on each access
** by invoking a functor on the stored hash value,
** thus the final resolution is done _late_.
**
** @see OutputDesignation
** @see OutputMapping_test
**
*/
#ifndef STEAM_MOBJECT_OUTPUT_MAPPING_H
#define STEAM_MOBJECT_OUTPUT_MAPPING_H
#include "lib/error.hpp"
#include "lib/util.hpp"
#include "lib/hash-value.h"
#include "steam/asset/pipe.hpp"
#include "common/query.hpp"
#include <compare>
#include <map>
namespace steam {
namespace mobject {
namespace { // Helper to extract and rebind definition types
/**
* @internal used by OutputMapping
* to figure out the mapping target type
*/
template<class DEF>
class _def
{
typedef asset::ID<asset::Pipe> PId;
template<typename FUN>
struct Rebind;
template<typename RET>
struct Rebind<RET(DEF::*)(PId)>
{
typedef RET Res;
};
using OutputMappingMemberFunc = decltype(&DEF::output) ;
using Rebinder = Rebind<OutputMappingMemberFunc>;
public:
using Target = typename Rebinder::Res;
};
}//(End) type rebinding helper
namespace error = lumiera::error;
using lumiera::Query;
using lib::HashVal;
/**
* OutputMapping is a facility to resolve output designations.
* The session/model uses preliminary or partial output specifications,
* which are to be resolved to an actual system output while building
* and preparing a render network for operation (playback/rendering).
* For a given specification, resolution to the desired target spec
* may be derived by querying the OutputMapping. Here, the kind of
* the target specification is defined through the type parameter.
*
* # definition of specific mapping behaviour
*
* This is an generic map-like container, acting as Interface to be used
* in the signature of API functions either providing or requiring a Mapping.
* For each distinct usage situation, an instantiation of this template should
* be created, providing a _definition context_ as template parameter.
* Instances of this concrete mapping type may then be default constructed
* and copied freely. The definition context is supposed to provide
* - a functor `DEF::output` usable as function pipe-ID --> Target
* - the concrete output-functor also defines the concrete Target type,
* which will be returned when accessing the OutputMapping
* - a function `DEF::buildQuery (sourcePipeID,seqNr)` yielding a (defaults)
* query to be issued in case of accessing a non existent mapping
* @tparam DEF static (compile-time) definition/configuration context
*/
template<class DEF>
class OutputMapping
: public DEF
{
using Setup = _def<DEF>;
using PId = asset::ID<asset::Pipe>;
using PPipe = asset::PPipe;
/* == mapping table storage == */
std::map<HashVal,HashVal> table_;
public:
using Target = typename Setup::Target;
// using default ctor and copy operations
size_t size() const { return table_.size(); }
bool empty() const { return 0 == size(); }
void clear() { table_.clear(); }
/**
* @internal transient resolution wrapper to be exposed by map-style access.
* A Resolver instance represents an output mapping result, yet to be fully resolved.
* It is created on the stack by the OutputMapping container and internally wired
* back to the container and the actually stored value (pipe-ID-hash) in the table.
* Actually retrieving the result value by the client code triggers invocation
* of the specific resolution functor, embedded in the definition context `DEF`,
* which was given when instantiating the OutputMapping template.
* @note depends on the template parameter of the enclosing OutputMapping type!
* @remarks
* - final mapping result can be compared to Target
* - Resolvers (mapping values) can be compared based on the Pipe-hash
*/
class Resolver
{
OutputMapping& thisMapping_;
HashVal& pID_;
Resolver (OutputMapping& container, HashVal& resultVal)
: thisMapping_(container)
, pID_(resultVal)
{ }
friend class OutputMapping;
/* copy by clients prohibited */
Resolver& operator= (Resolver const&);
Target
resolve() const
{
REQUIRE (pID_);
PId targetPipeID (pID_);
return thisMapping_.resolveTarget (targetPipeID);
}
public:
/** explicitly define a new target ID for this individual mapping
* @note the actually returned result depends on what the configured
* `DEF::output` functor will yield when invoked on this ID
*/
void
operator= (PId newId2map)
{
pID_ = newId2map;
}
void
operator= (PPipe newPipe2map)
{
REQUIRE (newPipe2map);
pID_ = newPipe2map->getID();
}
void
disconnect() ///< switch this individual mapping into \em unconnected state
{
pID_ = 0;
}
/** actually retrieve the target object of the mapping.
* This operation is invoked when client code accesses
* the result of an OutputMapping query.
* @return result of invoking the configured `DEF::output` functor
* @throw error::Logic when resolving an _unconnected_ mapping
*/
operator Target()
{
if (not isValid())
throw error::Logic{"attempt to resolve an unconnected output mapping"
, LERR_(UNCONNECTED)};
return resolve();
}
bool
isValid() const ///< is this a valid \em connected mapping?
{
return bool(pID_);
}
explicit
operator bool() const
{
return isValid();
}
/* === equality comparisons (boost::operators) === */
friend bool
operator== (Resolver const& a, Resolver const& b)
{
return a.pID_ == b.pID_;
} // note: Resolver depends on template parameter DEF
// All instances of DEF are considered equivalent!
friend bool
operator== (Resolver const& rr, Target const& tval)
{
return rr.resolve() == tval;
}
};
/* === Map-style access for clients === */
Resolver operator[] (PId sourcePipeID);
Resolver operator[] (PPipe const& pipe);
Resolver operator[] (Query<asset::Pipe> query4pipe);
bool
contains (PId mapping4sourcePipeID)
{
return util::contains (table_, mapping4sourcePipeID);
}
bool
contains (PPipe sourcePipe)
{
return !sourcePipe
or this->contains (sourcePipe->getID());
}
private:
Target
resolveTarget (PId mappedPipeID)
{
return DEF::output (mappedPipeID);
}
Resolver
buildResolutionWrapper (HashVal tableSlot)
{
ASSERT (this->contains (tableSlot));
return Resolver (*this, table_[tableSlot]);
}
};
/* ===== Implementation details ===== */
namespace _mapping {
/** yield a suitable table slot for this query */
HashVal slot (Query<asset::Pipe> const&);
/** delegate target pipe resolution to the rules system */
HashVal resolveQuery (Query<asset::Pipe> const&);
/** detect the special case, when actually the Nth
* solution of a defaults query is requested */
uint is_defaults_query_with_channel (Query<asset::Pipe> const&);
/** ..and build the corresponding defaults source query for this case */
Query<asset::Pipe> build_corresponding_sourceQuery (Query<asset::Pipe> const&);
}
/** standard map-style access to an OutputMapping.
* For the given source pipe-ID the mapped target pipe-ID is fetched
* and then handed over to the configured \c DEF::output functor, which
* is assumed to calculate or retrieve the actual result object.
*
* \par default mappings
* whenever accessing an yet non-existent mapping, a query is issued
* behind the scenes to establish a suitable default mapping. The actual
* query is built from a query template by the \c DEF::buildQuery function
* and thus can be configured for the concrete usage situation of the mapping.
* @warning depending on the actually configured defaults query, there might be
* no solution, in which case an \em unconnected marker is retrieved and
* stored. Thus the yielded Resolver should be checked, if in doubt.
*/
template<class DEF>
inline typename OutputMapping<DEF>::Resolver
OutputMapping<DEF>::operator[] (PId sourcePipeID)
{
if (not contains (sourcePipeID))
{
// issue a defaults query to resolve this mapping first
Query<asset::Pipe> query4pipe = DEF::buildQuery (sourcePipeID);
table_[sourcePipeID] = _mapping::resolveQuery (query4pipe);
}
return buildResolutionWrapper (sourcePipeID);
}
/** similar to the standard map-style access, but accepts
* a source pipe object instead of just a pipe-ID */
template<class DEF>
inline typename OutputMapping<DEF>::Resolver
OutputMapping<DEF>::operator[] (PPipe const& pipe)
{
REQUIRE (pipe);
return (*this) [pipe->getID()];
}
/** determine an OutputMapping by resolving a complex query,
* instead of just picking a mapped pipe (which is the default usage).
* Accessing the OutputMapping this way by query enables all kinds of
* extended usages: It suffices that the given query somehow yields a Pipe,
* which then is considered the mapped result and handed over to the `DEF::output`
* functor for resolution to a result object to be returned.
*
* \par Query for the Nth default instance
* OutputMapping provides a special behaviour for retrieving "the Nth default pipe".
* The rationale being the request for connection to the Nth bus of a given kind, like
* e.g. the 3rd audio subgroup or the 2nd video master. This special behaviour is triggered
* by the predicate "ord(##)" in the query. The _remainder of the query_ is supposed to
* designate a _default_ in this case, rather then querying directly for the result of
* the mapping. Thus this remainder of the query is used to retrieve a _source pipe_,
* which then is treated as if accessing a non-existent mapping: a suitable default
* solution for this mapping is retrieved, but in this special case, we append the
* given sequence number to the ID of the retrieved pipe, i.e. we get the Nth
* (identical) solution to the aforementioned query for a default pipe.
*
* @note the mapped result is remembered within this mapping. Further invocations
* with the _same query_ will just fetch this stored pipe-ID and hand it
* to the functor, without resolving the query again. You might want to
* [remove](\ref Resolver::disconnect) this specific mapping
* in order to force re-evaluation of the query.
* @param Query for a pipe, which is handed over as-is to the rules engine.
* @warning depending on the actual query, there might be no solution,
* in which case an _unconnected marker_ is retrieved and
* stored. Thus the yielded Resolver should be checked,
* if in doubt.
*/
template<class DEF>
inline typename OutputMapping<DEF>::Resolver
OutputMapping<DEF>::operator[] (Query<asset::Pipe> query4pipe)
{
HashVal hash4query = _mapping::slot (query4pipe);
if (not contains (hash4query))
{
// need to resolve this query first
auto addEntry = [&](auto query)
{
table_[hash4query] = _mapping::resolveQuery (query);
};
if (uint seqNr = _mapping::is_defaults_query_with_channel (query4pipe))
{
// treat the special case
// when actually requesting the "Nth default of this kind"
PPipe corresponding_sourcePipe
= asset::Struct::retrieve (
_mapping::build_corresponding_sourceQuery (query4pipe));
ENSURE (corresponding_sourcePipe);
PId sourcePipeID = corresponding_sourcePipe->getID();
addEntry (DEF::buildQuery (sourcePipeID, seqNr));
}
else
addEntry (query4pipe);
}
ENSURE (this->contains (hash4query));
return buildResolutionWrapper (hash4query);
}
}} // namespace steam::mobject
#endif