diff --git a/src/proc/asset/struct-factory-impl.hpp b/src/proc/asset/struct-factory-impl.hpp index fbbd6a0ad..93836e2b2 100644 --- a/src/proc/asset/struct-factory-impl.hpp +++ b/src/proc/asset/struct-factory-impl.hpp @@ -56,6 +56,7 @@ #include "lib/util.hpp" #include +#include using boost::format; @@ -80,7 +81,13 @@ namespace asset { namespace { Symbol genericIdSymbol ("id"); + Symbol seqNrPredicate ("ord"); + inline uint + asNumber (string const& spec) + { + return abs(std::atoi (spec.c_str())); + } // returns 0 in case of unparseable number } @@ -116,6 +123,11 @@ namespace asset { } ENSURE (!isnil (nameID)); + // does the query actually demand the Nth instance/element? + string seqID = extractID (seqNrPredicate, query); + if (!isnil (seqID) && 1 < asNumber(seqID)) + nameID += "."+seqID; + Category cat (STRUCT, StructTraits::catFolder()); return Asset::Ident (nameID, cat ); } diff --git a/src/proc/mobject/output-designation.cpp b/src/proc/mobject/output-designation.cpp index 137d515bc..0423e5890 100644 --- a/src/proc/mobject/output-designation.cpp +++ b/src/proc/mobject/output-designation.cpp @@ -38,6 +38,7 @@ #include "lib/error.hpp" +#include "lib/symbol.hpp" #include "proc/mobject/mobject.hpp" #include "proc/mobject/placement-ref.hpp" #include "proc/mobject/output-designation.hpp" @@ -45,9 +46,13 @@ #include "common/configrules.hpp" #include +#include using lumiera::query::QueryHandler; +using lumiera::query::removeTerm; +using lumiera::query::extractID; using lumiera::ConfigRules; +using lumiera::Symbol; namespace mobject { @@ -167,6 +172,23 @@ namespace mobject { HashVal resulting_targetPipeID (res? (HashVal)res->getID() : 0 ); return resulting_targetPipeID; } + + Symbol SEQNR_PREDICATE = "ord"; + + uint + is_defaults_query_with_channel (Query const& query4pipe) + { + string seqNr = extractID (SEQNR_PREDICATE, query4pipe); + return abs(std::atoi (seqNr.c_str())); // also 0 in case of an invalid number + } + + Query + build_corresponding_sourceQuery (Query const& query4pipe) + { + Query srcQuery = query4pipe; + removeTerm (SEQNR_PREDICATE, srcQuery); + return srcQuery; + } } diff --git a/src/proc/mobject/output-mapping.hpp b/src/proc/mobject/output-mapping.hpp index 642a04664..45f26ae0a 100644 --- a/src/proc/mobject/output-mapping.hpp +++ b/src/proc/mobject/output-mapping.hpp @@ -52,8 +52,9 @@ #ifndef PROC_MOBJECT_OUTPUT_MAPPING_H #define PROC_MOBJECT_OUTPUT_MAPPING_H -#include "lib/error.hpp" +#include "proc/asset/pipe.hpp" #include "lib/bool-checkable.hpp" +#include "lib/error.hpp" #include "lib/query.hpp" #include "lib/util.hpp" @@ -66,8 +67,6 @@ namespace mobject { namespace { // Helper to extract and rebind definition types - using util::contains; - /** * @internal used by OutputMapping * to figure out the mapping target type @@ -120,8 +119,8 @@ namespace mobject { * - a functor \c 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 \c DEF::buildQuery(sourcePipeID) yielding a defaults querry, - * to be issued in case of accessing a non existant mapping + * - a function \c DEF::buildQuery(sourcePipeID,seqNr) yielding a (defaults) + * query to be issued in case of accessing a non existent mapping */ template class OutputMapping @@ -234,7 +233,7 @@ namespace mobject { operator== (Resolver const& a, Resolver const& b) { return a.pID_ == b.pID_; - } // note: Resolver depends on tempate parameter DEF + } // note: Resolver depends on template parameter DEF // All instances of DEF are considered equivalent! friend bool operator== (Resolver const& rr, Target const& tval) @@ -249,7 +248,20 @@ namespace mobject { Resolver operator[] (PId sourcePipeID); Resolver operator[] (PPipe const& pipe); - Resolver operator[] (Query const& query4pipe); + Resolver operator[] (Query query4pipe); + + bool + contains (PId mapping4sourcePipeID) + { + return util::contains (table_, mapping4sourcePipeID); + } + + bool + contains (PPipe sourcePipe) + { + return !sourcePipe + || this->contains (sourcePipe->getID()); + } @@ -263,7 +275,7 @@ namespace mobject { Resolver buildResolutionWrapper (HashVal tableSlot) { - ASSERT (contains (table_, tableSlot)); + ASSERT (this->contains (tableSlot)); return Resolver (*this, table_[tableSlot]); } }; @@ -280,14 +292,35 @@ namespace mobject { /** delegate target pipe resolution to the rules system */ HashVal resolveQuery (Query const&); + + /** detect the special case, when actually the Nth + * solution of a defaults query is requested */ + uint is_defaults_query_with_channel (Query const&); + + /** ..and build the corresponding defaults source query for this case */ + Query build_corresponding_sourceQuery (Query 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 inline typename OutputMapping::Resolver OutputMapping::operator[] (PId sourcePipeID) { - if (!contains (table_, sourcePipeID)) + if (!contains (sourcePipeID)) { // issue a defaults query to resolve this mapping first Query query4pipe = DEF::buildQuery (sourcePipeID); @@ -297,6 +330,8 @@ namespace mobject { } + /** similar to the standard map-style access, but accepts + * a source pipe object instead of just a pipe-ID */ template inline typename OutputMapping::Resolver OutputMapping::operator[] (PPipe const& pipe) @@ -311,7 +346,20 @@ namespace mobject { * 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 \c DEF::output - * functor for resolution to a result object to be returned. + * 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 \em remainder of the query is supposed to + * designate a \em 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 \em 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 \em same query will just fetch this stored pipe-ID and hand it * to the functor, without resolving the query again. You might want to @@ -325,14 +373,29 @@ namespace mobject { */ template inline typename OutputMapping::Resolver - OutputMapping::operator[] (Query const& query4pipe) + OutputMapping::operator[] (Query query4pipe) { HashVal hash4query = _mapping::slot (query4pipe); - if (!contains (table_, hash4query)) - // need to resolve this query first - table_[hash4query] = _mapping::resolveQuery (query4pipe); + if (!contains (hash4query)) + { + 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::Pipe::query( + _mapping::build_corresponding_sourceQuery (query4pipe)); + ENSURE (corresponding_sourcePipe); + + PId sourcePipeID = corresponding_sourcePipe->getID(); + query4pipe = DEF::buildQuery (sourcePipeID, seqNr); + } + + // need to resolve this query first + table_[hash4query] = _mapping::resolveQuery (query4pipe); + } - ENSURE (contains (table_, hash4query)); + ENSURE (this->contains (hash4query)); return buildResolutionWrapper (hash4query); } diff --git a/tests/44builder.tests b/tests/44builder.tests index 14772e4fe..6424f453f 100644 --- a/tests/44builder.tests +++ b/tests/44builder.tests @@ -15,3 +15,7 @@ PLANNED "BuildSegment_test" BuildSegment_test < -//#include +#include #include -//using boost::format; -//using lumiera::Time; -//using util::contains; +using boost::format; using util::isnil; using std::string; -//using std::cout; namespace mobject { namespace test { -//using asset::VIDEO; using asset::Pipe; using asset::PPipe; typedef asset::ID PID; - - namespace { - - } - /******************************************************************* - * @test create a synthetic / example mapping to verify - * generic mapping behaviour + /********************************************************************************* + * @test create a synthetic / example mapping to verify generic mapping behaviour. + * We're creating a custom mapping type here, for this test only: The + * struct DummyDef provides a "definition context" for this custom mapping + * - there is a function to retrieve the actual target object + * for any target pipe stored in the mapping. In this case, + * we just extract the name-ID string from the pipe as result + * - as an additional sideeffect, this DummyDef::output functor + * also defines the Target type of this custom mapping to be std::string + * - DummyDef::buildQuery provides a template query, to be issued whenever + * a yet nonexistent mapping is requested. In this special case here + * we query for a pipe with the name "master_XXX", where XXX denotes + * the stream-type of the source pipe to be mapped. * * @see mobject::OutputDesignation * @see mobject::session::Binding @@ -76,9 +72,11 @@ namespace test { } Query - buildQuery (PID sourcePipeID) + buildQuery (PID sourcePipeID, uint seqNr =0) { - UNIMPLEMENTED ("fabricate defaults query for unit-test"); + PPipe srcP = Pipe::lookup (sourcePipeID); + format queryPattern ("id(master_%1%), stream(%1%), ord(%2%)"); + return Query (queryPattern % srcP->getStreamID() % seqNr); } }; @@ -110,8 +108,14 @@ namespace test { CHECK (map[p1].isValid()); CHECK (map[p1]); + CHECK (!map.contains (pX)); + CHECK (!map.contains (p2)); + + // create an unconnected mapping + map[pX].disconnect(); + CHECK (map.contains (pX)); + CHECK (!map[pX].isValid()); CHECK (!map[pX]); - CHECK (!map[p2]); } @@ -128,22 +132,22 @@ namespace test { Mapping m2(m1); CHECK (!isnil (m2)); CHECK (1 == m2.size()); - CHECK (m1[p1] == "hairy"); - CHECK (m2[p1] == "hairy"); + CHECK (m1[pi] == "hairy"); + CHECK (m2[pi] == "hairy"); m1[pi] = p2; - CHECK (m1[p1] == "furry"); - CHECK (m2[p1] == "hairy"); + CHECK (m1[pi] == "furry"); + CHECK (m2[pi] == "hairy"); m2 = m1; - CHECK (m1[p1] == "furry"); - CHECK (m2[p1] == "furry"); + CHECK (m1[pi] == "furry"); + CHECK (m2[pi] == "furry"); m1.clear(); CHECK (isnil(m1)); CHECK (!isnil(m2)); - CHECK (m2[p1] == "furry"); - CHECK (!m1[p1].isValid()); + CHECK (m2[pi] == "furry"); + CHECK (!m1.contains (pi)); } @@ -156,17 +160,22 @@ namespace test { PPipe p1 = Pipe::query("stream(hairy)"); PPipe p2 = Pipe::query("stream(furry)"); - CHECK (map[p1] == "master(hairy)"); - CHECK (map[p2] == "master(furry)"); + CHECK (map[p1] == "master_hairy"); + CHECK (map[p2] == "master_furry"); + // create new mapping to an explicitly queried target + Query some_pipe ("pipe(super_curly)"); + CHECK (map[some_pipe] == "super_curly"); + + // create a new mapping to the 2nd master for "furry" data Query special_bus ("stream(furry), ord(2)"); - CHECK (map[special_bus] == "master.2(furry)"); + CHECK (map[special_bus] == "master_furry.2"); } }; /** Register this test class... */ - LAUNCHER (OutputMapping_test, "unit session"); + LAUNCHER (OutputMapping_test, "unit session builder");