diff --git a/tests/51asset.tests b/tests/51asset.tests index 6d8195ceb..5e259e95e 100644 --- a/tests/51asset.tests +++ b/tests/51asset.tests @@ -34,7 +34,7 @@ return: 0 END -PLANNED "BasicPort_test" BasicPort_test < + + 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 02139, USA. + +* *****************************************************/ + + +#include "common/test/run.hpp" +#include "common/util.hpp" + +#include "proc/asset.hpp" +#include "proc/asset/pipe.hpp" +#include "common/query.hpp" +#include "proc/assetmanager.hpp" +#include "proc/mobject/session.hpp" + +#include +#include + +using boost::format; +using util::isnil; +using std::string; + + +namespace asset + { + namespace test + { + using mobject::Session; + using cinelerra::Query; + using cinelerra::query::normalizeID; + + + + /*********************************************************************** + * @test basic behaviour of the defaults manager. + *
  1. retrieving a "default" object repeatedly
  2. + *
  3. retrieving a more constrained "default" object
  4. + *
  5. failure registers a new "default"
  6. + *
+ * Using pipe assets as an example. The defaults manager shouldn't + * interfere with memory management (it holds weak refs). + */ + class DefsManager_test : public Test + { + virtual void run(Arg arg) + { + string pipeID = isnil(arg)? "Black Hole" : arg[1]; + string streamID = 2>arg.size()? "teststream" : arg[2] ; + + normalizeID (pipeID); + normalizeID (streamID); + + retrieveSimpleDefault (pipeID); + retrieveConstrainedDefault (pipeID, streamID); + pipeID = failureCreatesNewDefault(); + verifyRemoval (pipeID); + } + + + + + void retrieveSimpleDefault(string pID) + { + PPipe pipe1 = Pipe::query (""); // "the default pipe" + PPipe pipe2; + + // several variants to query for "the default pipe" + pipe2 = Pipe::query (""); + ASSERT (pipe2 == pipe1); + pipe2 = Pipe::query ("default(X)"); + ASSERT (pipe2 == pipe1); + pipe2 = Session::current->defaults(Query ()); + ASSERT (pipe2 == pipe1); + pipe2 = asset::Struct::create (Query ()); + ASSERT (pipe2 == pipe1); + pipe2 = asset::Struct::create (Query ("default(X)")); + ASSERT (pipe2 == pipe1); + } + + + void retrieveConstrainedDefault(string pID, string sID) + { + PPipe pipe1 = Pipe::query (""); // "the default pipe" + ASSERT (sID != pipe1->getProcPatt()->queryStreamID(), + "stream-ID \"%s\" not suitable for test, because " + "the default-pipe \"%s\" happens to have the same " + "stream-ID. We need it to be different", + sID.c_str(), pID.c_str() + ); + + string query_for_sID ("stream("+sID+")"); + PPipe pipe2 = Pipe::query (query_for_sID); + ASSERT (sID == pipe2->getProcPatt()->queryStreamID()); + ASSERT (pipe2 != pipe1); + ASSERT (pipe2 == Pipe::query (query_for_sID)); // reproducible + } + + + string failureCreatesNewDefault () + { + PPipe pipe1 = Session::current->defaults(Query ()); // "the default pipe" + + string new_pID (str (format ("dummy_%s_%i") + % pipe1->getPipeID() + % std::rand() + )); // make random new pipeID + string query_for_new ("pipe("+new_pID+")"); + PPipe pipe2 = Session::current->defaults(Query (query_for_new)); + + TODO ("a way to do only a query, without creating. Use this to check it is really added as default"); + + ASSERT (pipe1 != pipe2); + ASSERT (pipe2 == Pipe::query (query_for_new)); + return new_pID; + } + + + void verifyRemoval(string pID) + { + string query_for_pID ("pipe("+pID+")"); + size_t hash; + { + PPipe pipe1 = Pipe::query (query_for_pID); + hash = pipe1->getID(); + } + // now AssetManager should have the only ref + ID assetID (hash); + + AssetManager& aMang (AssetManager::instance()); + ASSERT ( aMang.known (assetID)); + aMang.remove (assetID); + ASSERT (!aMang.known (assetID)); + + TODO ("assure the bare default-query now fails..."); + PPipe pipe2 = Session::current->defaults(Query (query_for_pID)); + TODO ("assure the bare default-query now succeeds..."); + } + }; + + + /** Register this test class... */ + LAUNCHER (DefsManager_test, "function session"); + + + + } // namespace test + +} // namespace asset diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 5bc243c00..35ed4e339 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -797,7 +797,7 @@ Error: #f88 &rarr; [[Configuration Rules system|ConfigRules]] -
+
Many features can be implemented by specifically configuring and wiring some unspecific components. Rather than tie the client code in need of some given feature to these configuration internals, in Cinelerra-3 the client can //query // for some kind of object providing the //needed capabilities. // Right from start (summer 2007), Ichthyo had the intention to implement such a feature using sort of a ''declarative database'', e.g. by embedding a Prolog system. By adding rules to the basic session configuration, users should be able to customize the semi-automatic part of Cinelerra's behaviour to great extent.
 
 [[Configuration Queries|ConfigQuery]] are used at various places, when creating and adding new objects, as well when building or optimizing the render engine node network.
@@ -808,7 +808,7 @@ Error: #f88
!anatomy of a Configuration Query The query is given as a number of logic predicates, which are required to be true. Syntactically, it is a string in prolog syntax, e.g. {{{stream(mpeg)}}}, where "stream" is the //predicate, // meaning here "the stream type is...?" and "mpeg" is a //term // denoting an actual property, object, thing, number etc &mdash; the actual kind of stream in the given example. Multible comma separated predicates are combined with logical "and". Terms may be //variable // at start, which is denoted syntactically by starting them with a uppercase letter. But any variable term need to be //bound // to some known value while computing the solution to the query, otherwise the query fails. A failed query is treated as a local failure, which may cause some operation being aborted or just some other possibility being chosen. -Queries are represented by instantiations of the {{{Query<TYPE>}}} template, because their actual meaning is "retrieve or create an object of TYPE, configured such that...!". At the C++ side, this ensures type safety and fosters programming against interfaces, while being implemented rule-wise by silently prepending the query with the predicate "{{{object(tYPE)}}}" +Queries are represented by instantiations of the {{{Query<TYPE>}}} template, because their actual meaning is "retrieve or create an object of TYPE, configured such that...!". At the C++ side, this ensures type safety and fosters programming against interfaces, while being implemented rule-wise by silently prepending the query with the predicate "{{{object(TYPE)}}}" !executing a Configuration Query Actually posing such an configuration query, for example to the [[Defaults Manager in the Sessison|DefaultsManagement]], may trigger several actions: First it is checked against internal object registries (depending on the target object type), which may cause the delivery of an already existing object (as reference, clone, or smart pointer). Otherwise, the system tries to figure out an viable configuration for a newly created object instance, possibly by issuing recursive queries. In the most general case this may silently impose additional decisions onto the //execution context // of the query &mdash; by default the session. @@ -818,6 +818,9 @@ At start and for debugging/testing, there is an ''dummy'' implementation using a &rarr; [[considerations for a Prolog based implementation|QueryImplProlog]] &rarr; see {{{src/common/query/mockconfigrules.cpp}}} for the table with the hard wired (mock) answers + +{{red{WARN}}} there is an interference with the (planned) Undo function: a totally correct implementation of "Undo" would need to remember and restore the internal state of the query system (similar to backtracking). But, more generally, such an correct implementation is not achievable, because we are never able to capture and control the whole state of a real world system doing such advanced things like video and sound processing. Seemingly we have to accept that after undoing an action, there is no guarantee we can re-do it the same way as it was first time. +
@@ -839,10 +842,36 @@ This is an very important external Interface, because it links together all thre
[[ProcLayer and Engine]]
 
-
-
For several components and properties there is an implicit default value or configuration; it is stored alongside with the session. The intention is that defaults never create an error, instead, they are to be extended silently on demand. Objects configured according to this defaults can be retrieved at the [[Session]] interface by a set of overloaded functions {{{Session::current->default(Query<TYPE> ("query string"))}}}, where the //query string // defines a capability query similar to what is employed for pipes, stream types, codecs etc. The Queries are implemented by [[configuration rules|ConfigRules]]
+
+
As detailed in the [[definition|DefaultsManagement]], {{{default(Obj)}}} is sort of a Joker along the lines "give me a suitable Object and I don't care for further details". Actually, default objects are implemented by the {{{mobject::session::DefsManager}}}, which remembers and keeps track of anything labeled as "default". This defaults manager is a singleton and can be accessed via the [[Session]] interface, meaning that the memory regarding defaults is part of the session state. Accessing an object via the query for an default actually //tagges// this object (stores a weak ref in the ~DefsManager). Alongside with each object successfully queried via "default", the degree of constriction is remembered, i.e. the number of additional conditions contained in the query. This enables us to search for default objects starting with the most unspecific.
+
+!Skeleton
+# ''search'': using the predicate {{{default(X)}}} enumerates existing objects of suitable type
+#* candidates are delivered starting with the least constrained default
+#* the argument is unified
+#** if the rest of the query succeeds we've found our //default object// and are happy.
+#** otherwise, if all enumerated solutions are exhausted without success, we enter
+# ''default creation'': try to get an object fulfilling the conditions and remember this situation
+#* we issue an ConfigQuery with the query terms //minus// the {{{default(X)}}} predicate
+#* it depends on the circumstances how this query is handled. Typically the query resolution first searches existing objects and then creates a new instance to match the required capabilities. Usually, this process succeeds, but there can be configurations leading to failure.
+#** failing the ~ConfigQuery is considered an (non-critical) exception (throws), as defaults queries are supposed to succeed
+#** otherwise, the newly created object is remembered (tagged) as new default, together with the degree of constriction
+
+!!!Implementation details
+Taken precisely, the "degree of constriction" yields only a partial ordering &mdash; but as the "default"-predicate is sort of a existential quantification anyways, its sole purpose is to avoid polluting the session with unnecessary default objects, and we don't need to care for absolute precision. A suitable approximation is to count the number of predicates terms in the query and use a pqueue (separate for each Type) to store weak refs to the the objects tagged as "default"
+{{red{WARN}}} there is an interference with the (planned) Undo function. This is a general problem of the config queries; it seems reasonable we can ignore this issue.
+
+!!!Problems with the (preliminary) mock implementation
+As we don't have a Prolog interpreter on board yet, we utilize a mock store with preconfigured answers. (see {{{MockConfigQuery}}}). As this preliminary solution is lacking the ability to create new objects, we need to resort to some trickery here (please look away). The overall logic is quite broken, because the system isn't capable to do any real resolution &mdash; if we ignore this fact, the rest of the algorithm can be implemented, tested and used right now.
 
+
+
For several components and properties there is an implicit default value or configuration; it is stored alongside with the session. The intention is that defaults never create an error, instead, they are to be extended silently on demand. Objects configured according to this defaults can be retrieved at the [[Session]] interface by a set of overloaded functions {{{Session::current->default(Query<TYPE> ("query string"))}}}, where the //query string // defines a capability query similar to what is employed for pipes, stream types, codecs etc. The Queries are implemented by [[configuration rules|ConfigRules]]
+
+!!!!what is denoted by {{{default}}}?
+{{{default(Obj)}}} is a predicate expressing that the object {{{Obj}}} can be considered the default setup under the given conditions. Using the //default// can be considered as a shortcut for actually finding a exact and unique solution. The latter would require to specify all sorts of detailed properties up to the point where only one single object can satisfy all conditions. On the other hand, leaving some properties unspecified would yield a set of solutions (and the user code issuing the query had to provide means for selecting one soltution from this set). Just falling back on the //default// means that the user code actually doesn't care for any additional properties (as long as the properties he //does// care for are satisfied). Nothing is said specifically on //how//&nbsp; this default gets configured; actually there can be rules //somewhere,// and, additionally, anything encountered once while asking for a default can be re-used as default under similar circumstances.
+&rarr; [[implementing defaults|DefaultsImplementation]]
+
Along the way of working out various [[implementation details|ImplementationDetails]], decisions need to be made on how to understand the different facilities and entities and how to tackle some of the problems. This page is mainly a collection of keywords, summaries and links to further the discussion. And the various decisions should allways be read as proposals to solve some problem at hand...
 
@@ -2226,6 +2255,33 @@ DAMAGE.
 <html><sub><a href="javascript:;" onclick="scrollAnchorVisible('Top',null, event)">[Top]</sub></a></html>
 ***/
+
+
Pipes play an central role within the Proc Layer, because for everything placed and handled within the EDL, the final goal is to get it transformed into data which can be retrieved at some pipe's exit port. Pipes are special facilities, rather like inventory, separate and not treated like all the other objects.
+We don't distinguish between "input" and "output" ports &mdash; rather, pipes are thought to be ''hooks for making connections to''. By following this line of thought, each pipe has an input side and an output side and is in itself something like a ''Bus'' or ''processing chain''. Other processing entities like effects and transitions can be placed (attached) at the pipe, resulting them to be appended to form this chain. Likewise, we can place [[wiring requests|WiringRequest]] to the pipe, meaning we want it connected so to send it's output to another destination pipe. The [[Builder]] may generate further wiring requests to fulfil the placement of other entities.
+Thus //Pipes are the basic building blocks// of the whole render network. We distinguish ''global available'' Pipes, which are like the sum groups of a mixing console, and the ''lokal pipe'' or [[source port|ClipSourcePort]] of the individual clips, which exist only within the duration of the corresponding clip. The design //limits the possible kinds of pipes // to these two types &mdash; thus we can build local processing chains at clips and global processing chains at the global pipes of the session and that's all we can do. (because of the flexibility which comes with the concept of [[placements|Placement]], this is no real limitation)
+
+The GUI can connect the viewer(s) to some pipe (and moreover can use [[probe points|ProbePoint]] placed like effects and connected to some pipe), and likewise, when starting a ''render'', we get the opportunity to specify the pipes to pull the data from. Pulling data from some pipe is the (only) way to activate the render nodes network reachable from this pipe.
+
+&rarr; [[Handling of Tracks|TrackHandling]]
+&rarr; [[Handling of Pipes|PipeHandling]]
+
+
+
+
+
!Identification
+Pipes are distinct objects and can be identified by their asset ~IDs. Besides, as for all [[structural assets|StructAsset]] there are extended query capabilities, including a symbolic pipe-id and a media (stream) type id. Any pipe can accept and deliver exactly one media stream kind (which may be inherently structured though, e.g. spatial sound systems or stereoscopic video)
+
+!creating pipes
+Pipe assets are created automatically by being used and referred. The [[Session]] holds a collection of global pipes, and further pipes can be created by using an new pipe reference in some placement. Moreover, every clip has an (implicit) [[source port|ClipSourcePort]], which will appear as pipe asset when first used (referred) while [[building|BuildProcess]].
+
+!removal
+Deleting a Pipe is an advanced operation, because it includes finding and "detaching" all references, otherwise the pipe will leap back into existence immediately. Thus, global pipe entries in the Session and pipe references in [[locating pins|LocatingPin]] within any placement have to be removed, while clips using a given source port will be disabled. {{red{todo: implementation deferred}}}
+
+!using Pipes
+there is not much you can do directly with a pipe asset. It is an point of reference, after all. Any connection to some pipe is only temporarily done by a placement in some part of the timeline, so it isn't stored with the pipe. You can edit the (user visible) description an you can globally disable a pipe asset. The pipe's ID and media stream type of course are fixed, because any connection and referral (via the asset ID) is based on them. Later on, we should provide a {{{rewire(oldPipe, newPipe)}}} to search any ref to the {{{oldPipe}}} and try to rewrite it to use the {{{newPipe}}}, possibly with a new media stream type.
+Pipes are integrated with the [[management of defaults|DefaultsManagement]]. For example, any pipe uses implicitly some [[processing pattern|ProcPatt]] &mdash; it may default to the empty pattern. This feature enables to apply some standard wiring to the pipes (e.g a fader for audio, similar to the classic mixing consoles). This //is // a global property of the pipe, but &mdash; contrary to the stream type &mdash; this pattern may be switched
+
+
A Placement represents a //relation:// it is always linked to a //Subject// (this being a [[Media Object|MObject]]) and has the meaning to //place// this Subject in some manner, either relatively to other Media Objects, or by some Constraint or simply absolute at (time,track). The latter case is especially important and represented by a special [[Sub-Interface|ExplicitPlacement]]
 
@@ -2351,34 +2407,6 @@ We need a way of addressing existing [[pipes|Pipe]]. Besides, as the Pipes and T
 //Note, we have yet to specify how exactly the building and rendering will work together with the backend. There are several possibilities how to structure the Playlist//
 
-
-
-Pipes play an central role within the Proc Layer, because for everything placed and handled within the EDL, the final goal is to get it transformed into data which can be retrieved at some pipe's exit port. Pipes are special facilities, rather like inventory, separate and not treated like all the other objects.
-We don't distinguish between "input" and "output" ports &mdash; rather, pipes are thought to be ''hooks for making connections to''. By following this line of thought, each pipe has an input side and an output side and is in itself something like a ''Bus'' or ''processing chain''. Other processing entities like effects and transitions can be placed (attached) at the pipe, resulting them to be appended to form this chain. Likewise, we can place [[wiring requests|WiringRequest]] to the pipe, meaning we want it connected so to send it's output to another destination pipe. The [[Builder]] may generate further wiring requests to fulfil the placement of other entities.
-Thus //Pipes are the basic building blocks// of the whole render network. We distinguish ''global available'' Pipes, which are like the sum groups of a mixing console, and the ''lokal pipe'' or [[source port|ClipSourcePort]] of the individual clips, which exist only within the duration of the corresponding clip. The design //limits the possible kinds of pipes // to these two types &mdash; thus we can build local processing chains at clips and global processing chains at the global pipes of the session and that's all we can do. (because of the flexibility which comes with the concept of [[placements|Placement]], this is no real limitation)
-
-The GUI can connect the viewer(s) to some pipe (and moreover can use [[probe points|ProbePoint]] placed like effects and connected to some pipe), and likewise, when starting a ''render'', we get the opportunity to specify the pipes to pull the data from. Pulling data from some pipe is the (only) way to activate the render nodes network reachable from this pipe.
-
-&rarr; [[Handling of Tracks|TrackHandling]]
-&rarr; [[Handling of Pipes|PipeHandling]]
-
-
-
-
-
!Identification
-Pipes are distinct objects and can be identified by their asset ~IDs. Besides, as for all [[structural assets|StructAsset]] there are extended query capabilities, including a symbolic pipe-id and a media (stream) type id. Any pipe can accept and deliver exactly one media stream kind (which may be inherently structured though, e.g. spatial sound systems or stereoscopic video)
-
-!creating pipes
-Pipe assets are created automatically by being used and referred. The [[Session]] holds a collection of global pipes, and further pipes can be created by using an new pipe reference in some placement. Moreover, every clip has an (implicit) [[source port|ClipSourcePort]], which will appear as pipe asset when first used (referred) while [[building|BuildProcess]].
-
-!removal
-Deleting a Pipe is an advanced operation, because it includes finding and "detaching" all references, otherwise the pipe will leap back into existence immediately. Thus, global pipe entries in the Session and pipe references in [[locating pins|LocatingPin]] within any placement have to be removed, while clips using a given source port will be disabled. {{red{todo: implementation deferred}}}
-
-!using Pipes
-there is not much you can do directly with a pipe asset. It is an point of reference, after all. Any connection to some pipe is only temporarily done by a placement in some part of the timeline, so it isn't stored with the pipe. You can edit the (user visible) description an you can globally disable a pipe asset. The pipe's ID and media stream type of course are fixed, because any connection and referral (via the asset ID) is based on them. Later on, we should provide a {{{rewire(oldPipe, newPipe)}}} to search any ref to the {{{oldPipe}}} and try to rewrite it to use the {{{newPipe}}}, possibly with a new media stream type.
-Pipes are integrated with the [[management of defaults|DefaultsManagement]]. For example, any pipe uses implicitly some [[processing pattern|ProcPatt]] &mdash; it may default to the empty pattern. This feature enables to apply some standard wiring to the pipes (e.g a fader for audio, similar to the classic mixing consoles). This //is // a global property of the pipe, but &mdash; contrary to the stream type &mdash; this pattern may be switched
-
-
Open issues, Things to be worked out, Problems still to be solved... 
 
@@ -2484,9 +2512,9 @@ Like all [[structural assets|StructAsset]], ~ProcPatt employs a special naming s
 
a given Render Engine configuration is a list of Processors. Each Processor in turn contains a Graph of ProcNode.s to do the acutal data processing. In order to cary out any calculations, the Processor needs to be called with a StateProxy containing the state information for this RenderProcess
 
-
+
//obviously, getting this one to work requires quite a lot of technical details to be planned and implemented.// This said...
-The intention is to get much more readable ("declarative") and changeable configuration as by programming it directly within the implementation of some object.
+The intention is to get much more readable ("declarative") and changeable configuration as by programming the decision logic literately within the implementation of some object.
 
 !Draft
 As an example, specifying how a Track can be configured for connecting automatically to some "mpeg" bus (=pipe)