From eafbd4b1cb53b0bc23665d6adee9cf5270505adf Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 27 Jan 2011 14:35:46 +0100 Subject: [PATCH 01/28] fix a comment --- src/lib/time/timevalue.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp index 7e3d370ad..014d3283d 100644 --- a/src/lib/time/timevalue.hpp +++ b/src/lib/time/timevalue.hpp @@ -109,7 +109,7 @@ namespace time { * @note supports scaling by a factor, * which \em deliberately is chosen * as int, not gavl_time_t, because the - * multiplying times is meaningless. + * multiplying of times is meaningless. */ class TimeVar : public TimeValue @@ -170,8 +170,11 @@ namespace time { : public TimeValue { protected: + /** generally immutable, + * but derived classes allow some limited mutation + * through special API calls */ Offset& - operator= (Offset const& o) ///< derived classes allow mutation + operator= (Offset const& o) { TimeValue::operator= (o); return *this; From ab72b528c8c7b0e3a92155010e2136e61aabd59c Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 7 Mar 2011 02:52:47 +0100 Subject: [PATCH 02/28] draft test for changing time specs --- tests/40components.tests | 5 + tests/lib/time/time-mutation-test.cpp | 127 ++++++++++++++++++++++++++ wiki/renderengine.html | 11 ++- 3 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 tests/lib/time/time-mutation-test.cpp diff --git a/tests/40components.tests b/tests/40components.tests index 98e5c290a..7b3bdd31d 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -678,6 +678,11 @@ return: 0 END +TEST "Changing time specifications" TimeMutation_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 "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/time/timevalue.hpp" +#include "lib/time/timequant.hpp" +//#include "lib/time/display.hpp" +#include "lib/util.hpp" + +#include +#include +//#include +#include + +using boost::lexical_cast; +using util::isnil; +//using std::rand; +using std::cout; +using std::endl; +using std::string; + +//using lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE; + +namespace lib { +namespace time{ +namespace test{ + + namespace { + inline string + pop (Arg arg) + { + if (isnil (arg)) return ""; + string entry = arg[0]; + arg.erase (arg.begin()); + return entry; + } + } + + + /**************************************************************** + * @test cover all basic cases for mutating a time specification. + * - change to a given value + * - change by an offset + * - change using a grid value + * - apply an (grid) increment + */ + class TimeMutation_test : public Test + { + gavl_time_t + random_or_get (string arg) + { + if (isnil(arg)) + return 1 + (rand() % 10000); + else + return lexical_cast (arg); + } + + + virtual void + run (Arg arg) + { + TimeValue o (random_or_get (pop(arg))); + TimeValue c (random_or_get (pop(arg))); + + PQuant fixQ (new FixedFrameQuantiser(FrameRate::PAL)); + QuTime qVal (o, fixQ); + FrameNr count(qVal); + + mutate_by_Value (o, c); + mutate_by_Offset (o, Offset(c)); + mutate_quantised (o, qVal); + mutate_by_Increment(o, count); + } + + + void + mutate_by_Value(TimeValue o, TimeValue change) + { + } + + + void + mutate_by_Offset (TimeValue o, Offset change) + { + } + + + void + mutate_quantised (TimeValue o, QuTime change) + { + } + + + void + mutate_by_Increment (TimeValue o, int change) + { + } + }; + + + /** Register this test class... */ + LAUNCHER (TimeMutation_test, "unit common"); + + + +}}} // namespace lib::time::test diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 1985b9c83..96acf0937 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -6618,8 +6618,8 @@ Thus no server and no network connection is needed. Simply open the file in your * see [[Homepage|http://tiddlywiki.com]], [[Wiki-Markup|http://tiddlywiki.org/wiki/TiddlyWiki_Markup]] -
-
Simple time points are just like values and thus easy to change. The difficulties arise when time values are to be //quantised to an existing time grid.// The first noticeable point to note is that now the effect can be decoupled from the cause. Small changes below the threshold might be accumulated, and a tiny change might trigger a jump to the next grid point. While this might be annoying, the yet more complex questions arise when we acknowledge that the change itself might be related to a time grid.
+
+
Simple time points are just like values and thus easy to change. The difficulties arise when time values are to be //quantised to an existing time grid.// The first relevant point to note is that for quantised time values, the effect of a change can be disproportional to the cause. Small changes below the threshold might be accumulated, and a tiny change might trigger a jump to the next grid point. While this might be annoying, the yet more complex questions arise when we acknowledge that the amount of the change itself might be related to a time grid.
 
 The problem with modification of quantised values highlights an inner contradiction or conflicting goals within the design
 * the whole system should fit in naturally and just feel like using raw time values
@@ -6631,7 +6631,7 @@ At this point, we should recall some general assumptions within Lumiera
 * there are no hard-coded defaults regarding the specific nature of the manipulated objects (format, number of channels)
 * there is not "the" timeline, but we have multiple timelines
 * sequences might be nesetd and thus be brought into a different context
-* framerate is a property of the stream type of a channel; it depends on the output. Ther is no global format or framerate.
+* framerate is a property of the stream type of a channel; it depends on the output. There is no global format or framerate.
 
 !Usage situations
 The complexity arises from the mixture of several concerns, which often blend into a single usage situation.
@@ -6680,7 +6680,10 @@ As rationale, consider the following
 * time values are immutable (as far as possible)
 * only allow to assign a completely new setting
 * only accept raw time values for redefining a quantised interval
-* only accept an abstract //modification object.//
+* only accept an abstract //modification object.// &rarr;{{red{No! any kind of abstraction requires an indirection}}}
+
+{{red{Question 3/11}}} -- can we avoid accepting mutations for Time (and thus only mutate ~TimeSpan)?
+Rationale: allowing mutations for Time bears the danger of making ~TimeVar obsolete
 
 !!!usage considerations
 Question is: how fine grained and configurable needs this to be?

From 9eb4f66372bc258482576e5851e98369b350b29c Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Sat, 2 Apr 2011 04:01:06 +0200
Subject: [PATCH 03/28] draft more convenience handling for TimeSpan

---
 src/lib/time/timevalue.hpp         | 45 +++++++++------
 tests/lib/time/time-value-test.cpp | 89 ++++++++++++++++++++++++++++--
 2 files changed, 111 insertions(+), 23 deletions(-)

diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp
index 014d3283d..315f192cb 100644
--- a/src/lib/time/timevalue.hpp
+++ b/src/lib/time/timevalue.hpp
@@ -240,18 +240,15 @@ namespace time {
    * 
    * Lumiera Time provides some limited capabilities for
    * direct manipulation; Time values can be created directly
-   * from \c (h,m,s,ms) specification and there is an string
-   * representation intended for internal use (reporting and
-   * debugging). Any real output, formatting and persistent
+   * from \c (ms,sec,min,hour) specification and there is an
+   * string representation intended for internal use (reporting
+   * and debugging). Any real output, formatting and persistent
    * storage should be based on the (quantised) timecode
    * formats though, which can be generated from time values.
    * 
-   * Non constant Time objects can receive an encapsulated
-   * \em mutation message, which is also the basis for
-   * changing time spans, durations and for re-aligning
-   * quantised values to some other grid.
-   * 
-   * @todo define these mutations
+   * Similar to TimeValue, also Time objects are considered
+   * immutable values. As convenience shortcut, some operators
+   * are provided, creating a TimVar for further calculations.
    */
   class Time
     : public TimeValue
@@ -300,10 +297,8 @@ namespace time {
   /**
    * Duration is the internal Lumiera time metric.
    * It is an absolute (positive) value, but can be
-   * promoted from an offset. Similar to Time,
-   * Duration can receive mutation messages.
-   * 
-   * @todo define these mutations
+   * promoted from an offset. Like plain TimeValue,
+   * Duration is an immutable value.
    */
   class Duration
     : public Offset
@@ -342,8 +337,13 @@ namespace time {
    * to fully specify the temporal properties of an
    * object within the model.
    * 
-   * Similar to Time and Duration, a TimeSpan may
-   * also receive an (opaque) mutation message.
+   * As an exception to the generally immutable Time
+   * entities, a non constant TimeSpan may receive
+   * \em mutation messages, both for the start point
+   * and the duration. This allows for changing
+   * position and length of objects in the timeline.
+   * 
+   * @todo define these mutations
    */
   class TimeSpan
     : public Time
@@ -351,11 +351,16 @@ namespace time {
       Duration dur_;
       
     public:
-      TimeSpan(Time start, Duration length)
+      TimeSpan(TimeValue start, Duration length)
         : Time(start)
         , dur_(length)
         { }
-                                                ///////////TODO creating timespans needs to be more convenient....
+      
+      TimeSpan(TimeValue start, FSecs(duration_in_secs))
+        : Time(start)
+        , dur_(duration_in_secs)
+        { }
+      
       
       Duration
       duration()  const 
@@ -363,6 +368,12 @@ namespace time {
           return dur_;
         }
       
+      Time
+      start()  const
+        {
+          return *this;
+        }
+      
       Time
       end()  const
         {
diff --git a/tests/lib/time/time-value-test.cpp b/tests/lib/time/time-value-test.cpp
index e73354ea8..06cb251e7 100644
--- a/tests/lib/time/time-value-test.cpp
+++ b/tests/lib/time/time-value-test.cpp
@@ -71,11 +71,12 @@ namespace test{
           
           checkBasicTimeValues (ref);
           checkMutableTime (ref);
-          checkTimeConveniance (ref);
+          checkTimeConvenience (ref);
           verify_invalidFramerateProtection();
           createOffsets (ref);
           buildDuration (ref);
           buildTimeSpan (ref);
+          compareTimeSpan (ref);
         } 
       
       
@@ -159,7 +160,7 @@ namespace test{
        *        especially by the canonical Time values.
        */
       void
-      checkTimeConveniance (TimeValue org)
+      checkTimeConvenience (TimeValue org)
         {
           Time o1(org);
           TimeVar v(org);
@@ -296,15 +297,91 @@ namespace test{
           CHECK (theLength == Offset(org,five).abs());
           
           Time endpoint = interval.end();
-          TimeSpan successor (endpoint, Duration(Time(0,2,0,0)));
+          TimeSpan successor (endpoint, FSecs(2));
           
           CHECK (Offset(interval,endpoint) == Offset(org,five).abs());
           CHECK (Offset(endpoint,successor.end()) == Duration(successor));
           
-          cout <<   "Interval-1: " << interval 
-               << "  Interval-2: " << successor 
+          cout <<   "Interval-1: " << interval
+               << "  Interval-2: " << successor
                << "  End point: "  << successor.end()
-               << endl; 
+               << endl;
+        }          
+      
+      
+      void
+      compareTimeSpan (TimeValue org)
+        {
+          TimeSpan span1 (Time(org), Duration(Time(org)));
+          TimeSpan span2 (Time(org), Offset(org, TimeValue(0)));
+          TimeSpan span3 (Time(org), FSecs(5,2));
+          TimeSpan span4 (Time(org), FSecs(5,-2));
+          
+          CHECK (span1 == span2);
+          CHECK (span2 == span1);
+          CHECK (span3 == span4);
+          CHECK (span4 == span3);
+          
+          CHECK (span1 != span3);
+          CHECK (span3 != span1);
+          CHECK (span1 != span4);
+          CHECK (span4 != span1);
+          CHECK (span2 != span3);
+          CHECK (span3 != span2);
+          CHECK (span2 != span4);
+          CHECK (span4 != span2);
+          
+          // especially note that creating a TimeSpan
+          // based on offset will take the offset absolute
+          CHECK (span1.end() == span2.end());
+          CHECK (span3.end() == span4.end());
+          
+          // Verify the extended order on time intervals
+          TimeSpan span1x (Time(org)+Time(org), Duration(Time(org))); // starting later than span1
+          TimeSpan span3y (Time(org), FSecs(2));                      // shorter than span3
+          TimeSpan span3z (Time(org)+Time(org), FSecs(2));            // starting later and shorter than span3
+          
+          CHECK (span1 != span1x);
+          CHECK (span3 != span3y);
+          CHECK (span3 != span3z);
+          
+          CHECK (  span1 <  span1x);
+          CHECK (  span1 <= span1x);
+          CHECK (!(span1 >  span1x));
+          CHECK (!(span1 >= span1x));
+          
+          CHECK (  span3 >  span3y);
+          CHECK (  span3 >= span3y);
+          CHECK (!(span3 <  span3y));
+          CHECK (!(span3 <= span3y));
+          
+          CHECK (  span3 < span3z);    // Note: the start point takes precedence on comparison
+          CHECK ( span3y < span3z);
+          
+          // Verify this order is really different
+          // than the basic ordering of time values
+          CHECK (span1 < span1x);
+          CHECK (span1.duration() == span1x.duration());
+          CHECK (span1.start() < span1x.start());
+          CHECK (span1.end()   < span1x.end());
+          
+          CHECK (span3 > span3y);
+          CHECK (span3.duration() > span3y.duration());
+          CHECK (span3.start() == span3y.start());
+          CHECK (span3.end()   == span3y.end());
+          CHECK (Time(span3)   == Time(span3y));
+          
+          CHECK (span3 < span3z);
+          CHECK (span3.duration() > span3z.duration());
+          CHECK (span3.start() < span3z.start());
+          CHECK (span3.end()   < span3z.end());
+          CHECK (Time(span3)   < Time(span3z));
+          
+          CHECK (span3y < span3z);
+          CHECK (span3y.duration() == span3z.duration());
+          CHECK (span3y.start() < span3z.start());
+          CHECK (span3.end()   < span3z.end());
+          CHECK (Time(span3)  < Time(span3z));
         }
     };
   

From 0e4ed856d7b0438800d9c1f28481ba58bf9561f8 Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Sat, 2 Apr 2011 04:02:05 +0200
Subject: [PATCH 04/28] WIP: get back into the topic of time value mutations

start drafting a unit test....
---
 tests/lib/time/time-mutation-test.cpp | 26 ++++++++++++++++++++++++--
 wiki/renderengine.html                | 11 +++++++----
 2 files changed, 31 insertions(+), 6 deletions(-)

diff --git a/tests/lib/time/time-mutation-test.cpp b/tests/lib/time/time-mutation-test.cpp
index d47c7d3fb..e7c3dbe5f 100644
--- a/tests/lib/time/time-mutation-test.cpp
+++ b/tests/lib/time/time-mutation-test.cpp
@@ -25,6 +25,7 @@
 #include "lib/test/test-helper.hpp"
 #include "lib/time/timevalue.hpp"
 #include "lib/time/timequant.hpp"
+#include "proc/asset/meta/time-grid.hpp"
 //#include "lib/time/display.hpp"
 #include "lib/util.hpp"
 
@@ -46,6 +47,8 @@ namespace lib {
 namespace time{
 namespace test{
   
+  using asset::meta::TimeGrid;
+  
   namespace {
     inline string
     pop (Arg arg)
@@ -76,6 +79,19 @@ namespace test{
             return lexical_cast (arg);
         }
       
+      struct TestValues
+        {
+          TimeVar var;
+          TimeSpan span;
+          QuTime quant;
+          
+          TestValues (TimeValue o)
+            : var(o)
+            , span(o, Offset(o))
+            , quant(o, "test_grid")
+            { }
+        };
+      
       
       virtual void
       run (Arg arg) 
@@ -83,8 +99,10 @@ namespace test{
           TimeValue o (random_or_get (pop(arg)));
           TimeValue c (random_or_get (pop(arg)));
           
-          PQuant fixQ (new FixedFrameQuantiser(FrameRate::PAL));
-          QuTime qVal (o, fixQ);
+          // using a 25fps-grid, but with an time origin offset by 1/50sec
+          TimeGrid::build("test_grid", FrameRate::PAL, Time(FSecs(1,50)));
+          
+          QuTime qVal (o, "test_grid");
           FrameNr count(qVal);
           
           mutate_by_Value (o, c);
@@ -97,24 +115,28 @@ namespace test{
       void
       mutate_by_Value(TimeValue o, TimeValue change)
         {
+          TestValues t(o);
         }
       
       
       void
       mutate_by_Offset (TimeValue o, Offset change)
         {
+          TestValues t(o);
         }
       
       
       void
       mutate_quantised (TimeValue o, QuTime change)
         {
+          TestValues t(o);
         }
       
       
       void
       mutate_by_Increment (TimeValue o, int change)
         {
+          TestValues t(o);
         }
     };
   
diff --git a/wiki/renderengine.html b/wiki/renderengine.html
index 96acf0937..3b1a5caf9 100644
--- a/wiki/renderengine.html
+++ b/wiki/renderengine.html
@@ -6618,7 +6618,7 @@ Thus no server and no network connection is needed. Simply open the file in your
  * see [[Homepage|http://tiddlywiki.com]], [[Wiki-Markup|http://tiddlywiki.org/wiki/TiddlyWiki_Markup]]
 
-
+
Simple time points are just like values and thus easy to change. The difficulties arise when time values are to be //quantised to an existing time grid.// The first relevant point to note is that for quantised time values, the effect of a change can be disproportional to the cause. Small changes below the threshold might be accumulated, and a tiny change might trigger a jump to the next grid point. While this might be annoying, the yet more complex questions arise when we acknowledge that the amount of the change itself might be related to a time grid.
 
 The problem with modification of quantised values highlights an inner contradiction or conflicting goals within the design
@@ -6664,11 +6664,12 @@ This results in the following combinations to be implemented:
 |~|Duration || -- |
 |~|~TimeSpan ||set start |
 |~|quantised ||set raw value |
-| Offset|Time ||offset the value |
-|~|Duration ||adjust by offset |
+| Duration|~TimeSpan ||set length |
+| Offset|Time || -- |
+|~|Duration || -- |
 |~|~TimeSpan ||adjust start/length |
 |~|quantised ||adjust raw value |
-| ~QuTime|any raw ||treat as raw |
+| ~QuTime|any raw ||treat as raw ~TimeValue |
 |~|~QuTime ||materialise, then set raw |
 | increment|~QuTime ||snap to grid point |
 As rationale, consider the following
@@ -6684,6 +6685,8 @@ As rationale, consider the following
 
 {{red{Question 3/11}}} -- can we avoid accepting mutations for Time (and thus only mutate ~TimeSpan)?
 Rationale: allowing mutations for Time bears the danger of making ~TimeVar obsolete
+* //4/11 yes, implementing it now this way....//
+* we have ~TimeVar, so the only thing that //needs// to be mutable is a whole object &rArr; ~TimeSpan
 
 !!!usage considerations
 Question is: how fine grained and configurable needs this to be?

From 9364d717b069ca0f4097431bd8b7a9da0fdbc257 Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Mon, 4 Apr 2011 00:41:38 +0200
Subject: [PATCH 05/28] implement total order on time intervals

---
 src/lib/time/timevalue.hpp         | 22 +++++++++++++++++++---
 tests/lib/time/time-value-test.cpp | 16 +++++++---------
 2 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp
index 315f192cb..5ce03d679 100644
--- a/src/lib/time/timevalue.hpp
+++ b/src/lib/time/timevalue.hpp
@@ -312,7 +312,7 @@ namespace time {
         { }
       
       explicit
-      Duration (Time const& timeSpec)
+      Duration (TimeValue const& timeSpec)
         : Offset(Offset(timeSpec).abs())
         { }
       
@@ -347,16 +347,27 @@ namespace time {
    */
   class TimeSpan
     : public Time
+    , boost::totally_ordered
     {
       Duration dur_;
       
     public:
-      TimeSpan(TimeValue start, Duration length)
+      TimeSpan(TimeValue const& start, Duration const& length)
         : Time(start)
         , dur_(length)
         { }
       
-      TimeSpan(TimeValue start, FSecs(duration_in_secs))
+      TimeSpan(TimeValue const& start, Offset const& reference_distance)
+        : Time(start)
+        , dur_(reference_distance)
+        { }
+      
+      TimeSpan(TimeValue const& start, TimeValue const& end)
+        : Time(start)
+        , dur_(Offset(start,end))
+        { }
+      
+      TimeSpan(TimeValue const& start, FSecs(duration_in_secs))
         : Time(start)
         , dur_(duration_in_secs)
         { }
@@ -380,6 +391,11 @@ namespace time {
           TimeVar startPoint (*this);
           return (startPoint + dur_);
         }
+      
+      /// Supporting extended total order, based on start and interval length
+      friend bool operator== (TimeSpan const& t1, TimeSpan const& t2)  { return t1.t_==t2.t_ && t1.dur_==t2.dur_; }
+      friend bool operator<  (TimeSpan const& t1, TimeSpan const& t2)  { return t1.t_< t2.t_ ||
+                                                                               (t1.t_==t2.t_ && t1.dur_< t2.dur_);}
     };
   
   
diff --git a/tests/lib/time/time-value-test.cpp b/tests/lib/time/time-value-test.cpp
index 06cb251e7..27c4c1524 100644
--- a/tests/lib/time/time-value-test.cpp
+++ b/tests/lib/time/time-value-test.cpp
@@ -29,12 +29,10 @@
 
 #include 
 #include 
-//#include 
 #include 
 
 using boost::lexical_cast;
 using util::isnil;
-//using std::rand;
 using std::cout;
 using std::endl;
 using std::string;
@@ -312,10 +310,10 @@ namespace test{
       void
       compareTimeSpan (TimeValue org)
         {
-          TimeSpan span1 (Time(org), Duration(Time(org)));
-          TimeSpan span2 (Time(org), Offset(org, TimeValue(0)));
-          TimeSpan span3 (Time(org), FSecs(5,2));
-          TimeSpan span4 (Time(org), FSecs(5,-2));
+          TimeSpan span1 (Time(org), Time(org)+Time(org));            // using the distance between start and end point
+          TimeSpan span2 (Time(org), Offset(org, TimeValue(0)));      // note: the offset is taken absolute, as Duration
+          TimeSpan span3 (Time(org), FSecs(5,2));                     // Duration given explicitly, in seconds
+          TimeSpan span4 (Time(org), FSecs(5,-2));                    // again: the Duration is taken absolute
           
           CHECK (span1 == span2);
           CHECK (span2 == span1);
@@ -368,19 +366,19 @@ namespace test{
           CHECK (span3 > span3y);
           CHECK (span3.duration() > span3y.duration());
           CHECK (span3.start() == span3y.start());
-          CHECK (span3.end()   == span3y.end());
+          CHECK (span3.end()    > span3y.end());
           CHECK (Time(span3)   == Time(span3y));
           
           CHECK (span3 < span3z);
           CHECK (span3.duration() > span3z.duration());
           CHECK (span3.start() < span3z.start());
-          CHECK (span3.end()   < span3z.end());
+          CHECK (span3.end()  != span3z.end());        // it's shorter, and org can be random, so that's all we know
           CHECK (Time(span3)   < Time(span3z));
           
           CHECK (span3y < span3z);
           CHECK (span3y.duration() == span3z.duration());
           CHECK (span3y.start() < span3z.start());
-          CHECK (span3.end()   < span3z.end());
+          CHECK (span3y.end()  < span3z.end());
           CHECK (Time(span3)  < Time(span3z));
         }
     };

From 4d6bb3d54c43da5162c9186a4dcdca21dd45bd57 Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Mon, 4 Apr 2011 02:07:09 +0200
Subject: [PATCH 06/28] design outline for a limited time::Mutation capability

---
 src/lib/time/mutation.cpp             |  49 ++++++++++++
 src/lib/time/mutation.hpp             | 108 ++++++++++++++++++++++++++
 src/lib/time/timequant.hpp            |   4 +
 src/lib/time/timevalue.hpp            |  12 ++-
 tests/lib/time/time-mutation-test.cpp |   2 +
 wiki/renderengine.html                |   9 ++-
 6 files changed, 181 insertions(+), 3 deletions(-)
 create mode 100644 src/lib/time/mutation.cpp
 create mode 100644 src/lib/time/mutation.hpp

diff --git a/src/lib/time/mutation.cpp b/src/lib/time/mutation.cpp
new file mode 100644
index 000000000..e2f5b1ae4
--- /dev/null
+++ b/src/lib/time/mutation.cpp
@@ -0,0 +1,49 @@
+/*
+  Mutation  -  changing and adjusting time values
+
+  Copyright (C)         Lumiera.org
+    2011,               Hermann Vosseler 
+
+  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 "lib/time/timevalue.hpp"
+#include "lib/time/mutation.hpp"
+//#include "lib/time.h"
+//#include "lib/util.hpp"
+
+//#include 
+
+//using std::string;
+//using util::unConst;
+//using util::floorwrap;
+
+
+namespace lib {
+namespace time {
+  
+  
+  Mutation::~Mutation()   { }  // emit VTable here....
+  
+  
+  /** */
+  
+  
+  
+  
+}} // lib::time
+  
diff --git a/src/lib/time/mutation.hpp b/src/lib/time/mutation.hpp
new file mode 100644
index 000000000..b4d6961df
--- /dev/null
+++ b/src/lib/time/mutation.hpp
@@ -0,0 +1,108 @@
+/*
+  MUTATION.hpp  -  changing and adjusting time values
+
+  Copyright (C)         Lumiera.org
+    2011,               Hermann Vosseler 
+
+  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.
+
+*/
+
+
+/** @file mutation.hpp
+ ** Modifying time and timecode values.
+ ** Basically, time values are designed to be immutable. Under some circumstances
+ ** we need a means to change existing time values though. Moreover, actually performing
+ ** these changes gets quite involved at the implementation level, due to time / timecode
+ ** values being \em quantised (grid aligned). To keep this implementation logic separate
+ ** from the simple time values, and to allow for later extensions and more elaborate
+ ** change schemes, we choose the visitor pattern for implementing these
+ ** time mutating operations.
+ ** 
+ ** \par usage situations
+ ** 
+ ** There are four basic usage situations causing an existing time value to be mutated:
+ ** 
+ ** - dragging an object or object boundary (resizing)
+ ** - nudging
+ ** - placing an object (e.g. a clip)
+ ** - update of a timecode display in the GUI
+ ** 
+ ** All these include sending a change message to the original time spec, while the
+ ** actual content of the change message determines the concrete behaviour. Especially,
+ ** when the target time to be changed or the message content are quantised, an interference
+ ** between multiple time grids may be happen, possibly resulting in a re-quantisation of
+ ** the message into the target object's own time grid.
+ ** 
+ ** @todo WIP-WIP-WIP
+ **
+ */
+
+#ifndef LIB_TIME_MUTATION_H
+#define LIB_TIME_MUTATION_H
+
+#include "lib/time/timevalue.hpp"
+//#include "lib/symbol.hpp"
+
+#include 
+//#include 
+//#include 
+//#include 
+
+
+namespace lib {
+namespace time {
+  
+//using std::string;
+//using lib::Literal;
+  
+  class QuTime;
+  
+  /**
+   * Interface: an opaque change imposed onto some time value.
+   * 
+   * @see time::TimeSpan#accept(Mutation const&)
+   * @todo WIP-WIP-WIP
+   */
+  class Mutation
+    : boost::noncopyable
+    {
+    public:
+      virtual ~Mutation();
+      
+      virtual void change (Duration&)  const  =0;
+      virtual void change (TimeSpan&)  const  =0;
+      virtual void change (QuTime&)    const  =0;
+      
+      /* === convenience shortcuts for simple cases === */
+      
+      static void changeTime (Time);
+      static void changeDuration (Duration);
+      static void nudge (int adjustment);
+    };
+  
+  
+  /* === defining the visitation responses === */
+    
+  inline void Duration::accept (Mutation const& muta) { muta.change (*this); }
+  inline void TimeSpan::accept (Mutation const& muta) { muta.change (*this); }
+  
+#ifdef LIB_TIME_TIMEQUQNT_H
+  inline void QuTime::accept   (Mutation const& muta) { muta.change (*this); }
+#endif
+  
+  
+}} // lib::time
+#endif
diff --git a/src/lib/time/timequant.hpp b/src/lib/time/timequant.hpp
index 0580d2e6f..b12a761a3 100644
--- a/src/lib/time/timequant.hpp
+++ b/src/lib/time/timequant.hpp
@@ -75,6 +75,10 @@ namespace time {
       template
       void
       castInto (TC& timecode)  const;
+      
+      /** receive change message, which
+          might cause re-quantisation */
+      void accept (Mutation const&);
     };
   
   
diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp
index 5ce03d679..7fd185a62 100644
--- a/src/lib/time/timevalue.hpp
+++ b/src/lib/time/timevalue.hpp
@@ -293,12 +293,14 @@ namespace time {
   
   
   class TimeSpan;
+  class Mutation;
   
   /**
    * Duration is the internal Lumiera time metric.
    * It is an absolute (positive) value, but can be
-   * promoted from an offset. Like plain TimeValue,
-   * Duration is an immutable value.
+   * promoted from an offset. While Duration generally
+   * is treated as immutable value, there is the 
+   * possibility to send a \em Mutation message.
    */
   class Duration
     : public Offset
@@ -325,6 +327,8 @@ namespace time {
       Duration (ulong count, FrameRate const& fps);
       
       static const Duration NIL;
+      
+      void accept (Mutation const&);
     };
   
   
@@ -392,6 +396,10 @@ namespace time {
           return (startPoint + dur_);
         }
       
+      /** may change start / duration */
+      void accept (Mutation const&);
+      
+      
       /// Supporting extended total order, based on start and interval length
       friend bool operator== (TimeSpan const& t1, TimeSpan const& t2)  { return t1.t_==t2.t_ && t1.dur_==t2.dur_; }
       friend bool operator<  (TimeSpan const& t1, TimeSpan const& t2)  { return t1.t_< t2.t_ ||
diff --git a/tests/lib/time/time-mutation-test.cpp b/tests/lib/time/time-mutation-test.cpp
index e7c3dbe5f..7b9e21622 100644
--- a/tests/lib/time/time-mutation-test.cpp
+++ b/tests/lib/time/time-mutation-test.cpp
@@ -82,11 +82,13 @@ namespace test{
       struct TestValues
         {
           TimeVar var;
+          Duration dur;
           TimeSpan span;
           QuTime quant;
           
           TestValues (TimeValue o)
             : var(o)
+            , dur(o)
             , span(o, Offset(o))
             , quant(o, "test_grid")
             { }
diff --git a/wiki/renderengine.html b/wiki/renderengine.html
index 3b1a5caf9..0f414c368 100644
--- a/wiki/renderengine.html
+++ b/wiki/renderengine.html
@@ -6618,7 +6618,7 @@ Thus no server and no network connection is needed. Simply open the file in your
  * see [[Homepage|http://tiddlywiki.com]], [[Wiki-Markup|http://tiddlywiki.org/wiki/TiddlyWiki_Markup]]
 
-
+
Simple time points are just like values and thus easy to change. The difficulties arise when time values are to be //quantised to an existing time grid.// The first relevant point to note is that for quantised time values, the effect of a change can be disproportional to the cause. Small changes below the threshold might be accumulated, and a tiny change might trigger a jump to the next grid point. While this might be annoying, the yet more complex questions arise when we acknowledge that the amount of the change itself might be related to a time grid.
 
 The problem with modification of quantised values highlights an inner contradiction or conflicting goals within the design
@@ -6665,6 +6665,7 @@ This results in the following combinations to be implemented:
 |~|~TimeSpan ||set start |
 |~|quantised ||set raw value |
 | Duration|~TimeSpan ||set length |
+|~|Duration  ||set length |
 | Offset|Time || -- |
 |~|Duration || -- |
 |~|~TimeSpan ||adjust start/length |
@@ -6687,12 +6688,18 @@ As rationale, consider the following
 Rationale: allowing mutations for Time bears the danger of making ~TimeVar obsolete
 * //4/11 yes, implementing it now this way....//
 * we have ~TimeVar, so the only thing that //needs// to be mutable is a whole object &rArr; ~TimeSpan
+* err, because MObject will include a Duration separate from the start time in the Placement, Duration needs to be mutable too
 
 !!!usage considerations
 Question is: how fine grained and configurable needs this to be?
 * for example, when moving a clip taken from 50fps media, the new position might be quantised to the 50fps grid established by the media, while the target timeline runs with 25fps, allowing for finer adjustments based on the intermediate frames present in the source material.
 * likely we need a "nudge by unit(s)"
 
+!!! design draft
+The special focus of this problem seems to lead itself to a __visitor pattern__ based implementation. Because the basic hierarchy of applicable types is fixed, but the behaviour is open ended (and not yet fully determined). A conventional implementation would scatter this behaviour over all the time entities, thus making it hard to understand and reason about. The classical ~GoF cyclic visitor solution to the contrary allows us to arrange closely related behaviour into thematically grouped visitor classes. As a plus, the concrete action can be bound dynamically, allowing for more flexibility when it comes to dealing with the intricate situations when a quantised time span (= a clip) recieves a quantised mutation (= is re-alligend to a possibly different frame grid)
+
+Thus the possibly mutalble time entities get an {{{accept(time::Mutation)}}} function, which is defined to reflect back into the (virtual) visitation functions of the Mutation Interface. Additionally, we provide some static convenience shortcuts right in the
+Mutation interface, performing the trivial mutations.
 
From eb733df1bb781449d4ef1f3e81a7c2c1b4977f1a Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 4 Apr 2011 02:43:01 +0200 Subject: [PATCH 07/28] proof-of-concept: implement simple mutation --- src/lib/time/mutation.cpp | 82 ++++++++++++++++++++++++++++++++++++++ src/lib/time/mutation.hpp | 6 +++ src/lib/time/timevalue.hpp | 3 ++ 3 files changed, 91 insertions(+) diff --git a/src/lib/time/mutation.cpp b/src/lib/time/mutation.cpp index e2f5b1ae4..070386c62 100644 --- a/src/lib/time/mutation.cpp +++ b/src/lib/time/mutation.cpp @@ -21,7 +21,9 @@ * *****************************************************/ +#include "lib/error.hpp" #include "lib/time/timevalue.hpp" +#include "lib/time/timequant.hpp" #include "lib/time/mutation.hpp" //#include "lib/time.h" //#include "lib/util.hpp" @@ -32,15 +34,95 @@ //using util::unConst; //using util::floorwrap; +namespace error = lumiera::error; namespace lib { namespace time { + LUMIERA_ERROR_DEFINE (INVALID_MUTATION, "Changing a time value in this way was not designated"); + + Mutation::~Mutation() { } // emit VTable here.... + /** @internal actually force a change + * into a target time entity to mutate. + * Mutation is declared fried to TimeValue + * and thus is allowed to influence the basic + * value stored in each time entity + */ + void + Mutation::imposeChange (TimeValue& target, TimeValue valueToSet) + { + target = valueToSet; + } + + + + + /** + * concrete time value mutation: + * impose fixed new start time. + */ + class SetNewStartTime + : public Mutation + { + TimeValue newTime_; + + virtual void + change (Duration&) const + { + throw error::Logic ("mutating the start point of a pure Duration doesn't make sense" + , LUMIERA_ERROR_INVALID_MUTATION); + } + + + virtual void + change (TimeSpan& target) const + { + imposeChange (target, newTime_); + } + + + /** @note the re-quantisation happens automatically + * when the (changed) QuTime is materialised */ + virtual void + change (QuTime& target) const + { + imposeChange (target, newTime_); + } + + + public: + explicit + SetNewStartTime (TimeValue t) + : newTime_(t) + { } + }; + + + /** */ + void + Mutation::changeTime (Time newStartTime) + { + UNIMPLEMENTED ("perform a trivial mutation to set a new fixed time value"); + } + + + void + Mutation::changeDuration (Duration) + { + UNIMPLEMENTED ("perform a trivial mutation to set a new Duration"); + } + + + void + Mutation::nudge (int adjustment) + { + UNIMPLEMENTED ("Nudge by a predefined nudge amount"); + } diff --git a/src/lib/time/mutation.hpp b/src/lib/time/mutation.hpp index b4d6961df..bbb325911 100644 --- a/src/lib/time/mutation.hpp +++ b/src/lib/time/mutation.hpp @@ -53,6 +53,7 @@ #ifndef LIB_TIME_MUTATION_H #define LIB_TIME_MUTATION_H +#include "lib/error.hpp" #include "lib/time/timevalue.hpp" //#include "lib/symbol.hpp" @@ -68,6 +69,8 @@ namespace time { //using std::string; //using lib::Literal; + LUMIERA_ERROR_DECLARE (INVALID_MUTATION); ///< Changing a time value in this way was not designated + class QuTime; /** @@ -91,6 +94,9 @@ namespace time { static void changeTime (Time); static void changeDuration (Duration); static void nudge (int adjustment); + + protected: + static void imposeChange (TimeValue&, TimeValue); }; diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp index 7fd185a62..b6fc7d28f 100644 --- a/src/lib/time/timevalue.hpp +++ b/src/lib/time/timevalue.hpp @@ -74,6 +74,9 @@ namespace time { return *this; } + /** some subclasses may receive modification messages */ + friend class Mutation; + public: /** explicit limit of allowed time range */ static gavl_time_t limited (gavl_time_t raw); From 0aaa010350799e7b764bf6609a7be734c226ccd6 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 20 Apr 2011 19:11:08 +0200 Subject: [PATCH 08/28] WIP draft how polymorphic value objects are expected to behave --- tests/lib/polymorphic-value-test.cpp | 208 +++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 tests/lib/polymorphic-value-test.cpp diff --git a/tests/lib/polymorphic-value-test.cpp b/tests/lib/polymorphic-value-test.cpp new file mode 100644 index 000000000..d56e2a122 --- /dev/null +++ b/tests/lib/polymorphic-value-test.cpp @@ -0,0 +1,208 @@ +/* + PolymorphicValue(Test) - verify handling of opaque polymorphic values + + Copyright (C) Lumiera.org + 2011, Hermann Vosseler + + 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 "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/util.hpp" +#include "lib/util-foreach.hpp" + +#include "lib/opaque-holder.hpp" +//#include "lib/bool-checkable.hpp" + +#include +//#include +#include + + +namespace lib { +namespace test{ + + using ::Test; +// using util::isnil; + using util::for_each; + using util::isSameObject; +// using lumiera::error::LUMIERA_ERROR_INVALID; + using lumiera::error::LUMIERA_ERROR_ASSERTION; + + using std::vector; +// using std::cout; +// using std::endl; + + namespace { // test dummy hierarchy + // Note: largely varying space requirements + // correct function depending on concrete class + + + struct Interface + { + virtual ~Interface() {} + virtual long apiFunc() =0; + + virtual long& localSum() =0; + + bool + operator== (Interface const& o) + { + return localSum() == o.localSum(); + } + }; + + + const uint MAX_RAND = 1000; + const uint MAX_SIZ = sizeof(long[111]); + + long _checkSum = 0; + long _callSum = 0; + uint _created = 0; + + + template + struct Imp : Interface + { + long localData_[ii]; + + Imp() + { + REQUIRE (0 < ii); + localSum() = 0; + note(ii); + ++ _created; + } + + ~Imp() + { + note (-1 * localSum()); + CHECK (0 == localSum()); + } + + + long + apiFunc() + { + long rr = ii * (rand() % MAX_RAND); + note (rr); + _callSum += rr; + return rr; + } + + long& + localSum() + { + return localData[ii-1]; + } + + void + note (long markerValue) + { + localSum() += markerValue; + _checkSum += markerValue; + } + }; + + + /** maximum additional storage maybe wasted + * due to alignment of the contained object + * within the embedded Holder/Buffer + */ + const size_t _ALIGN_ = sizeof(size_t); + + } + + typedef PolymorphicValue PolyVal; + typedef vector TestList; + + + + /********************************************************************************** + * @test build a bunch of PolymorphicValue objects. Handle them like copyable + * value objects, without knowing the exact implementation type; moreover + * execute implementation internals only disclosed to the concrete subtype. + * Verify correctness through checksums. + */ + class PolymorphicValue_test : public Test + { + + virtual void + run (Arg) + { + _checkSum = 0; + _callSum = 0; + _created = 0; + + { + TestList objs = createOpaqueValues (); + for_each (objs, operate); + } + CHECK (0 == _checksum); // all dead + + verifyOverrunProtection(); + } + + + TestList + createOpaqueValues () + { + TestList list; + list.push_back (PolyVal::build > () ); + list.push_back (PolyVal::build > () ); + list.push_back (PolyVal::build >() ); + list.push_back (PolyVal::build > () ); + list.push_back (PolyVal::build > () ); + return list; + } //note: copy + + + static void + operate (PolyVal& elm) + { + PolyVal myLocalVal(elm); + CHECK (elm == myLocalVal); + + long prevSum = _callSum; + long randVal = myLocalVal.apiFunc(); + CHECK (prevSum + randVal == _callSum); + CHECK (elm != myLocalVal); + + elm = myLocalVal; + CHECK (elm == myLocalVal); + CHECK (!isSameObject (elm, myLocalVal)); + + CHECK (sizeof(myLocalVal) <= MAX_SIZ + _ALIGN_); + } + + + void + verifyOverrunProtection() + { +#if false ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #537 : restore throwing ASSERT + VERIFY_ERROR (ASSERTION, PolyVal::build >() ); +#endif ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #537 : restore throwing ASSERT + } + }; + + + LAUNCHER (PolymorphicValue_test, "unit common"); + + +}} // namespace lib::test From 9aa601d0048bdf0c67aff83ba904860122b36abb Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 21 Apr 2011 19:35:09 +0200 Subject: [PATCH 09/28] WIP first implementation version of PolymorphicValue holder template --- src/lib/polymorphic-value.hpp | 287 +++++++++++++++++++++++++++ tests/lib/polymorphic-value-test.cpp | 21 +- 2 files changed, 299 insertions(+), 9 deletions(-) create mode 100644 src/lib/polymorphic-value.hpp diff --git a/src/lib/polymorphic-value.hpp b/src/lib/polymorphic-value.hpp new file mode 100644 index 000000000..bd343342f --- /dev/null +++ b/src/lib/polymorphic-value.hpp @@ -0,0 +1,287 @@ +/* + POLYMORPHIC-VALUE.hpp - building opaque polymorphic value objects + + Copyright (C) Lumiera.org + 2011, Hermann Vosseler + + 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. + +*/ + +/** @file polymorphic-value.hpp + ** A mechanism to allow for opaque polymorphic value objects. + ** This template helps to overcome a problem frequently encountered in + ** C++ programming, based on the fundamental design of C++, favouring + ** explicit low-level control, copying of values and strict ctor-dtor pairs. + ** Many object oriented design patterns build on polymorphism, where the + ** actual type of an object isn't disclosed and collaborations rely on + ** common interfaces. This doesn't mix well with the emphasis the C/C++ + ** language puts on efficient handling of small data elements as values + ** and explicit control of the used storage; indeed several of the modern + ** object oriented and functional programming techniques more or less + ** assume the presence of a garbage collector or similar mechanism, + ** so 'objects' need just to be mentioned by reference. + ** + ** In C++ to employ many of the well known techniques, you're more or less + ** bound to explicitly put the objects somewhere in hash allocated memory + ** and then pass an interface pointer or reference into the actual algorithm. + ** Often, this hinders a design based on constant values and small descriptor + ** objects used inline, thus forcing into unnecessarily complex and heavyweight + ** alternatives. While it's certainly pointless to fight the fundamental nature + ** of the programming language, we may try to pull some (template based) trickery + ** to make polymorphic objects fit better with the handling of small copyable + ** value objects. Especially, C++ gives a special meaning to passing parameters + ** as \c const& -- typically constructing an anonymous temporary object conveniently + ** just for passing an abstraction barrier (while the optimiser can be expected to + ** remove this barrier and the accompanying nominal copy operations altogether in + ** the generated code). Thus, the ability to return a polymorphic object from a + ** factory or configuration function by value would open a lot of + ** straight forward design possibilities and concise formulations. + ** + ** \par how to build a copyable value without knowing it's layout in detail + ** + ** So the goal is to build a copyable and assignable type with value semantics, + ** without disclosing the actual implementation and object layout at the usage site. + ** This seemingly contradictory can be achieved, provided that + ** - the space occupied by the actual implementation object is limited, + ** so it can be placed as binary data into an otherwise opaque holder buffer + ** - and the actual implementation object assists with copying and cloning + ** itself, observing the actual implementation type and layout + ** + ** @see polymorphic-value-test.cpp + ** @see opaque-holder.hpp other similar opaque inline buffer templates + ** @see lib::time::Mutation usage example + */ + + +#ifndef LIB_POLYMORPHIC_VALUE_H +#define LIB_POLYMORPHIC_VALUE_H + + +#include "lib/error.hpp" +//#include "lib/bool-checkable.hpp" +//#include "lib/access-casted.hpp" +//#include "lib/util.hpp" + +//#include + + +namespace lib { + +// using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; +// using util::isSameObject; +// using util::unConst; + + + namespace { // implementation helpers... + + + } + + + + + + /** + * Template to build polymorphic value objects. + * Inline buffer with value semantics, yet holding and owning an object + * while concealing the concrete type, exposing only the public interface. + * Access to the contained object is by implicit conversion to this public + * interface. The actual implementation object might be placed into the + * buffer through a builder function; later, this buffer may be copied + * and passed on without knowing the actual contained type. + * + * For using PolymorphicValue, several \b assumptions need to be fulfilled + * - any instance placed into OpaqueHolder is below the specified maximum size + * - the caller cares for thread safety. No concurrent get calls while in mutation! + * + * @warning when a create or copy-into operation fails with exception, the whole + * PolymorphicValue object is in undefined state and must not be used further. + */ + template + < class IFA ///< the nominal Base/Interface class for a family of types + , size_t siz ///< maximum storage required for the targets to be held inline + > + class PolymorphicValue + { + mutable char buf_[siz]; + + template + IMP& + access() const + { + return reinterpret_cast (buf_); + } + + void + destroy() + { + access().~IFA(); + } + +// REQUIRE (siz >= sizeof(IMP)); + + template + PolymorphicValue (IMP*) + { + new(&buf_) IMP(); + } + + template + PolymorphicValue (IMP*, A1& a1) + { + new(&buf_) IMP (a1); + } + + template + PolymorphicValue (IMP*, A1& a1, A2& a2) + { + new(&buf_) IMP (a1,a2); + } + + template + PolymorphicValue (IMP*, A1& a1, A2& a2, A3& a3) + { + new(&buf_) IMP (a1,a2,a3); + } + + + class HandlingAdapter + { + public: + virtual ~HandlingAdapter() { }; + virtual void cloneInto (void* targetBuffer) const =0; + virtual void copyInto (IFA& targetBase) const =0; + }; + + template + class Adapter + : public IMP + , public HandlingAdapter + { + virtual void + cloneInto (void* targetBuffer) const + { + new(targetBuffer) Adapter(*this); + } + + virtual void + copyInto (IFA& targetBase) const + { + REQUIRE (INSTANCEOF (Adapter, &targetBase)); + Adapter& target = static_cast (targetBase); + target = (*this); + } + + public: + /* using default copy and assignment */ + Adapter() : IMP() { } + + template + Adapter (A1& a1) : IMP(a1) { } + + template + Adapter (A1& a1, A2& a2) : IMP(a1,a2) { } + + template + Adapter (A1& a1, A2& a2, A3& a3) : IMP(a1,a2,a3) { } + }; + + + HandlingAdapter& + accessHandlingInterface () const + { + IFA& bufferContents = access(); + REQUIRE (INSTANCEOF (HandlingAdapter, &bufferContents)); + HandlingAdapter& hap = dynamic_cast (bufferContents); + return hap; + } + + public: + operator IFA& () + { + return access(); + } + operator IFA const& () const + { + return access(); + } + + ~PolymorphicValue() + { + destroy(); + } + + PolymorphicValue (PolymorphicValue const& o) + { + o.accessHandlingInterface().cloneInto (&buf_); + } + + PolymorphicValue& + operator= (PolymorphicValue const& o) + { + o.accessHandlingInterface().copyInto (this->access()); + return *this; + } + + template + static PolymorphicValue + build () + { + Adapter* type_to_build_in_buffer; + return PolymorphicValue (type_to_build_in_buffer); + } + + template + static PolymorphicValue + build (A1& a1) + { + Adapter* type_to_build_in_buffer; + return PolymorphicValue (type_to_build_in_buffer, a1); + } + + template + static PolymorphicValue + build (A1& a1, A2& a2) + { + Adapter* type_to_build_in_buffer; + return PolymorphicValue (type_to_build_in_buffer, a1,a2); + } + + template + static PolymorphicValue + build (A1& a1, A2& a2, A3& a3) + { + Adapter* type_to_build_in_buffer; + return PolymorphicValue (type_to_build_in_buffer, a1,a2,a3); + } + + friend bool + operator== (PolymorphicValue const& v1, PolymorphicValue const& v2) + { + return v1.access() == v2.access(); + } + friend bool + operator!= (PolymorphicValue const& v1, PolymorphicValue const& v2) + { + return ! (v1 == v2); + } + }; + + + + +} // namespace lib +#endif diff --git a/tests/lib/polymorphic-value-test.cpp b/tests/lib/polymorphic-value-test.cpp index d56e2a122..755cbe3a9 100644 --- a/tests/lib/polymorphic-value-test.cpp +++ b/tests/lib/polymorphic-value-test.cpp @@ -27,7 +27,7 @@ #include "lib/util.hpp" #include "lib/util-foreach.hpp" -#include "lib/opaque-holder.hpp" +#include "lib/polymorphic-value.hpp" //#include "lib/bool-checkable.hpp" #include @@ -41,11 +41,12 @@ namespace test{ using ::Test; // using util::isnil; using util::for_each; + using util::unConst; using util::isSameObject; // using lumiera::error::LUMIERA_ERROR_INVALID; using lumiera::error::LUMIERA_ERROR_ASSERTION; - using std::vector; +// using std::vector; // using std::cout; // using std::endl; @@ -62,15 +63,16 @@ namespace test{ virtual long& localSum() =0; bool - operator== (Interface const& o) + operator== (Interface const& o) const { - return localSum() == o.localSum(); + return unConst(this)->localSum() + == unConst(o).localSum(); } }; const uint MAX_RAND = 1000; - const uint MAX_SIZ = sizeof(long[111]); + const uint MAX_SIZ = sizeof(long[113]); /////////////////////TODO: using just 111 causes SEGV ---> suspect the HandlingAdapter mixin to require additional storage long _checkSum = 0; long _callSum = 0; @@ -109,7 +111,7 @@ namespace test{ long& localSum() { - return localData[ii-1]; + return localData_[ii-1]; } void @@ -130,7 +132,7 @@ namespace test{ } typedef PolymorphicValue PolyVal; - typedef vector TestList; + typedef std::vector TestList; @@ -154,7 +156,7 @@ namespace test{ TestList objs = createOpaqueValues (); for_each (objs, operate); } - CHECK (0 == _checksum); // all dead + CHECK (0 == _checkSum); // all dead verifyOverrunProtection(); } @@ -180,7 +182,8 @@ namespace test{ CHECK (elm == myLocalVal); long prevSum = _callSum; - long randVal = myLocalVal.apiFunc(); + Interface& subject = myLocalVal; + long randVal = subject.apiFunc(); CHECK (prevSum + randVal == _callSum); CHECK (elm != myLocalVal); From a10e381cfaffa7d3181d5c120be21e08d4ad2025 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 22 Apr 2011 03:29:12 +0200 Subject: [PATCH 10/28] fix error in test logic need to adjust the checksum on copy :) --- tests/40components.tests | 5 +++ tests/lib/polymorphic-value-test.cpp | 58 ++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/tests/40components.tests b/tests/40components.tests index 7b3bdd31d..51b9c54bc 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -505,6 +505,11 @@ out: can_IterForEach : Yes END +TEST "Polymorphic value objects" PolymorphicValue_test < >(); + CHECK (prevSum+111 == _checkSum); // We got one primary ctor call + CHECK (prevCnt+1 <= _created); // Note: usually, the compiler optimises + CHECK (prevCnt+2 >= _created); // and skips the spurious copy-operation + CHECK (sizeof(PolyVal) > sizeof(Imp<111>)); + Interface& embedded = val; + CHECK (isSameObject(embedded,val)); + CHECK (INSTANCEOF(Imp<111>, &embedded)); + } + + void verifyOverrunProtection() { From 710ae8fa0fa2f396ab8743b1b17aac5571138734 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 23 Apr 2011 20:19:29 +0200 Subject: [PATCH 11/28] Allow for improved performance in special cases Using a policy based switch in case the client interface collaborates and provides copy operations; in this case, a direct static downcast instead of the expensive indirect (dynamic) cast to the management interface can be employed. --- src/lib/polymorphic-value.hpp | 145 +++++++++++++++++++++++---- tests/lib/polymorphic-value-test.cpp | 2 +- 2 files changed, 125 insertions(+), 22 deletions(-) diff --git a/src/lib/polymorphic-value.hpp b/src/lib/polymorphic-value.hpp index bd343342f..70a8bd35a 100644 --- a/src/lib/polymorphic-value.hpp +++ b/src/lib/polymorphic-value.hpp @@ -23,7 +23,7 @@ /** @file polymorphic-value.hpp ** A mechanism to allow for opaque polymorphic value objects. ** This template helps to overcome a problem frequently encountered in - ** C++ programming, based on the fundamental design of C++, favouring + ** C++ programming, based on the fundamental design of C++, which favours ** explicit low-level control, copying of values and strict ctor-dtor pairs. ** Many object oriented design patterns build on polymorphism, where the ** actual type of an object isn't disclosed and collaborations rely on @@ -35,7 +35,7 @@ ** so 'objects' need just to be mentioned by reference. ** ** In C++ to employ many of the well known techniques, you're more or less - ** bound to explicitly put the objects somewhere in hash allocated memory + ** bound to explicitly put the objects somewhere in heap allocated memory ** and then pass an interface pointer or reference into the actual algorithm. ** Often, this hinders a design based on constant values and small descriptor ** objects used inline, thus forcing into unnecessarily complex and heavyweight @@ -46,19 +46,54 @@ ** as \c const& -- typically constructing an anonymous temporary object conveniently ** just for passing an abstraction barrier (while the optimiser can be expected to ** remove this barrier and the accompanying nominal copy operations altogether in - ** the generated code). Thus, the ability to return a polymorphic object from a - ** factory or configuration function by value would open a lot of + ** the generated code). Consequently the ability to return a polymorphic object + ** from a factory or configuration function by value would open a lot of ** straight forward design possibilities and concise formulations. ** ** \par how to build a copyable value without knowing it's layout in detail ** ** So the goal is to build a copyable and assignable type with value semantics, ** without disclosing the actual implementation and object layout at the usage site. - ** This seemingly contradictory can be achieved, provided that + ** This seemingly contradictory goal can be achieved, provided that ** - the space occupied by the actual implementation object is limited, ** so it can be placed as binary data into an otherwise opaque holder buffer ** - and the actual implementation object assists with copying and cloning - ** itself, observing the actual implementation type and layout + ** itself, observing the actual implementation data layout + ** + ** The PolymorphicValue template implements this idea, by exposing an copyable + ** container with value semantics to the client code. On instantiation, a common + ** base interface for the actual value objects needs to be provided; the resulting + ** instance will be automatically convertible to this interface. Obviously this + ** common interface must be an ABC or at least contain some virtual functions. + ** Moreover, the PolymorphicValue container provides static builder functions, + ** allowing to place a concrete instance of a subclass into the content buffer. + ** After construction, the actual type of this instance will be forgotten + ** (``type erasure''), but because the embedded vtable, on access the + ** proper implementation functions will be invoked. + ** + ** Expanding on that pattern, the copying and cloning operations of the whole + ** container can be implemented by forwarding to appropriate virtual functions + ** on the embedded payload (implementation) object -- the concrete implementation + ** of these virtual functions can be assumed to know the real type and thus be + ** able to invoke the correct copy ctor or assignment operator. For this to + ** work, the interface needs to expose those copy and clone operations somehow + ** as virtual functions. There are two alternatives to get at this point: + ** - in the general case, the common base interface doesn't expose such operations. + ** Thus we need to mix in an additional \em management interface; this + ** can be done by \em subclassing the desired implementation type, because + ** this concrete type is irrelevant after finishing the placement constructor. + ** In order to re-access this management interface, so to be able to invoke + ** the copy or clone operations, we need to do an elaborate re-cast operation, + ** first going down to the leaf type and then back up into the mixed in + ** management interface. Basically this operation is performed by using + ** an \c dynamic_cast(bufferContents) + ** - but when the used client types provide some collaboration and implement + ** this management interface either directly on the API or as an immediate + ** sub-interface, then this copy/management interface is located within the + ** direct inheritance chain and can be reached by a simple \c static_cast. + ** Indeed, as we're just using a different meaning of the VTable, only a + ** single indirection (virtual function call) is required at runtime in + ** this case to invoke the copy ctor or assignment operator. ** ** @see polymorphic-value-test.cpp ** @see opaque-holder.hpp other similar opaque inline buffer templates @@ -71,11 +106,13 @@ #include "lib/error.hpp" +#include "lib/meta/duck-detector.hpp" //#include "lib/bool-checkable.hpp" //#include "lib/access-casted.hpp" //#include "lib/util.hpp" //#include +#include namespace lib { @@ -85,8 +122,74 @@ namespace lib { // using util::unConst; - namespace { // implementation helpers... + namespace polyvalue { // implementation helpers... + using boost::enable_if; + using lumiera::Yes_t; + using lumiera::No_t; + + struct EmptyBase{ }; + + template + class CopySupport + : public BA + { + public: + virtual ~CopySupport() { }; + virtual void cloneInto (void* targetBuffer) const =0; + virtual void copyInto (IFA& targetBase) const =0; + }; + + + + + template + class exposes_copySupportFunctions + { + + META_DETECT_FUNCTION(void, cloneInto, (void*) const); + + public: + enum{ value = HasFunSig_cloneInto::value + }; + }; + + + template + struct Trait + { + typedef CopySupport CopyAPI; + enum{ ADMIN_OVERHEAD = 2 * sizeof(void*) }; + + static CopyAPI& + accessCopyHandlingInterface (TY& bufferContents) + { + REQUIRE (INSTANCEOF (CopyAPI, &bufferContents)); + return dynamic_cast (bufferContents); + } + + typedef CopyAPI AdapterAttachment; + }; + + + template + struct Trait >::type> + { + typedef TY CopyAPI; + enum{ ADMIN_OVERHEAD = 1 * sizeof(void*) }; + + template + static CopyAPI& + accessCopyHandlingInterface (IFA& bufferContents) + { + REQUIRE (INSTANCEOF (CopyAPI, &bufferContents)); + return static_cast (bufferContents); + } + + typedef EmptyBase AdapterAttachment; + }; } @@ -112,10 +215,18 @@ namespace lib { */ template < class IFA ///< the nominal Base/Interface class for a family of types - , size_t siz ///< maximum storage required for the targets to be held inline + , size_t storage ///< maximum storage required for the target data to be held inline + , class CPY = IFA ///< special sub-interface to support copy operations > class PolymorphicValue { + + typedef polyvalue::Trait _Traits; + typedef typename _Traits::CopyAPI _CopyHandlingAdapter; + enum{ + siz = storage + _Traits::ADMIN_OVERHEAD + }; + mutable char buf_[siz]; template @@ -158,18 +269,10 @@ namespace lib { } - class HandlingAdapter - { - public: - virtual ~HandlingAdapter() { }; - virtual void cloneInto (void* targetBuffer) const =0; - virtual void copyInto (IFA& targetBase) const =0; - }; - template class Adapter : public IMP - , public HandlingAdapter + , public _Traits::AdapterAttachment { virtual void cloneInto (void* targetBuffer) const @@ -200,15 +303,15 @@ namespace lib { }; - HandlingAdapter& + _CopyHandlingAdapter& accessHandlingInterface () const { IFA& bufferContents = access(); - REQUIRE (INSTANCEOF (HandlingAdapter, &bufferContents)); - HandlingAdapter& hap = dynamic_cast (bufferContents); - return hap; + _CopyHandlingAdapter& hap = _Traits::accessCopyHandlingInterface (bufferContents); + return hap; ////TODO cleanup unnecessary temporary } + public: operator IFA& () { diff --git a/tests/lib/polymorphic-value-test.cpp b/tests/lib/polymorphic-value-test.cpp index 7674b677e..26570c8ff 100644 --- a/tests/lib/polymorphic-value-test.cpp +++ b/tests/lib/polymorphic-value-test.cpp @@ -216,7 +216,7 @@ namespace test{ CHECK (elm == myLocalVal); CHECK (!isSameObject (elm, myLocalVal)); - CHECK (sizeof(myLocalVal) <= MAX_SIZ + _ALIGN_); + CHECK (sizeof(myLocalVal) <= MAX_SIZ + polyvalue::Trait::ADMIN_OVERHEAD + _ALIGN_); } From 6daf14211ba2168a0fedcae9d30f22c278148125 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 24 Apr 2011 20:28:36 +0200 Subject: [PATCH 12/28] Finish the PolymorphicValue support template --- src/lib/polymorphic-value.hpp | 172 ++++++++++++++++++++------- tests/lib/polymorphic-value-test.cpp | 75 ++++++++---- 2 files changed, 186 insertions(+), 61 deletions(-) diff --git a/src/lib/polymorphic-value.hpp b/src/lib/polymorphic-value.hpp index 70a8bd35a..94a4b4b09 100644 --- a/src/lib/polymorphic-value.hpp +++ b/src/lib/polymorphic-value.hpp @@ -94,6 +94,43 @@ ** Indeed, as we're just using a different meaning of the VTable, only a ** single indirection (virtual function call) is required at runtime in ** this case to invoke the copy ctor or assignment operator. + ** Thus, in this latter (optimal) case, the fact that PolymorphicValue allows + ** to conceal the actual implementation type comes with zero runtime overhead, + ** compared with direct usage of a family of polymorphic types (with VTable). + ** + ** \par using polymorphic value objects + ** + ** To start with, we need a situation where polymorphic treatment and type erasure + ** might be applicable. That is, we use a public API, and only that, in any client + ** code, while the concrete implementation is completely self contained. Thus, in + ** the intended use, the concrete implementation objects can be assembled once, + ** typically in a factory, and after that, no further knowledge of the actual + ** implementation type is required. All further use can be coded against + ** the exposed public API. + ** + ** Given such a situation, it might be desirable to conceal the whereabouts of + ** the implementation completely from the clients employing the generated objects. + ** For example, the actual implementation might rely on a complicated subsystem + ** with many compilation dependencies, and we don't want to expose all those + ** details on the public API. + ** + ** Now, to employ PolymorphicValue in such a situation, on the usage side (header): + ** - expose the public API, but not the implementation type of the objects + ** - define an instantiation of PolymorphicValue with this API + ** - be sure to define a hard wired size limit not to be exceeded by the + ** actual implementation objects (PolymorphicValue's ctor has an assertion + ** to verify this constraint) + ** - provide some kind of factory for the clients to get the actual polymorphic + ** value instances. Clients may then freely move and copy those objects, but + ** do not need to know anything about the actual implementation object layout + ** (it could be figured out using RTTI though) + ** + ** On the implementation side (separate compilation unit) + ** - include the definition of the PolymorphicValue instantiation (of course) + ** - define the implementation types to inherit from the public API + ** - implement the mentioned factory function, based on the static build + ** PolymorphicValue#build functions, using the actual implementation type + ** as parameter. ** ** @see polymorphic-value-test.cpp ** @see opaque-holder.hpp other similar opaque inline buffer templates @@ -107,22 +144,14 @@ #include "lib/error.hpp" #include "lib/meta/duck-detector.hpp" -//#include "lib/bool-checkable.hpp" -//#include "lib/access-casted.hpp" -//#include "lib/util.hpp" -//#include #include namespace lib { -// using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; -// using util::isSameObject; -// using util::unConst; - - namespace polyvalue { // implementation helpers... + namespace polyvalue { // implementation details... using boost::enable_if; using lumiera::Yes_t; @@ -130,8 +159,23 @@ namespace lib { struct EmptyBase{ }; - template + * (where \c Interface would be the public API for all these embedded + * implementation objects). + * Alternatively, it's also possible to place this CopySupport API as parent + * to the public API (it might even be completely absent, but then you'd need + * to provide an explicit specialisation of the Traits template to tell + * PolymorphicValue how to access the copy support functions.) + */ + template class CopySupport : public BA @@ -144,7 +188,10 @@ namespace lib { - + /** + * helper to detect presence of a function + * to support clone operations + */ template class exposes_copySupportFunctions { @@ -157,23 +204,38 @@ namespace lib { }; + /** + * traits template to deal with + * different ways to support copy operations. + * Default is no support by the API and implementation types. + * In this case, the CopySupport interface is mixed in at the + * level of the concrete implementation class and later on + * accessed through an \c dynamic_cast + */ template struct Trait { - typedef CopySupport CopyAPI; + typedef CopySupport CopyAPI; enum{ ADMIN_OVERHEAD = 2 * sizeof(void*) }; static CopyAPI& accessCopyHandlingInterface (TY& bufferContents) { REQUIRE (INSTANCEOF (CopyAPI, &bufferContents)); - return dynamic_cast (bufferContents); + return dynamic_cast (bufferContents); } typedef CopyAPI AdapterAttachment; }; + /** + * Special case when the embedded types support copying + * on the API level, e.g. there is a sub-API exposing a \c cloneInto + * function. In this case, the actual implementation classes can be + * instantiated as-is and the copy operations can be accessed by a + * simple \c static_cast without runtime overhead. + */ template struct Trait >::type> { @@ -185,13 +247,13 @@ namespace lib { accessCopyHandlingInterface (IFA& bufferContents) { REQUIRE (INSTANCEOF (CopyAPI, &bufferContents)); - return static_cast (bufferContents); + return static_cast (bufferContents); } typedef EmptyBase AdapterAttachment; }; - } + }//(End)implementation details @@ -205,7 +267,7 @@ namespace lib { * interface. The actual implementation object might be placed into the * buffer through a builder function; later, this buffer may be copied * and passed on without knowing the actual contained type. - * + * * For using PolymorphicValue, several \b assumptions need to be fulfilled * - any instance placed into OpaqueHolder is below the specified maximum size * - the caller cares for thread safety. No concurrent get calls while in mutation! @@ -227,57 +289,73 @@ namespace lib { siz = storage + _Traits::ADMIN_OVERHEAD }; + + /* === embedded object in buffer === */ + + /** Storage for embedded objects */ mutable char buf_[siz]; - template - IMP& - access() const + IFA& + accessEmbedded() const { - return reinterpret_cast (buf_); + return reinterpret_cast (buf_); } void - destroy() + destroyEmbedded() { - access().~IFA(); + accessEmbedded().~IFA(); } -// REQUIRE (siz >= sizeof(IMP)); template PolymorphicValue (IMP*) { + REQUIRE (siz >= sizeof(IMP)); new(&buf_) IMP(); } template PolymorphicValue (IMP*, A1& a1) { + REQUIRE (siz >= sizeof(IMP)); new(&buf_) IMP (a1); } template PolymorphicValue (IMP*, A1& a1, A2& a2) { + REQUIRE (siz >= sizeof(IMP)); new(&buf_) IMP (a1,a2); } template PolymorphicValue (IMP*, A1& a1, A2& a2, A3& a3) { + REQUIRE (siz >= sizeof(IMP)); new(&buf_) IMP (a1,a2,a3); } + /** + * Implementation Helper: supporting copy operations. + * Actually instances of this Adapter template are placed + * into the internal buffer, such that they both inherit + * from the desired implementation type and the copy + * support interface. The implementation of the + * concrete copy operations is provided here + * forwarding to the copy operations + * of the implementation object. + */ template class Adapter : public IMP - , public _Traits::AdapterAttachment + , public _Traits::AdapterAttachment // mix-in, might be empty { virtual void cloneInto (void* targetBuffer) const { - new(targetBuffer) Adapter(*this); + new(targetBuffer) Adapter(*this); // forward to copy ctor } virtual void @@ -285,11 +363,11 @@ namespace lib { { REQUIRE (INSTANCEOF (Adapter, &targetBase)); Adapter& target = static_cast (targetBase); - target = (*this); + target = (*this); // forward to assignment operator } - public: - /* using default copy and assignment */ + public: /* == forwarding ctor to implementation type == */ + Adapter() : IMP() { } template @@ -300,31 +378,40 @@ namespace lib { template Adapter (A1& a1, A2& a2, A3& a3) : IMP(a1,a2,a3) { } + + /* using default copy and assignment */ }; _CopyHandlingAdapter& accessHandlingInterface () const { - IFA& bufferContents = access(); - _CopyHandlingAdapter& hap = _Traits::accessCopyHandlingInterface (bufferContents); - return hap; ////TODO cleanup unnecessary temporary + IFA& bufferContents = accessEmbedded(); + return _Traits::accessCopyHandlingInterface (bufferContents); } - public: - operator IFA& () + public: /* === PolymorphicValue public API === */ + + typedef IFA Interface; + + operator Interface& () { - return access(); + return accessEmbedded(); } - operator IFA const& () const + operator Interface const& () const { - return access(); + return accessEmbedded(); + } + Interface* + operator-> () const + { + return &( accessEmbedded() ); } ~PolymorphicValue() { - destroy(); + destroyEmbedded(); } PolymorphicValue (PolymorphicValue const& o) @@ -335,10 +422,12 @@ namespace lib { PolymorphicValue& operator= (PolymorphicValue const& o) { - o.accessHandlingInterface().copyInto (this->access()); + o.accessHandlingInterface().copyInto (this->accessEmbedded()); return *this; } + /* === static factory functions === */ + template static PolymorphicValue build () @@ -371,10 +460,13 @@ namespace lib { return PolymorphicValue (type_to_build_in_buffer, a1,a2,a3); } + + /* === support Equality by forwarding to embedded === */ + friend bool operator== (PolymorphicValue const& v1, PolymorphicValue const& v2) { - return v1.access() == v2.access(); + return v1.accessEmbedded() == v2.accessEmbedded(); } friend bool operator!= (PolymorphicValue const& v1, PolymorphicValue const& v2) diff --git a/tests/lib/polymorphic-value-test.cpp b/tests/lib/polymorphic-value-test.cpp index 26570c8ff..bdc366f9c 100644 --- a/tests/lib/polymorphic-value-test.cpp +++ b/tests/lib/polymorphic-value-test.cpp @@ -24,14 +24,10 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" -#include "lib/util.hpp" #include "lib/util-foreach.hpp" #include "lib/polymorphic-value.hpp" -//#include "lib/bool-checkable.hpp" -#include -//#include #include @@ -39,16 +35,11 @@ namespace lib { namespace test{ using ::Test; -// using util::isnil; using util::for_each; using util::unConst; using util::isSameObject; -// using lumiera::error::LUMIERA_ERROR_INVALID; using lumiera::error::LUMIERA_ERROR_ASSERTION; -// using std::vector; -// using std::cout; -// using std::endl; namespace { // test dummy hierarchy // Note: largely varying space requirements @@ -72,15 +63,23 @@ namespace test{ const uint MAX_RAND = 1000; - const uint MAX_SIZ = sizeof(long[113]); /////////////////////TODO: using just 111 causes SEGV ---> suspect the HandlingAdapter mixin to require additional storage + const uint MAX_ELM = 111; + const uint MAX_SIZ = sizeof(long[MAX_ELM]); + /* Checksums to verify proper ctor-dtor calls and copy operations */ long _checkSum = 0; long _callSum = 0; uint _created = 0; - template - struct Imp : Interface + /** + * Template to generate concrete implementation classes. + * @note the generated classes vary largely in size, and + * moreover the actual place to store the checksum + * also depends on that size parameter. + */ + template + struct Imp : BASE { long localData_[ii]; @@ -159,6 +158,7 @@ namespace test{ + /********************************************************************************** * @test build a bunch of PolymorphicValue objects. Handle them like copyable * value objects, without knowing the exact implementation type; moreover @@ -175,9 +175,9 @@ namespace test{ _callSum = 0; _created = 0; + verifyBasics(); + { - verifyBasics(); - TestList objs = createOpaqueValues (); for_each (objs, operate); } @@ -207,8 +207,7 @@ namespace test{ CHECK (elm == myLocalVal); long prevSum = _callSum; - Interface& subject = myLocalVal; - long randVal = subject.apiFunc(); + long randVal = myLocalVal->apiFunc(); CHECK (prevSum + randVal == _callSum); CHECK (elm != myLocalVal); @@ -223,25 +222,59 @@ namespace test{ void verifyBasics() { + typedef Imp MaximumSizedImp; + + // Standard case: no copy support by client objects + verifyCreation_and_Copy(); + + // Special case: client objects expose extension point for copy support + typedef polyvalue::CopySupport CopySupportAPI; // Copy support API declared as sub-interface + typedef Imp CopySupportingImp; // insert this sub-interface between public API and Implementation + typedef PolymorphicValue OptimalPolyVal; // Make the Holder use this special attachment point + CHECK (sizeof(OptimalPolyVal) < sizeof(PolyVal)); // results in smaller Holder and less implementation overhead + + verifyCreation_and_Copy(); + } + + + template + void + verifyCreation_and_Copy() + { + typedef PV Holder; + typedef IMP ImpType; + typedef typename PV::Interface Api; + long prevSum = _checkSum; uint prevCnt = _created; - PolyVal val = PolyVal::build >(); + Holder val = Holder::template build(); CHECK (prevSum+111 == _checkSum); // We got one primary ctor call CHECK (prevCnt+1 <= _created); // Note: usually, the compiler optimises CHECK (prevCnt+2 >= _created); // and skips the spurious copy-operation - CHECK (sizeof(PolyVal) > sizeof(Imp<111>)); - Interface& embedded = val; + CHECK (sizeof(Holder) >= sizeof(ImpType)); + Api& embedded = val; CHECK (isSameObject(embedded,val)); - CHECK (INSTANCEOF(Imp<111>, &embedded)); + CHECK (INSTANCEOF(ImpType, &embedded)); + + prevCnt = _created; + Holder val2(val); // invoke copy ctor without knowing the implementation type + embedded.apiFunc(); + CHECK (val != val2); // invoking the API function had an sideeffect on the state + val = val2; // assignment of copy back to the original... + CHECK (val == val2); // cancels the side effect + + CHECK (prevCnt+1 == _created); // one new embedded instance was created by copy ctor } void verifyOverrunProtection() { + typedef Imp OversizedImp; + CHECK (MAX_SIZ < sizeof(OversizedImp)); #if false ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #537 : restore throwing ASSERT - VERIFY_ERROR (ASSERTION, PolyVal::build >() ); + VERIFY_ERROR (ASSERTION, PolyVal::build() ); #endif ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #537 : restore throwing ASSERT } }; From 6fa11ffcf6b0a7fcb95c1d052e5ee215ea81b350 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 25 Apr 2011 04:49:05 +0200 Subject: [PATCH 13/28] special case: support cloning, but not copy needed to add yet another policy template, so PolymorphicValue doesn't invoke the assignment operator in such cases --- src/lib/meta/duck-detector.hpp | 20 ++++++- src/lib/polymorphic-value.hpp | 83 ++++++++++++++++++++++++++-- tests/lib/polymorphic-value-test.cpp | 25 +++++++++ 3 files changed, 121 insertions(+), 7 deletions(-) diff --git a/src/lib/meta/duck-detector.hpp b/src/lib/meta/duck-detector.hpp index 0091a894f..84363616e 100644 --- a/src/lib/meta/duck-detector.hpp +++ b/src/lib/meta/duck-detector.hpp @@ -60,9 +60,25 @@ ** specific function signature and then try to assign the named member. This allows even ** to determine if a member function of a type in question has the desired signature. ** - ** All these detection building blocks are written such as to provide a bool member \v ::value, + ** All these detection building blocks are written such as to provide a bool member \c ::value, ** which is in accordance to the conventions of boost metaprogramming. I.e. you can immediately - ** use them within boost::enable_if + ** use them within \c boost::enable_if + ** + ** \par some pitfalls to consider + ** + ** @warning The generated metafunctions all yield the \c false value by default. + ** Effectively this means that an error in the test expression might go unnoticed; + ** you'd be better off explicitly checking the detection result by an unit test. + ** + ** There are several typical problems to care about + ** - a member can be both a variable or a function of that name + ** - function signatures need to match precisely, including const modifiers + ** - the generated metafunction (template) uses a type parameter 'TY', which could + ** shadow or conflict with an type parameter in the enclosing scope + ** - the member and function checks rely on member pointers, which generally rely on + ** the explicit static type. These checks don't see any inherited members / functions. + ** - obviously, all those checks are never able to detect anything depending on runtime + ** types or RTTI ** ** @see util-foreach.hpp usage example ** @see duck-detector-test.cpp diff --git a/src/lib/polymorphic-value.hpp b/src/lib/polymorphic-value.hpp index 94a4b4b09..0f13a9b98 100644 --- a/src/lib/polymorphic-value.hpp +++ b/src/lib/polymorphic-value.hpp @@ -153,6 +153,7 @@ namespace lib { namespace polyvalue { // implementation details... + namespace error = lumiera::error; using boost::enable_if; using lumiera::Yes_t; using lumiera::No_t; @@ -186,6 +187,21 @@ namespace lib { virtual void copyInto (IFA& targetBase) const =0; }; + /** + * A variation for limited copy support. + * Sometimes, only cloning (copy construction) of value objects is allowed, + * but not assignment of new values to existing objects. In this case, the + * concrete values can inherit from this variant of the support API. + */ + template ///< direct baseclass to use for this clone support API + class CloneValueSupport + : public BA + { + public: + virtual ~CloneValueSupport() { }; + virtual void cloneInto (void* targetBuffer) const =0; + }; + /** @@ -193,7 +209,7 @@ namespace lib { * to support clone operations */ template - class exposes_copySupportFunctions + class exposes_CloneFunction { META_DETECT_FUNCTION(void, cloneInto, (void*) const); @@ -203,8 +219,58 @@ namespace lib { }; }; + /** + * helper to detect if the API supports only + * copy construction, but no assignment + */ + template + class allow_Clone_but_no_Copy + { + META_DETECT_MEMBER(copyInto); + + public: + enum{ value = exposes_CloneFunction::value + && ! HasMember_copyInto::value + }; + }; - /** + /** + * Policy class for invoking the assignment operator. + * By default the embedded payload objects are assumed + * to be freely assignable + */ + template + struct AssignmentPolicy + { + template + static void + assignEmbedded(IMP& dest,IMP const& src) + { + dest = src; + } + }; + + /** + * special case when the embedded payload objects permit + * copy construction, but no assignment to existing instances. + * Instead of invoking the assignment operator (which typically + * isn't defined at all in such cases), a misguided assignment + * to the container will raise an exception + */ + template + struct AssignmentPolicy >::type> + { + template + static void + assignEmbedded(IMP&,IMP const&) + { + throw error::Logic("attempt to overwrite unmodifiable value"); + } + }; + + + + /** * traits template to deal with * different ways to support copy operations. * Default is no support by the API and implementation types. @@ -226,6 +292,7 @@ namespace lib { } typedef CopyAPI AdapterAttachment; + typedef AssignmentPolicy Assignment; }; @@ -237,7 +304,7 @@ namespace lib { * simple \c static_cast without runtime overhead. */ template - struct Trait >::type> + struct Trait >::type> { typedef TY CopyAPI; enum{ ADMIN_OVERHEAD = 1 * sizeof(void*) }; @@ -251,6 +318,7 @@ namespace lib { } typedef EmptyBase AdapterAttachment; + typedef AssignmentPolicy Assignment; }; }//(End)implementation details @@ -259,6 +327,10 @@ namespace lib { + + + + /** * Template to build polymorphic value objects. * Inline buffer with value semantics, yet holding and owning an object @@ -285,6 +357,7 @@ namespace lib { typedef polyvalue::Trait _Traits; typedef typename _Traits::CopyAPI _CopyHandlingAdapter; + typedef typename _Traits::Assignment _AssignmentPolicy; enum{ siz = storage + _Traits::ADMIN_OVERHEAD }; @@ -363,8 +436,8 @@ namespace lib { { REQUIRE (INSTANCEOF (Adapter, &targetBase)); Adapter& target = static_cast (targetBase); - target = (*this); // forward to assignment operator - } + _AssignmentPolicy::assignEmbedded(target,*this); + } // forward to assignment operator public: /* == forwarding ctor to implementation type == */ diff --git a/tests/lib/polymorphic-value-test.cpp b/tests/lib/polymorphic-value-test.cpp index bdc366f9c..8ee1ef356 100644 --- a/tests/lib/polymorphic-value-test.cpp +++ b/tests/lib/polymorphic-value-test.cpp @@ -184,6 +184,7 @@ namespace test{ CHECK (0 == _checkSum); // all dead verifyOverrunProtection(); + verifyCopySupportDetectionMetafunctions(); } @@ -277,6 +278,30 @@ namespace test{ VERIFY_ERROR (ASSERTION, PolyVal::build() ); #endif ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #537 : restore throwing ASSERT } + + + /** @Test internally, PolymorphicValue uses some metafunctions + * to pick a suitable code path, based on the presence of helper functions + * on the API of the embedded objects. Default is no support by these objects, + * which then requires to use a more expensive implementation. Sometimes it's + * desirable to support \em cloning only (copy ctor), but no assignment after + * the fact. In this special case, a support API with only a \cloneInto member + * can be implemented, causing the PolymorphicValue container to raise an + * exception in case the copy operator is invoked. + */ + void + verifyCopySupportDetectionMetafunctions() + { + typedef polyvalue::CopySupport CopySupportAPI; + typedef polyvalue::CloneValueSupport CloneOnlyAPI; + + CHECK ( !polyvalue::exposes_CloneFunction::value ); + CHECK ( polyvalue::exposes_CloneFunction::value ); + CHECK ( polyvalue::exposes_CloneFunction::value ); + + CHECK ( polyvalue::allow_Clone_but_no_Copy::value ); + CHECK ( !polyvalue::allow_Clone_but_no_Copy::value ); + } }; From 58513326284b189981dab7a58399e4154b3902d2 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 25 Apr 2011 05:42:05 +0200 Subject: [PATCH 14/28] back to business: now able to implement time mutation messages --- src/lib/time/mutation.cpp | 72 ++++++++++++++++++++++++++++++++------ src/lib/time/mutation.hpp | 20 ++++++++--- src/lib/time/timevalue.hpp | 6 ++++ src/proc/asset.hpp | 2 +- wiki/renderengine.html | 10 +++--- 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/src/lib/time/mutation.cpp b/src/lib/time/mutation.cpp index 070386c62..4cc06d931 100644 --- a/src/lib/time/mutation.cpp +++ b/src/lib/time/mutation.cpp @@ -65,8 +65,8 @@ namespace time { * concrete time value mutation: * impose fixed new start time. */ - class SetNewStartTime - : public Mutation + class SetNewStartTimeMutation + : public ClonableMutation { TimeValue newTime_; @@ -96,29 +96,81 @@ namespace time { public: explicit - SetNewStartTime (TimeValue t) + SetNewStartTimeMutation (TimeValue t) : newTime_(t) { } }; + /** + * concrete time value mutation: + * impose fixed new start time. + */ + class SetNewDuration + : public ClonableMutation + { + Duration changedDuration_; + + virtual void + change (Duration& target) const + { + imposeChange (target, changedDuration_); + } + + + virtual void + change (TimeSpan& target) const + { + imposeChange (target.duration(), changedDuration_); + } + + + /** @note the re-quantisation happens automatically + * when the (changed) QuTime is materialised */ + virtual void + change (QuTime&) const + { + throw error::Logic ("mutating the duration of a (quantised) time point doesn't make sense" + , LUMIERA_ERROR_INVALID_MUTATION); + } + + + public: + explicit + SetNewDuration (Duration dur) + : changedDuration_(dur) + { } + }; - /** */ - void + + + /** Convenience factory to yield a simple Mutation changing the absolute start time. + * This whole procedure might look quite inefficient, but actually most of the + * abstractions are removed at runtime, leaving only a single indirection + * through the VTable of the Mutation Interface. + * @throw error::Logic when attempting to change the (non existent) start time of a Duration + * @return EncapsulatedMutation, which is an "polymorphic value object", + * actually carrying an embedded Mutation subclass with the new start time + */ + EncapsulatedMutation Mutation::changeTime (Time newStartTime) { - UNIMPLEMENTED ("perform a trivial mutation to set a new fixed time value"); + return EncapsulatedMutation::build (newStartTime); } - void - Mutation::changeDuration (Duration) + /** Convenience factory: simple Mutation to adjust the duration or length of a timespan + * @throw error::Logic when attempting to change the "duration" of a quantised time point + * @return EncapsulatedMutation, carrying the new duration value to impose + */ + EncapsulatedMutation + Mutation::changeDuration (Duration changedDur) { - UNIMPLEMENTED ("perform a trivial mutation to set a new Duration"); + return EncapsulatedMutation::build (changedDur); } - void + EncapsulatedMutation Mutation::nudge (int adjustment) { UNIMPLEMENTED ("Nudge by a predefined nudge amount"); diff --git a/src/lib/time/mutation.hpp b/src/lib/time/mutation.hpp index bbb325911..c5e920356 100644 --- a/src/lib/time/mutation.hpp +++ b/src/lib/time/mutation.hpp @@ -55,6 +55,7 @@ #include "lib/error.hpp" #include "lib/time/timevalue.hpp" +#include "lib/polymorphic-value.hpp" //#include "lib/symbol.hpp" #include @@ -72,6 +73,18 @@ namespace time { LUMIERA_ERROR_DECLARE (INVALID_MUTATION); ///< Changing a time value in this way was not designated class QuTime; + class Mutation; + + + /* The following typedefs allow to hand out + * "unspecific" mutation value objects from + * factory functions, without disclosing any + * implementation details here in this header. + */ + enum{ MUTATION_IMPL_SIZE = sizeof(TimeValue) }; + + typedef lib::polyvalue::CloneValueSupport ClonableMutation; + typedef lib::PolymorphicValue EncapsulatedMutation; /** * Interface: an opaque change imposed onto some time value. @@ -80,7 +93,6 @@ namespace time { * @todo WIP-WIP-WIP */ class Mutation - : boost::noncopyable { public: virtual ~Mutation(); @@ -91,9 +103,9 @@ namespace time { /* === convenience shortcuts for simple cases === */ - static void changeTime (Time); - static void changeDuration (Duration); - static void nudge (int adjustment); + static EncapsulatedMutation changeTime (Time); + static EncapsulatedMutation changeDuration (Duration); + static EncapsulatedMutation nudge (int adjustment); protected: static void imposeChange (TimeValue&, TimeValue); diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp index b6fc7d28f..d7fe90b30 100644 --- a/src/lib/time/timevalue.hpp +++ b/src/lib/time/timevalue.hpp @@ -380,6 +380,12 @@ namespace time { { } + Duration& + duration() + { + return dur_; + } + Duration duration() const { diff --git a/src/proc/asset.hpp b/src/proc/asset.hpp index bef9463ac..bd26b92ae 100644 --- a/src/proc/asset.hpp +++ b/src/proc/asset.hpp @@ -130,7 +130,7 @@ namespace asset { * - asset::Media representing mediafiles * - asset::Proc representing media data processing components (e.g. Effects) * - asset::Struct representing structural components used in the session (e.g. Tracks) - * - asset::Meta representing meta objects created while editing (e.g. Automation) + * - asset::Meta representing metadata, parametrisation, customisation. * * And of course there are various concrete Asset subclasses, like asset::Clip, * asset::Effect, asset::Codec, asset::Track, asset::Dataset. diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 0f414c368..d0649b5c1 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -6618,7 +6618,7 @@ Thus no server and no network connection is needed. Simply open the file in your * see [[Homepage|http://tiddlywiki.com]], [[Wiki-Markup|http://tiddlywiki.org/wiki/TiddlyWiki_Markup]]
-
+
Simple time points are just like values and thus easy to change. The difficulties arise when time values are to be //quantised to an existing time grid.// The first relevant point to note is that for quantised time values, the effect of a change can be disproportional to the cause. Small changes below the threshold might be accumulated, and a tiny change might trigger a jump to the next grid point. While this might be annoying, the yet more complex questions arise when we acknowledge that the amount of the change itself might be related to a time grid.
 
 The problem with modification of quantised values highlights an inner contradiction or conflicting goals within the design
@@ -6666,9 +6666,10 @@ This results in the following combinations to be implemented:
 |~|quantised ||set raw value |
 | Duration|~TimeSpan ||set length |
 |~|Duration  ||set length |
+|~|quantised || -- |
 | Offset|Time || -- |
-|~|Duration || -- |
-|~|~TimeSpan ||adjust start/length |
+|~|Duration ||adjust length |
+|~|~TimeSpan ||adjust start |
 |~|quantised ||adjust raw value |
 | ~QuTime|any raw ||treat as raw ~TimeValue |
 |~|~QuTime ||materialise, then set raw |
@@ -6698,8 +6699,7 @@ Question is: how fine grained and configurable needs this to be?
 !!! design draft
 The special focus of this problem seems to lead itself to a __visitor pattern__ based implementation. Because the basic hierarchy of applicable types is fixed, but the behaviour is open ended (and not yet fully determined). A conventional implementation would scatter this behaviour over all the time entities, thus making it hard to understand and reason about. The classical ~GoF cyclic visitor solution to the contrary allows us to arrange closely related behaviour into thematically grouped visitor classes. As a plus, the concrete action can be bound dynamically, allowing for more flexibility when it comes to dealing with the intricate situations when a quantised time span (= a clip) recieves a quantised mutation (= is re-alligend to a possibly different frame grid)
 
-Thus the possibly mutalble time entities get an {{{accept(time::Mutation)}}} function, which is defined to reflect back into the (virtual) visitation functions of the Mutation Interface. Additionally, we provide some static convenience shortcuts right in the
-Mutation interface, performing the trivial mutations.
+Thus the possibly mutalble time entities get an {{{accept(time::Mutation&)}}} function, which is defined to reflect back into the (virtual) visitation functions of the Mutation Interface. Additionally, we provide some static convenience shortcuts right in the Mutation interface, performing the trivial mutations.
 
From baf601c16bc99c982320b315ae5e372a69302584 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 25 Apr 2011 06:46:36 +0200 Subject: [PATCH 15/28] first time mutation test cases pass --- src/lib/time/mutation.cpp | 2 +- src/lib/time/mutation.hpp | 2 +- tests/lib/time/time-mutation-test.cpp | 41 +++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/lib/time/mutation.cpp b/src/lib/time/mutation.cpp index 4cc06d931..3a8ae6681 100644 --- a/src/lib/time/mutation.cpp +++ b/src/lib/time/mutation.cpp @@ -53,7 +53,7 @@ namespace time { * value stored in each time entity */ void - Mutation::imposeChange (TimeValue& target, TimeValue valueToSet) + Mutation::imposeChange (TimeValue& target, TimeValue const& valueToSet) { target = valueToSet; } diff --git a/src/lib/time/mutation.hpp b/src/lib/time/mutation.hpp index c5e920356..fd66bb010 100644 --- a/src/lib/time/mutation.hpp +++ b/src/lib/time/mutation.hpp @@ -108,7 +108,7 @@ namespace time { static EncapsulatedMutation nudge (int adjustment); protected: - static void imposeChange (TimeValue&, TimeValue); + static void imposeChange (TimeValue&, TimeValue const&); }; diff --git a/tests/lib/time/time-mutation-test.cpp b/tests/lib/time/time-mutation-test.cpp index 7b9e21622..3a973763b 100644 --- a/tests/lib/time/time-mutation-test.cpp +++ b/tests/lib/time/time-mutation-test.cpp @@ -25,6 +25,7 @@ #include "lib/test/test-helper.hpp" #include "lib/time/timevalue.hpp" #include "lib/time/timequant.hpp" +#include "lib/time/mutation.hpp" #include "proc/asset/meta/time-grid.hpp" //#include "lib/time/display.hpp" #include "lib/util.hpp" @@ -107,7 +108,7 @@ namespace test{ QuTime qVal (o, "test_grid"); FrameNr count(qVal); - mutate_by_Value (o, c); + mutate_by_Value (o, Time(c)); mutate_by_Offset (o, Offset(c)); mutate_quantised (o, qVal); mutate_by_Increment(o, count); @@ -115,9 +116,43 @@ namespace test{ void - mutate_by_Value(TimeValue o, TimeValue change) + mutate_by_Value(TimeValue original, Time newStart) { - TestValues t(o); + TestValues t(original); + + CHECK (t.span.start() == original); + t.span.accept (Mutation::changeTime (newStart)); + CHECK (t.span.start() != original); + CHECK (t.span.start() == newStart); + + // instead of invoking directly, we can store and copy mutation messages + EncapsulatedMutation change_back(Mutation::changeTime (Time(original))); + t.span.accept (change_back); + CHECK (t.span.start() == original); + + CHECK (t.quant == original); + t.quant.accept (Mutation::changeTime (newStart)); + CHECK (t.quant != original); + CHECK (t.quant == newStart); + + // Durations have no start time... + VERIFY_ERROR (INVALID_MUTATION, t.dur.accept(change_back)); + VERIFY_ERROR (INVALID_MUTATION, t.span.duration().accept(change_back)); + + CHECK (t.dur == original); + t.dur.accept (Mutation::changeDuration (Duration(2*t.var))); + CHECK (t.dur != original); + CHECK (t.dur == t.var*2); + + CHECK (t.span.start() == original); + CHECK (t.span.duration() == original); + t.span.accept (Mutation::changeDuration(Duration(3*t.var))); + CHECK (t.span.duration() != original); + CHECK (t.span.duration() == t.var*3); // affects the duration, + CHECK (t.span.start() == original); // while the start time remains unaltered + + // can't change the 'duration' of a quantised time point... + VERIFY_ERROR (INVALID_MUTATION, t.quant.accept(Mutation::changeDuration (Duration(t.var)))); } From 203540525106fad012ed95397f140585847b931e Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 25 Apr 2011 18:51:50 +0200 Subject: [PATCH 16/28] add time Mutation to adjust by offset --- src/lib/time/mutation.cpp | 53 ++++++++++++++++++++++++++- src/lib/time/mutation.hpp | 1 + src/lib/time/timevalue.hpp | 8 ++++ src/proc/asset/meta.hpp | 5 ++- tests/lib/time/time-mutation-test.cpp | 25 ++++++++++++- 5 files changed, 87 insertions(+), 5 deletions(-) diff --git a/src/lib/time/mutation.cpp b/src/lib/time/mutation.cpp index 3a8ae6681..6555c22b4 100644 --- a/src/lib/time/mutation.cpp +++ b/src/lib/time/mutation.cpp @@ -104,7 +104,7 @@ namespace time { /** * concrete time value mutation: - * impose fixed new start time. + * set a new overall duration for an extended timespan. */ class SetNewDuration : public ClonableMutation @@ -143,6 +143,46 @@ namespace time { }; + /** + * concrete time value mutation: + * adjust the given time entity by an offset amount. + */ + class ImposeOffsetMutation + : public ClonableMutation + { + Offset adjustment_; + + virtual void + change (Duration& target) const + { + imposeChange (target, TimeVar(target)+=adjustment_); + } + + + virtual void + change (TimeSpan& target) const + { + imposeChange (target, TimeVar(target)+=adjustment_); + } + + + /** @note the re-quantisation happens automatically + * when the (changed) QuTime is materialised */ + virtual void + change (QuTime& target) const + { + imposeChange (target, TimeVar(target)+=adjustment_); + } + + + public: + explicit + ImposeOffsetMutation (Offset adj) + : adjustment_(adj) + { } + }; + + /** Convenience factory to yield a simple Mutation changing the absolute start time. * This whole procedure might look quite inefficient, but actually most of the @@ -170,6 +210,17 @@ namespace time { } + /** Convenience factory: simple Mutation to adjust the duration or length of a timespan + * @throw error::Logic when attempting to change the "duration" of a quantised time point + * @return EncapsulatedMutation, carrying the new duration value to impose + */ + EncapsulatedMutation + Mutation::adjust (Offset change) + { + return EncapsulatedMutation::build (change); + } + + EncapsulatedMutation Mutation::nudge (int adjustment) { diff --git a/src/lib/time/mutation.hpp b/src/lib/time/mutation.hpp index fd66bb010..13c1b6bca 100644 --- a/src/lib/time/mutation.hpp +++ b/src/lib/time/mutation.hpp @@ -105,6 +105,7 @@ namespace time { static EncapsulatedMutation changeTime (Time); static EncapsulatedMutation changeDuration (Duration); + static EncapsulatedMutation adjust (Offset); static EncapsulatedMutation nudge (int adjustment); protected: diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp index d7fe90b30..aa08c00ca 100644 --- a/src/lib/time/timevalue.hpp +++ b/src/lib/time/timevalue.hpp @@ -198,6 +198,9 @@ namespace time { { return TimeValue(std::llabs (t_)); } + + // Supporting sign flip + Offset operator- () const; }; //-- support linear offset chaining --------------- @@ -224,6 +227,11 @@ namespace time { return factor*distance; } + inline Offset + Offset::operator- () const + { + return -1 * (*this); + } diff --git a/src/proc/asset/meta.hpp b/src/proc/asset/meta.hpp index 77e08b71a..9ac73ea4f 100644 --- a/src/proc/asset/meta.hpp +++ b/src/proc/asset/meta.hpp @@ -48,7 +48,7 @@ ** and can be another already existing asset::Meta (inheriting from meta::Descriptor) ** Generally this yields a Builder object, which can be used for outfitting the new ** or changed metadata entry, finally \em committing this builder to yield a new - ** asset::Meta (which, in case of a mutation, might superseede an existin one). + ** asset::Meta (which, in case of a mutation, might supersede an existing one). ** ** @see asset.hpp explanation of assets in general ** @see MetaFactory creating concrete asset::Meta instances @@ -120,7 +120,8 @@ namespace asset { public: static MetaFactory create; - virtual const ID& getID() const ///< @return ID of kind Meta + /** @return ID of kind Meta */ + virtual const ID& getID() const { return static_cast& > (Asset::getID()); } diff --git a/tests/lib/time/time-mutation-test.cpp b/tests/lib/time/time-mutation-test.cpp index 3a973763b..65bd35612 100644 --- a/tests/lib/time/time-mutation-test.cpp +++ b/tests/lib/time/time-mutation-test.cpp @@ -157,9 +157,30 @@ namespace test{ void - mutate_by_Offset (TimeValue o, Offset change) + mutate_by_Offset (TimeValue original, Offset change) { - TestValues t(o); + TestValues t(original); + TimeValue& should_be(t.var+=change); // use as ref for verification + + CHECK (t.span == original); + CHECK (t.span != should_be); + t.span.accept (Mutation::adjust (change)); + CHECK (t.span == should_be); + + t.dur.accept (Mutation::adjust (change)); + CHECK (t.dur == should_be); + + t.quant.accept (Mutation::adjust (change)); + CHECK (t.quant == should_be); + + // adjustment is cumulative + EncapsulatedMutation back_off = Mutation::adjust (-change); + t.span.accept (back_off); + CHECK (t.span == original); + t.span.accept (back_off); + t.span.accept (back_off); + t.span.accept (back_off); + CHECK (t.span == Time(original) - 3*change); } From bf61ff7248544cf79cd8c80c8dcf8e2590a03ad5 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 26 Apr 2011 06:11:31 +0200 Subject: [PATCH 17/28] WIP test-driven brainstorming: nudge by grid --- src/lib/time/formats.hpp | 2 +- src/lib/time/timecode.cpp | 2 +- tests/lib/time/time-mutation-test.cpp | 74 +++++++++++++++++++++++++-- wiki/renderengine.html | 29 ++++++----- 4 files changed, 88 insertions(+), 19 deletions(-) diff --git a/src/lib/time/formats.hpp b/src/lib/time/formats.hpp index b82a8f631..aae1226a5 100644 --- a/src/lib/time/formats.hpp +++ b/src/lib/time/formats.hpp @@ -192,7 +192,7 @@ namespace time { flags_.set (typeID()); return define(FS()); } - Supported define(NullType) { return *this;} ///< recursion end + Supported define(NullType) { return *this;} ///< @internal recursion end Supported() { } ///< @note use #formats to set up a new descriptor diff --git a/src/lib/time/timecode.cpp b/src/lib/time/timecode.cpp index dd4b67015..587293995 100644 --- a/src/lib/time/timecode.cpp +++ b/src/lib/time/timecode.cpp @@ -51,7 +51,7 @@ namespace time { void Frames::rebuild (FrameNr& framecnt, QuantR quantiser, TimeValue const& rawTime) { - framecnt.setValueRaw(quantiser.gridPoint (rawTime)); + framecnt.setValueRaw (quantiser.gridPoint (rawTime)); } /** calculate the time point denoted by this frame count */ diff --git a/tests/lib/time/time-mutation-test.cpp b/tests/lib/time/time-mutation-test.cpp index 65bd35612..e1d461334 100644 --- a/tests/lib/time/time-mutation-test.cpp +++ b/tests/lib/time/time-mutation-test.cpp @@ -101,6 +101,7 @@ namespace test{ { TimeValue o (random_or_get (pop(arg))); TimeValue c (random_or_get (pop(arg))); + CHECK (o != c, "unsuitable testdata"); // using a 25fps-grid, but with an time origin offset by 1/50sec TimeGrid::build("test_grid", FrameRate::PAL, Time(FSecs(1,50))); @@ -185,16 +186,81 @@ namespace test{ void - mutate_quantised (TimeValue o, QuTime change) + mutate_quantised (TimeValue original, QuTime change) { - TestValues t(o); + TestValues t(original); + t.var = change; + CHECK (Time(change) == t.var); // the underlying raw time value + + CHECK (t.span == original); + t.span.accept (Mutation::materialise (change)); + CHECK (t.span != original); + CHECK (t.span != t.var); // really materialised (grid-aligned) + + // simulate what happened by explicit operations... + Secs seconds = change.formatAs(); + Time materialised = seconds.getTime(); + CHECK (t.span == materialised); + + CHECK (t.span.duration() == original); // not affected by mutation as usual + VERIFY_ERROR (INVALID_MUTATION, t.dur.accept (Mutation::materialise (change))); + // not surprising, a time point has no duration!! + + CHECK (t.quant == original); + t.quant.accept (Mutation::materialise (change)); + CHECK (t.quant != original); + CHECK (t.quant == materialised); + // but note, here we checked the underlying raw value. + // because t.quant is itself quantised, this might + // result in a second, chained quantisation finally + + // Here accidentally both the change and t.quant use the same grid. + // For a more contrived example, we try to use a different grid... + TimeGrid::build("special_funny_grid", 1); // (1 frame per second, no offset) + QuTime funny (original, "special_funny_grid"); + funny.accept (Mutation::materialise (change)); + CHECK (funny == t.quant); // leading to the same raw value this far + + Time doublyQuantised = Secs(funny).getTime(); + CHECK (doublyQuantised != materialised); } void - mutate_by_Increment (TimeValue o, int change) + mutate_by_Increment (TimeValue original, int change) { - TestValues t(o); + TestValues t(original); + + // without any additional specification, + // the nudge-Mutation uses a 'natural grid' + t.span.accept (Mutation::nudge (change)); + t.dur.accept (Mutation::nudge (change)); + + t.var += Time(FSecs(change)); // natural grid is in seconds + CHECK (t.span.start() == t.var); + CHECK (t.dur == t.var); + + // any other grid can be specified explicitly + t.dur.accept (Mutation::nudge (change, "test_grid")); + CHECK (t.dur != t.var); + CHECK (t.dur == t.var + change * FrameRate::PAL.duration()); + // ....this time the change was measured in grid units, + // taken relative to the origin of the specified grid + PGrid testGrid = TimeGrid::retrieve("test_grid"); + Offset distance (testGrid->timeOf(0), testGrid->timeOf(change)); + CHECK (distance == change * FrameRate::PAL.duration()); + CHECK (Time(t.dur) - t.var == distance); + + + + // To the contrary, *quantised* values behave quite differently... + long frameNr = t.quant.formatAs(); + + t.quant.accept (Mutation::nudge (change)); + CHECK (t.quant != original); + long frameNr_after = t.quant.formatAs(); + CHECK (frameNr_after != frameNr + change); + // i.e., use the quantised time's own grid } }; diff --git a/wiki/renderengine.html b/wiki/renderengine.html index d0649b5c1..2cca48ddd 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -4193,18 +4193,18 @@ Viewed as a micro program, the processing patterns are ''weak typed'' &mdash
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
 
-
+
The Quantiser implementation works by determining the grid interval containing a given raw time.
 These grid intervals are denoted by ordinal numbers (frame numbers), with interval #0 starting at the grid's origin and negative ordinals allowed.
 
 !frame quantisation convention
-Within Lumiera, there is a fixed convention how these frame intervals are to be defined (&rArr; [[time handling RfC|http://staging.lumiera.org/documentation/devel/rfc/TimeHandling.html]])
+Within Lumiera, there is a fixed convention how these frame intervals are to be defined (&rArr; [[time handling RfC|http://lumiera.org/documentation/devel/rfc/TimeHandling.html]])
 [img[Lumiera's frame quantisation convention|draw/FramePositions1.png]]
 Especially, this convention is agnostic of the actual zero-point of the scale and allows direct length calculations and seamless sequences of intervals.
 The //nominal coordinate// of an interval is also the starting point -- for automation keys frames we'll utilise special provisions.
 
 !range limitation problems
-because times are represented as 64bit integers, the time points addressable within a given scale grid can be limited, compared with time points addressable through raw (internal) time values. As an extreme example, consider a time scale with origin at Time::MIN -- such a scale is unable to represent any of the original scale's value above zero, because the resulting coordinates would exceed the range of the 64bit integer. Did I mention that 64bit micro ticks can represent about 300000 years?
+because times are represented as 64bit integers, the time points addressable within a given scale grid can be limited, compared with time points addressable through raw (internal) time values. As an extreme example, consider a time scale with origin at {{{Time::MAX}}} -- such a scale is unable to represent any of the original scale's value above zero, because the resulting coordinates would exceed the range of the 64bit integer. Did I mention that 64bit micro ticks can represent about 300000 years?
 
 Now the actual problem is that using 64bit integers already means pushing to the limit. There is no easy escape hatch, like using a larger scale data type for intermediaries -- it //is// the largest built-in type. Basically we're touching the topic of ''safe integer arithmetics'' here, which is frequently discussed as a security concern. The situation is as follows:
 * every programmer promises "I'll do the checks when necessary" -- just to forget doing so in practice then.
@@ -6618,7 +6618,7 @@ Thus no server and no network connection is needed. Simply open the file in your
  * see [[Homepage|http://tiddlywiki.com]], [[Wiki-Markup|http://tiddlywiki.org/wiki/TiddlyWiki_Markup]]
 
-
+
Simple time points are just like values and thus easy to change. The difficulties arise when time values are to be //quantised to an existing time grid.// The first relevant point to note is that for quantised time values, the effect of a change can be disproportional to the cause. Small changes below the threshold might be accumulated, and a tiny change might trigger a jump to the next grid point. While this might be annoying, the yet more complex questions arise when we acknowledge that the amount of the change itself might be related to a time grid.
 
 The problem with modification of quantised values highlights an inner contradiction or conflicting goals within the design
@@ -6661,18 +6661,18 @@ A mutation or an increment gets applied to a grid-aligned variable, which then (
 This results in the following combinations to be implemented:
 |  !mutation|!receiver ||!result |
 | ~TimeValue|Time || -- |
-|~|Duration || -- |
+|~|Duration || ↯ |
 |~|~TimeSpan ||set start |
 |~|quantised ||set raw value |
 | Duration|~TimeSpan ||set length |
 |~|Duration  ||set length |
-|~|quantised || -- |
+|~|quantised || ↯ |
 | Offset|Time || -- |
 |~|Duration ||adjust length |
 |~|~TimeSpan ||adjust start |
 |~|quantised ||adjust raw value |
-| ~QuTime|any raw ||treat as raw ~TimeValue |
-|~|~QuTime ||materialise, then set raw |
+| ~QuTime|any raw ||materialise, then set raw |
+|~|~QuTime ||same &rarr; chanined |
 | increment|~QuTime ||snap to grid point |
 As rationale, consider the following
 * immutable values are bliss. 
@@ -6764,7 +6764,7 @@ At the level of individual timecode formats, we're lacking a common denominator;
 &rarr; Quantiser [[implementation details|QuantiserImpl]]
 
-
+
the following collection of usage situations helps to shape the details of the time values and time quantisation design. &rarr; see also  [[time quantisation|TimeQuant]]
 
 ;time position of an object
@@ -6801,12 +6801,15 @@ We should note, that the problems regarding quantised durations also carry over
 
 Thus, an especially important instance of that problem is ''how to deal with updates in a quantised environment''. If we handle quantisation stictly as a view employed on output, we run into the problems with accumulating spurious information. On the other hand, allowing for quantised changes inevitably pulls in all the complexity of mixing quantised and non-quantised values. It would be desirable somehow to move these distinctions out of the scope of this design and offload them onto the client (code using these time classes).
 
-Another closely related problem is ''when to allow mutations'', if at all. We can't completely do away with mutations, simply because we don't have a pure functional language at our disposal. The whole concept of //reference semantics// doesn't play so well with immutable objects. The Lumiera high-level (session) model certainly relies on objects intended to be //manipulated.// Thus we need a re-settable length field in {{{MObject}}} and we need a time variable for position calculations. Yet we could make any //derived objects// into immutable descriptor records, which certainly helps with parallelism.
+Another closely related problem is ''when to allow [[mutations|TimeMutation]]'', if at all. We can't completely do away with mutations, simply because we don't have a pure functional language at our disposal. The whole concept of //reference semantics// doesn't play so well with immutable objects. The Lumiera high-level (session) model certainly relies on objects intended to be //manipulated.// Thus we need a re-settable length field in {{{MObject}}} and we need a time variable for position calculations. Yet we could make any //derived objects// into immutable descriptor records, which certainly helps with parallelism.
 
-The ''problem with playback position'' is -- that it's an attempt to conceptualise a non-existing entity. There is no such thing like "the" playback position. Yet most applications I'm aware off employ this concept. Likely they got trapped by the metaphor of the tape head, again. We should do away with that. On playback, we should show a //projection of wall-clock time onto the expected playback range// -- not more, not less. It should be acknowledged that there is //no direct link to the ongoing playback processes,// besides the fact that they're assumed to sync to wall-clock time as well. Recall, typically there are multiple playback processes going on in compound, and each might run on a different update rate. If we really want a //visual out-of-sync indicator,// we should treat that as a separate reporting facility and display it apart of the playback cursor.
+The ''problem with playback position'' is -- that indeed it's an attempt to conceptualise a non-existing entity. There is no such thing like "the" playback position. Yet most applications I'm aware off //do// employ this concept. Likely they got trapped by the metaphor of the tape head, again. We should do away with that. On playback, we should show a //projection of wall-clock time onto the expected playback range// -- not more, not less. It should be acknowledged that there is //no direct link to the ongoing playback processes,// besides the fact that they're assumed to sync to wall-clock time as well. Recall, typically there are multiple playback processes going on in compound, and each might run on a different update rate. If we really want a //visual out-of-sync indicator,// we should treat that as a separate reporting facility and display it apart of the playback cursor.
 
 Note that the ''display window might be treated as just an independent instance of quantisation''. This is similar to the approach taken above for modifying quantised time span values. We should provide a special kind of time grid, the display coordinates. The origin of these is always defined to the left (lower) side of the interval to be displayed, and they are gauged in screen units (pixels or similar, as used by the GUI toolkit set). The rest is handled by the general quantisation mechanisms. The problem of aligning the display should be transformed into a general facility to align grids, and solved for the general case. Doing so solves the remaining problems with quantised value changes and with ''specifying relative placements'' as well: If we choose to represent them as quantised values, we might (or might not) also choose to apply this //grid-alignment function.//
 
+!the complete time value usage cycle
+The way this time value and quantisation handling is designed in Lumiera creates a typical usage path, which actually is a one-way route. We might start out with a textual representation according to a specific ''timecode'' format. This timecode string may be parsed, and typically it will be ''materialised'', by invoking the {{{value()}}}-function. This brings us (back) to the very origin, which is a raw timevalue (''internal time'' value). Now, this time value might be manipulated, compared to other values, combined into a ''time span'' (time point and duration -- the most basic notion of an //object// to be manipulated in the Session). Anyway, at some point these time values need to be related to some ''time scale'' again, leading to ''quantised'' time values, which -- finally -- can be cast into a timecode format for external representation again, thus closing the circle.
+
 !substantial problems to be solved
 * how to [[align multiple grids|TimeGridAlignment]]
 * how to integrate [[modifications of quantised values|TimeMutation]].
@@ -6814,7 +6817,7 @@ Note that the ''display window might be treated as just an independent instance
 * how to design the relation of Timecode, Timecode formatting and Quantisation &rarr; [[more here|TimecodeFormat]]
 
-
+
The handling of [[Timecode]] is closely related to [[time representation and quantisation|TimeQuant]]. In fact, these two topics blend into one another. Time will be quantised into a //grid,// but this grid only makes sense when linked to a externally relevant meaning and representation, which is the Timecode. But a timecode value also denotes a specific point in time -- performing operations on a timecode is equivalent to manipulating a quantised time value.
 
 !Problem of dependencies
@@ -6860,7 +6863,7 @@ But because for Lumiera the SMPTE format is just a presentation rule applied to
 Because this decision is considered arbitrary and any reasoning will be context dependant, the decision is to provide a hook for a //strategy// --
 Currently (1/11), the strategy is implemented according to (1) and (4) above, leaving the actual configuration of a strategy for later.
 !!{{red{WIP 1/11}}}BUG
-Implementation of this strategy is still broken: it doesn't work properly when actually the change passing over the zero point happens by propagation from lower digits. Because then -- given the way the mutators are implemented -- the //new value of the wrapping digit hasn't been stored.// It seems the only sensible solution is to change the definition of the functors, so that any value will be changed by side-effect
+Implementation of this strategy is still broken: it doesn't work properly when actually the change passing over the zero point happens by propagation from lower digits. Because then -- given the way the mutators are implemented -- the //new value of the wrapping digit hasn't been stored.// It seems the only sensible solution is to change the definition of the functors, so that any value will be changed by side-effect {{red{Question 4/11 -- isn't this done and fixed by now??}}}
 
From af1561068eaca02ba2be813c1f151f2ed98fd02e Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 27 Apr 2011 02:25:36 +0200 Subject: [PATCH 18/28] definitions and stubs to make it compile --- src/lib/time/mutation.cpp | 62 +++++++++++++++++++++++++++ src/lib/time/mutation.hpp | 5 ++- src/lib/time/quantiser.cpp | 11 +++++ src/lib/time/quantiser.hpp | 2 + tests/lib/time/time-mutation-test.cpp | 4 +- 5 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/lib/time/mutation.cpp b/src/lib/time/mutation.cpp index 6555c22b4..6e0082f6e 100644 --- a/src/lib/time/mutation.cpp +++ b/src/lib/time/mutation.cpp @@ -24,6 +24,7 @@ #include "lib/error.hpp" #include "lib/time/timevalue.hpp" #include "lib/time/timequant.hpp" +#include "lib/time/timecode.hpp" #include "lib/time/mutation.hpp" //#include "lib/time.h" //#include "lib/util.hpp" @@ -183,6 +184,23 @@ namespace time { }; + /** + * concrete time value mutation: + * make the grid aligned time value explicit, + * and impose the resulting value to the given + * time points (or start points). + */ + class MaterialiseIntoTarget + : public SetNewStartTimeMutation + { + public: + explicit + MaterialiseIntoTarget (QuTime const& quant) + : SetNewStartTimeMutation (Secs(quant).getTime()) + { } + }; + + /** Convenience factory to yield a simple Mutation changing the absolute start time. * This whole procedure might look quite inefficient, but actually most of the @@ -221,6 +239,28 @@ namespace time { } + /** Convenience factory: materialise the given quantised time into an explicit fixed + * internal time value, according to the underlying time grid; impose the resulting + * value then as new time point or start point to the target + * @note same as materialising and then invoking #changeTime + */ + EncapsulatedMutation + Mutation::materialise (QuTime const& gridAlignedTime) + { + return EncapsulatedMutation::build (gridAlignedTime); + } + + + /** build a time mutation to \em nudge the target time value by an offset, + * defined as number of steps on an implicit nudge grid. If the target is an + * continuous (not quantised) time value or duration, an internal 'default nudge grid' + * will be used to calculate the offset value. Typically, this grid counts in seconds. + * To the contrary, when the target is a quantised value, it will be aligned to the + * grid point relative to the current value's next grid point, measured in number + * of steps. This includes \em materialising the internal to the exact grid + * position. If especially the adjustment is zero, the internal value will + * be changed to literally equal the current value's next grid point. + */ EncapsulatedMutation Mutation::nudge (int adjustment) { @@ -228,6 +268,28 @@ namespace time { } + /** build a time mutation to \em nudge the target time value; the nudge time grid + * is specified explicitly here, instead of using a global or 'natural' nudge grid. + * In case the target itself is a quantised time value, a chaining of the two + * grids will happen: first, the nudge grid is used to get an offset value, + * according to the number of steps, then this offset is applied to the + * raw value underlying the quantised target. If this resulting target + * value later will be cast into any kind of time code or materialised + * otherwise, the quantised value's own grid will apply as well, + * resulting in the net result of two quantisation operations + * being applied in sequence. + * @param adjustment number of grid steps to apply as offset + * @param gridID symbolic name used to register or define a + * suitable nudge grid, typically somewhere globally + * in the session (as meta asset) + */ + EncapsulatedMutation + Mutation::nudge (int adjustment, Symbol gridID) + { + UNIMPLEMENTED ("Nudge by a predefined nudge amount"); + } + + }} // lib::time diff --git a/src/lib/time/mutation.hpp b/src/lib/time/mutation.hpp index 13c1b6bca..b05fcaacc 100644 --- a/src/lib/time/mutation.hpp +++ b/src/lib/time/mutation.hpp @@ -56,7 +56,7 @@ #include "lib/error.hpp" #include "lib/time/timevalue.hpp" #include "lib/polymorphic-value.hpp" -//#include "lib/symbol.hpp" +#include "lib/symbol.hpp" #include //#include @@ -67,6 +67,7 @@ namespace lib { namespace time { + using lib::Symbol; //using std::string; //using lib::Literal; @@ -106,7 +107,9 @@ namespace time { static EncapsulatedMutation changeTime (Time); static EncapsulatedMutation changeDuration (Duration); static EncapsulatedMutation adjust (Offset); + static EncapsulatedMutation materialise (QuTime const&); static EncapsulatedMutation nudge (int adjustment); + static EncapsulatedMutation nudge (int adjustment, Symbol gridID); protected: static void imposeChange (TimeValue&, TimeValue const&); diff --git a/src/lib/time/quantiser.cpp b/src/lib/time/quantiser.cpp index 9c4eca790..6ae061587 100644 --- a/src/lib/time/quantiser.cpp +++ b/src/lib/time/quantiser.cpp @@ -101,6 +101,17 @@ namespace time { + /** Access an existing grid definition or quantiser, known by the given symbolic ID. + * Typically this fetches a meta::TimeGrid (asset) from the session. + * @throw error::Logic if the given gridID wasn't registered + * @return smart-ptr to the quantiser instance */ + PQuant + Quantiser::retrieve (Symbol gridID) + { + return retrieveQuantiser (gridID); + } + + /** alignment to a simple fixed size grid. * The actual calculation first determines the number * of the grid interval containing the given rawTime, diff --git a/src/lib/time/quantiser.hpp b/src/lib/time/quantiser.hpp index 369c48055..a8efad7a1 100644 --- a/src/lib/time/quantiser.hpp +++ b/src/lib/time/quantiser.hpp @@ -92,6 +92,8 @@ namespace time { return supportedFormats_.check(); } + static PQuant retrieve (Symbol gridID); + //------Grid-API---------------------------------------------- virtual long gridPoint (TimeValue const& raw) const =0; diff --git a/tests/lib/time/time-mutation-test.cpp b/tests/lib/time/time-mutation-test.cpp index e1d461334..e54b32655 100644 --- a/tests/lib/time/time-mutation-test.cpp +++ b/tests/lib/time/time-mutation-test.cpp @@ -246,10 +246,10 @@ namespace test{ CHECK (t.dur == t.var + change * FrameRate::PAL.duration()); // ....this time the change was measured in grid units, // taken relative to the origin of the specified grid - PGrid testGrid = TimeGrid::retrieve("test_grid"); + PQuant testGrid = Quantiser::retrieve("test_grid"); Offset distance (testGrid->timeOf(0), testGrid->timeOf(change)); CHECK (distance == change * FrameRate::PAL.duration()); - CHECK (Time(t.dur) - t.var == distance); + CHECK (t.dur - t.var == distance); From 11137f76342cee6b7958de6bfd8e371c534a8649 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 28 Apr 2011 03:21:00 +0200 Subject: [PATCH 19/28] provide implementation of the nudge-Mutation --- src/lib/time/mutation.cpp | 87 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/src/lib/time/mutation.cpp b/src/lib/time/mutation.cpp index 6e0082f6e..e98bafb33 100644 --- a/src/lib/time/mutation.cpp +++ b/src/lib/time/mutation.cpp @@ -27,13 +27,14 @@ #include "lib/time/timecode.hpp" #include "lib/time/mutation.hpp" //#include "lib/time.h" -//#include "lib/util.hpp" +#include "lib/util.hpp" //#include //using std::string; //using util::unConst; //using util::floorwrap; +using util::isnil; namespace error = lumiera::error; @@ -201,6 +202,84 @@ namespace time { }; + /** + * concrete time value mutation: + * nudge target value by the given number of 'steps', + * relative to either the given grid. + */ + class NudgeMutation + : public ImposeOffsetMutation + { + + static Offset + materialiseGridPoint (Symbol gridID, int steps) + { + REQUIRE (!isnil (gridID)); + PQuant grid = Quantiser::retrieve(gridID); + return Offset(grid->timeOf(0), grid->timeOf(steps)); + } + + public: + NudgeMutation (int relativeSteps, Symbol gridID) + : ImposeOffsetMutation(materialiseGridPoint (gridID,relativeSteps)) + { } + }; + + + /** + * concrete time value mutation: + * nudge based on a implicit grid, which is + * either a quantised target value's own grid, + * or a \em natural grid. + * @note currently the natural grid is hard wired, + * just interpreting the step parameter as + * offset in seconds. + */ + class NaturalNudgeMutation + : public ClonableMutation + { + int steps_; + + virtual void + change (Duration& target) const + { + imposeChange (target, TimeVar(target)+=Time(FSecs(steps_))); + } + + + virtual void + change (TimeSpan& target) const + { + imposeChange (target, TimeVar(target)+=Time(FSecs(steps_))); + } + + + /** Special treatment: use the quantised time's own grid; + * retrieve the corresponding grid point, offset it by the step-parameter, + * then retrieve the corresponding time from the quantised time's + * underlying quantiser (grid). + * @note when the #steps_ parameter is zero, what happens here effectively + * is the materialisation of the quantised target time, i.e. making + * the quantisation explicit and storing the resulting value. */ + virtual void + change (QuTime& target) const + { + long gridPoint = FrameNr(target)+steps_; + imposeChange (target, PQuant(target)->timeOf(gridPoint)); + } + + + public: + explicit + NaturalNudgeMutation (int relativeSteps) + : steps_(relativeSteps) + { } + }; + + + + + /** Convenience factory to yield a simple Mutation changing the absolute start time. * This whole procedure might look quite inefficient, but actually most of the @@ -257,14 +336,14 @@ namespace time { * will be used to calculate the offset value. Typically, this grid counts in seconds. * To the contrary, when the target is a quantised value, it will be aligned to the * grid point relative to the current value's next grid point, measured in number - * of steps. This includes \em materialising the internal to the exact grid + * of steps. This includes \em materialising the internal time to the exact grid * position. If especially the adjustment is zero, the internal value will * be changed to literally equal the current value's next grid point. */ EncapsulatedMutation Mutation::nudge (int adjustment) { - UNIMPLEMENTED ("Nudge by a predefined nudge amount"); + return EncapsulatedMutation::build (adjustment); } @@ -286,7 +365,7 @@ namespace time { EncapsulatedMutation Mutation::nudge (int adjustment, Symbol gridID) { - UNIMPLEMENTED ("Nudge by a predefined nudge amount"); + return EncapsulatedMutation::build (adjustment, gridID); } From 80f7dba3341b6e3c2af8ec5b7c8f54c3644e80b9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 29 Apr 2011 04:04:48 +0200 Subject: [PATCH 20/28] WIP some comments and considerations --- src/lib/time/formats.hpp | 2 +- src/lib/time/timecode.cpp | 4 ++-- src/lib/time/timecode.hpp | 26 ++++++++++++++++++++++++-- tests/lib/time/time-formats-test.cpp | 3 +++ tests/lib/time/time-mutation-test.cpp | 2 +- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/lib/time/formats.hpp b/src/lib/time/formats.hpp index aae1226a5..c96496c22 100644 --- a/src/lib/time/formats.hpp +++ b/src/lib/time/formats.hpp @@ -84,7 +84,7 @@ namespace time { static void rebuild (SmpteTC&, QuantR, TimeValue const&); static TimeValue evaluate (SmpteTC const&, QuantR); static uint getFramerate (QuantR, TimeValue const&); - static void rangeLimitStrategy (SmpteTC&); + static void applyRangeLimitStrategy (SmpteTC&); }; diff --git a/src/lib/time/timecode.cpp b/src/lib/time/timecode.cpp index 587293995..14b11fa10 100644 --- a/src/lib/time/timecode.cpp +++ b/src/lib/time/timecode.cpp @@ -131,7 +131,7 @@ namespace time { * orientation (0:0:0:0 - 1sec = -1:59:59:0) */ void - Smpte::rangeLimitStrategy (SmpteTC& tc) + Smpte::applyRangeLimitStrategy (SmpteTC& tc) { if (tc.hours < 0) tc.invertOrientation(); @@ -171,7 +171,7 @@ namespace time { wrapHours (SmpteTC* thisTC, int rawHours) { thisTC->hours.setValueRaw (rawHours); - format::Smpte::rangeLimitStrategy (*thisTC); + format::Smpte::applyRangeLimitStrategy (*thisTC); } diff --git a/src/lib/time/timecode.hpp b/src/lib/time/timecode.hpp index 3b5f320a8..dbaef3829 100644 --- a/src/lib/time/timecode.hpp +++ b/src/lib/time/timecode.hpp @@ -101,7 +101,29 @@ namespace time { /** + * Classical Timecode value reminiscent to SMPTE format. + * After quantisation, the resulting (materialised) time value is + * decimated into a hours, a minutes, a seconds part and the remainder + * is cast into a frame number relative to the seconds. Consequently, + * a SmpteTC representation is always linked implicitly to a specific framerate. * + * \par range extensions + * Historically, SMPTE timecode format was focused mainly on how to encode a + * unique timestamp in a way allowing to 'piggyback' these timestamps into an + * existing (analogue) media data format. As a side effect, quite tight limits + * had to be imposed on the possible component values in such a fixed-length format. + * This whole concern is completely beyond the scope of a typical computer based video + * implementation; thus we can consider ways to extend the value range to be represented + * in this SMPTE-like timecode format: + * - we can allow time values below the zero point + * - we can allow time values beyond 24 hours. + * Several different schemes how to do this extensions could be devised (and in addition, + * we could also wrap around the hours field, jumping from 23:59:59:## to 0:0:0:0). + * Currently, we implement an extension, where the timecode representation is symmetrical + * to the zero point and the hours field is just extended beyond 23 hours. To give an + * example: \c 0:0:0:0 minus 1 frame yields \c -0:0:0:1 + * + * @todo the range extension scheme could be a configurable strategy */ class SmpteTC : public TCode @@ -141,7 +163,7 @@ namespace time { /** - * + * @warning missing implementation */ class HmsTC : public TCode @@ -166,7 +188,7 @@ namespace time { /** - * + * @warning partially missing implementation */ class Secs : public TCode diff --git a/tests/lib/time/time-formats-test.cpp b/tests/lib/time/time-formats-test.cpp index eab218d58..fad531de4 100644 --- a/tests/lib/time/time-formats-test.cpp +++ b/tests/lib/time/time-formats-test.cpp @@ -74,6 +74,9 @@ namespace test{ checkTimecodeUsageCycle () { UNIMPLEMENTED ("full usage cycle for a timecode value"); + + /////////TODO: parse-funktion, wie? + /////////TODO: vieleicht doch irgendwo eine Funktion zum Materialisieren? } diff --git a/tests/lib/time/time-mutation-test.cpp b/tests/lib/time/time-mutation-test.cpp index e54b32655..c21a58a7b 100644 --- a/tests/lib/time/time-mutation-test.cpp +++ b/tests/lib/time/time-mutation-test.cpp @@ -216,7 +216,7 @@ namespace test{ // Here accidentally both the change and t.quant use the same grid. // For a more contrived example, we try to use a different grid... - TimeGrid::build("special_funny_grid", 1); // (1 frame per second, no offset) + TimeGrid::build("special_funny_grid", 1, Time(0,-10)); // (1 frame per second, zero point at -10s) QuTime funny (original, "special_funny_grid"); funny.accept (Mutation::materialise (change)); CHECK (funny == t.quant); // leading to the same raw value this far From ccd40c3fee25e73e01c83e37f17745846f1271e5 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 5 May 2011 19:05:48 +0200 Subject: [PATCH 21/28] better use a dedicated 'materialise' operation on the Quantiser --- src/lib/time/mutation.cpp | 8 +++++--- src/lib/time/quantiser.cpp | 23 ++++++++++++++++++++++- src/lib/time/quantiser.hpp | 1 + 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/lib/time/mutation.cpp b/src/lib/time/mutation.cpp index e98bafb33..cd2b205c3 100644 --- a/src/lib/time/mutation.cpp +++ b/src/lib/time/mutation.cpp @@ -197,7 +197,7 @@ namespace time { public: explicit MaterialiseIntoTarget (QuTime const& quant) - : SetNewStartTimeMutation (Secs(quant).getTime()) + : SetNewStartTimeMutation (PQuant(quant)->materialise(quant)) { } }; @@ -264,8 +264,10 @@ namespace time { virtual void change (QuTime& target) const { - long gridPoint = FrameNr(target)+steps_; - imposeChange (target, PQuant(target)->timeOf(gridPoint)); + PQuant const& grid (target); + int64_t originalGridPoint = grid->gridPoint(target); + int64_t adjustedGridPoint = originalGridPoint + steps_; + imposeChange (target, grid->timeOf (adjustedGridPoint)); } diff --git a/src/lib/time/quantiser.cpp b/src/lib/time/quantiser.cpp index 6ae061587..6a43b014a 100644 --- a/src/lib/time/quantiser.cpp +++ b/src/lib/time/quantiser.cpp @@ -47,7 +47,7 @@ namespace time { advice::Request query(gridID); PQuant grid_found = query.getAdvice(); if (!grid_found) - throw error::Logic ("unable to fetch the quantisation grid -- is it already defined?" + throw error::Logic ("unable to fetch the quantisation grid -- was it already defined?" ////////TICKET #197 , LUMIERA_ERROR_UNKNOWN_GRID); return grid_found; } @@ -112,6 +112,27 @@ namespace time { } + + /** convenience shortcut: \em materialise a raw time value + * based on this grid or time axis, but returning a raw time value. + * Implemented as combination of the #gridAlign and #timeOf operations, + * i.e. we quantise into this scale, but transform the result back onto + * the raw time value scale. + * @warning this operation incurs information loss. Values may be rounded + * and / or clipped, according to the grid used. And, contrary to + * a QuTime value, the information about the actual grid is + * discarded. Please don't use this operation if you just + * "want a number" but feel to too lazy to understand + * properly what quantisation means! + */ + TimeValue + Quantiser::materialise (TimeValue const& raw) const + { + return timeOf (gridPoint (raw)); + } + + + /** alignment to a simple fixed size grid. * The actual calculation first determines the number * of the grid interval containing the given rawTime, diff --git a/src/lib/time/quantiser.hpp b/src/lib/time/quantiser.hpp index a8efad7a1..33c4e9d3f 100644 --- a/src/lib/time/quantiser.hpp +++ b/src/lib/time/quantiser.hpp @@ -93,6 +93,7 @@ namespace time { } static PQuant retrieve (Symbol gridID); + TimeValue materialise (TimeValue const& raw) const; //------Grid-API---------------------------------------------- From 10215948a55f000442856642aecd325ba0cacd61 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 5 May 2011 19:11:07 +0200 Subject: [PATCH 22/28] Fix another 64/32bit loss-of-precision problem --- src/lib/time/grid.hpp | 4 ++-- src/lib/time/quantiser.cpp | 4 ++-- src/lib/time/quantiser.hpp | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/time/grid.hpp b/src/lib/time/grid.hpp index 4053fa5b0..7ace4a12e 100644 --- a/src/lib/time/grid.hpp +++ b/src/lib/time/grid.hpp @@ -62,9 +62,9 @@ namespace time { virtual ~Grid(); ///< this is an Interface - virtual long gridPoint (TimeValue const& raw) const =0; + virtual int64_t gridPoint (TimeValue const& raw) const =0; virtual TimeValue gridAlign (TimeValue const& raw) const =0; - virtual TimeValue timeOf (long gridPoint) const =0; + virtual TimeValue timeOf (int64_t gridPoint) const =0; virtual TimeValue timeOf (FSecs, int =0) const =0; }; diff --git a/src/lib/time/quantiser.cpp b/src/lib/time/quantiser.cpp index 6a43b014a..fcb916004 100644 --- a/src/lib/time/quantiser.cpp +++ b/src/lib/time/quantiser.cpp @@ -160,7 +160,7 @@ namespace time { * range when converting back into a TimeValue. * @see #lumiera_quantise_frames */ - long + int64_t FixedFrameQuantiser::gridPoint (TimeValue const& rawTime) const { return lumiera_quantise_frames (_raw(rawTime), _raw(origin_), _raw(raster_)); @@ -173,7 +173,7 @@ namespace time { * valid range of lumiera::Time */ TimeValue - FixedFrameQuantiser::timeOf (long gridPoint) const + FixedFrameQuantiser::timeOf (int64_t gridPoint) const { return TimeValue (lumiera_time_of_gridpoint (gridPoint, _raw(origin_), _raw(raster_))); } diff --git a/src/lib/time/quantiser.hpp b/src/lib/time/quantiser.hpp index 33c4e9d3f..06df4d3f1 100644 --- a/src/lib/time/quantiser.hpp +++ b/src/lib/time/quantiser.hpp @@ -97,9 +97,9 @@ namespace time { //------Grid-API---------------------------------------------- - virtual long gridPoint (TimeValue const& raw) const =0; + virtual int64_t gridPoint (TimeValue const& raw) const =0; virtual TimeValue gridAlign (TimeValue const& raw) const =0; - virtual TimeValue timeOf (long gridPoint) const =0; + virtual TimeValue timeOf (int64_t gridPoint) const =0; virtual TimeValue timeOf (FSecs, int =0) const =0; }; @@ -126,9 +126,9 @@ namespace time { FixedFrameQuantiser (FrameRate const& frames_per_second, TimeValue referencePoint =TimeValue(0)); FixedFrameQuantiser (Duration const& frame_duration, TimeValue referencePoint =TimeValue(0)); - long gridPoint (TimeValue const&) const; + int64_t gridPoint (TimeValue const&) const; TimeValue gridAlign (TimeValue const&) const; - TimeValue timeOf (long gridPoint) const; + TimeValue timeOf (int64_t gridPoint) const; TimeValue timeOf (FSecs, int =0) const; }; From 3139fb7f1ebda5f00e5bb2a0d42dc3df77470e5d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 13 May 2011 04:04:02 +0200 Subject: [PATCH 23/28] more 64/32bit fun --- src/lib/time.cpp | 24 ++++++++++++++++-------- src/lib/time.h | 20 +++++++++++++++++++- src/lib/time/timecode.cpp | 16 ++++++++-------- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/lib/time.cpp b/src/lib/time.cpp index 7cbe53004..9a760ac4d 100644 --- a/src/lib/time.cpp +++ b/src/lib/time.cpp @@ -81,6 +81,14 @@ lumiera_rational_to_time (FSecs const& fractionalSeconds) return rational_cast (GAVL_TIME_SCALE * fractionalSeconds); } +gavl_time_t +lumiera_framecount_to_time (uint64_t frameCount, FrameRate const& fps) +{ + // convert to 64bit + boost::rational framerate (fps.numerator(), fps.denominator()); + + return rational_cast (GAVL_TIME_SCALE * frameCount / framerate); +} gavl_time_t lumiera_frame_duration (FrameRate const& fps) @@ -122,7 +130,7 @@ lumiera_quantise_time (gavl_time_t time, gavl_time_t origin, gavl_time_t grid) } gavl_time_t -lumiera_time_of_gridpoint (long nr, gavl_time_t origin, gavl_time_t grid) +lumiera_time_of_gridpoint (int64_t nr, gavl_time_t origin, gavl_time_t grid) { gavl_time_t offset = nr * grid; return origin + offset; @@ -154,14 +162,14 @@ lumiera_build_time_fps (float fps, uint frames, uint secs, uint mins, uint hours gavl_time_t lumiera_build_time_ntsc_drop (uint frames, uint secs, uint mins, uint hours) { - int total_mins = 60 * hours + mins; - int total_frames = 108000 * hours - + 1800 * mins - + 30 * secs - + frames - - 2 * (total_mins - total_mins / 10); + uint64_t total_mins = 60 * hours + mins; + uint64_t total_frames = 108000 * hours + + 1800 * mins + + 30 * secs + + frames + - 2 * (total_mins - total_mins / 10); - return (total_frames / 29.97f) * 1000 * GAVL_TIME_SCALE_MS; + return lumiera_framecount_to_time (total_frames, FrameRate::NTSC); } int diff --git a/src/lib/time.h b/src/lib/time.h index fb1a7992a..c017f6713 100644 --- a/src/lib/time.h +++ b/src/lib/time.h @@ -69,6 +69,16 @@ gavl_time_t lumiera_rational_to_time (lib::time::FSecs const& fractionalSeconds); +/** + * Converts a frame count into Lumiera's internal time scale. + * based on a framerate given as rational number (e.g. NTSC) + * @note handles only positive frame counts and assumes the + * origin to be at zero. + */ +gavl_time_t +lumiera_framecount_to_time (uint64_t frameCount, lib::time::FrameRate const& fps); + + /** * Calculates the duration of one frame in Lumiera time units. * @param framerate underlying framerate as rational number @@ -125,7 +135,7 @@ lumiera_quantise_time (gavl_time_t time, gavl_time_t origin, gavl_time_t grid); * @return time point (frame start) on the Lumiera internal time scale */ gavl_time_t -lumiera_time_of_gridpoint (long nr, gavl_time_t origin, gavl_time_t grid); +lumiera_time_of_gridpoint (int64_t nr, gavl_time_t origin, gavl_time_t grid); /** * Builds a time value by summing up the given components. @@ -140,6 +150,11 @@ lumiera_build_time (long millis, uint secs, uint mins, uint hours); /** * Builds a time value by summing up the given components. * @todo replace float framerates by lib::time::FrameRate + * @deprecated this function doesn't fit in the general time handling concept. + * SMPTE is a timecode format, and should be distinguished from a + * raw time value. For any timecode format, we always require an explicit + * 'time grid' (coordinate system), which also defines a zero point. + * Usually, this time grid is managed and provided by the session */ gavl_time_t lumiera_build_time_fps (float fps, uint frames, uint secs, uint mins, uint hours); @@ -183,6 +198,9 @@ lumiera_time_frames (gavl_time_t time, float fps); /** * Extract the frame count for the given time, using the given fps. * @todo use the rational lib::time::FrameRate instead of a float + * @deprecated should be moved down into the implementation; + * this function doesn't fit into the general time handling, + * because it doesn't link to a time grid (coordinate system) */ int lumiera_time_frame_count (gavl_time_t time, float fps); diff --git a/src/lib/time/timecode.cpp b/src/lib/time/timecode.cpp index 14b11fa10..6ec229ae6 100644 --- a/src/lib/time/timecode.cpp +++ b/src/lib/time/timecode.cpp @@ -79,11 +79,11 @@ namespace time { TimeValue Smpte::evaluate (SmpteTC const& tc, QuantR quantiser) { - long frameRate = tc.getFps(); - long gridPoint = tc.frames - + tc.secs * frameRate - + tc.mins * frameRate * 60 - + tc.hours * frameRate * 60 * 60; + uint frameRate = tc.getFps(); + int64_t gridPoint(tc.frames); + gridPoint += int64_t(tc.secs) * frameRate; + gridPoint += int64_t(tc.mins) * frameRate * 60; + gridPoint += int64_t(tc.hours) * frameRate * 60 * 60; return quantiser.timeOf (tc.sgn * gridPoint); } @@ -103,9 +103,9 @@ namespace time { uint Smpte::getFramerate (QuantR quantiser_, TimeValue const& rawTime) { - long refCnt = quantiser_.gridPoint(rawTime); - long newCnt = quantiser_.gridPoint(Time(0.5,1) + rawTime); - long effectiveFrames = newCnt - refCnt; + int64_t refCnt = quantiser_.gridPoint(rawTime); + int64_t newCnt = quantiser_.gridPoint(Time(0,1) + rawTime); + int64_t effectiveFrames = newCnt - refCnt; ENSURE (1000 > effectiveFrames); ENSURE (0 < effectiveFrames); return uint(effectiveFrames); From 6b1b6cb805a306a76d8f66fda2350edec3c526ff Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 13 May 2011 06:28:55 +0200 Subject: [PATCH 24/28] draft how to integrate parsing of timecode formats --- src/lib/query.cpp | 3 +- src/lib/time/formats.hpp | 8 +++ src/lib/time/timecode.cpp | 103 ++++++++++++++++++++++++++- src/lib/time/timecode.hpp | 21 ++++-- src/lib/util.hpp | 2 +- tests/lib/time/time-formats-test.cpp | 2 +- wiki/renderengine.html | 8 +-- 7 files changed, 132 insertions(+), 15 deletions(-) diff --git a/src/lib/query.cpp b/src/lib/query.cpp index e6ff8e918..5683a0ac7 100644 --- a/src/lib/query.cpp +++ b/src/lib/query.cpp @@ -23,6 +23,7 @@ #include "lib/query.hpp" #include "lib/util.hpp" +#include "lib/symbol.hpp" #include "include/logging.h" #include @@ -74,7 +75,7 @@ namespace lumiera { map regexTable; - Symbol matchArgument = "\\(\\s*([\\w_\\.\\-]+)\\s*\\),?\\s*"; + Literal matchArgument = "\\(\\s*([\\w_\\.\\-]+)\\s*\\),?\\s*"; regex findPredicate (string("(\\w+)")+matchArgument); inline regex& diff --git a/src/lib/time/formats.hpp b/src/lib/time/formats.hpp index c96496c22..a2af01c6f 100644 --- a/src/lib/time/formats.hpp +++ b/src/lib/time/formats.hpp @@ -31,6 +31,7 @@ #include "lib/typed-counter.hpp" #include +#include #include @@ -53,6 +54,9 @@ namespace time { namespace format { + LUMIERA_ERROR_DECLARE (INVALID_TIMECODE); ///< timecode format error, illegal value encountered. + + using std::string; using lib::meta::NoInstance; // the following types are for metaprogramming only... @@ -67,6 +71,7 @@ namespace time { struct Frames : NoInstance { + static TimeValue parse (string const&, QuantR); static void rebuild (FrameNr&, QuantR, TimeValue const&); static TimeValue evaluate (FrameNr const&, QuantR); }; @@ -81,6 +86,7 @@ namespace time { struct Smpte : NoInstance { + static TimeValue parse (string const&, QuantR); static void rebuild (SmpteTC&, QuantR, TimeValue const&); static TimeValue evaluate (SmpteTC const&, QuantR); static uint getFramerate (QuantR, TimeValue const&); @@ -98,6 +104,7 @@ namespace time { struct Hms : NoInstance { + static TimeValue parse (string const&, QuantR); static void rebuild (HmsTC&, QuantR, TimeValue const&); static TimeValue evaluate (HmsTC const&, QuantR); }; @@ -115,6 +122,7 @@ namespace time { struct Seconds : NoInstance { + static TimeValue parse (string const&, QuantR); static void rebuild (Secs&, QuantR, TimeValue const&); static TimeValue evaluate (Secs const&, QuantR); }; diff --git a/src/lib/time/timecode.cpp b/src/lib/time/timecode.cpp index 6ec229ae6..3f0331e7e 100644 --- a/src/lib/time/timecode.cpp +++ b/src/lib/time/timecode.cpp @@ -30,12 +30,19 @@ #include "lib/util.hpp" #include +#include +#include using std::string; using util::unConst; using util::isSameObject; using util::floorwrap; +using boost::regex; +using boost::smatch; +using boost::regex_search; +using boost::lexical_cast; +namespace error = lumiera::error; namespace lib { namespace time { @@ -43,7 +50,101 @@ namespace time { TCode::~TCode() { } // emit VTable here.... - namespace format { + + namespace format { /* ================= Timecode implementation details ======== */ + + LUMIERA_ERROR_DEFINE (INVALID_TIMECODE, "timecode format error, illegal value encountered"); + + + /** try to parse a frame number specification + * @param frameNumber string containing an integral number with trailing '#' + * @param frameGrid coordinate system (and thus framerate) to use for the conversion + * @return (opaque internal) lumiera time value of the given frame's start position + * @throw error::Invalid in case of parsing failure + * @note the string may contain any additional content, as long as a + * regular-expression search is able to pick out a suitable value + */ + TimeValue + Frames::parse (string const& frameNumber, QuantR frameGrid) + { + static regex frameNr_parser ("(-?\\d+)#"); + smatch match; + if (regex_search (frameNumber, match, frameNr_parser)) + return frameGrid.timeOf (lexical_cast (match[1])); + else + throw error::Invalid ("unable to parse framecount \""+frameNumber+"\"" + , LUMIERA_ERROR_INVALID_TIMECODE); + } + + + TimeValue + Smpte::parse (string const&, QuantR) + { + UNIMPLEMENTED("parsing SMPTE timecode"); + } + + + TimeValue + Hms::parse (string const&, QuantR) + { + UNIMPLEMENTED("parse a hours:mins:secs time specification"); + } + + + /** try to parse a time specification in seconds or fractional seconds. + * The value is interpreted relative to the origin of a the given time grid + * This parser recognises full seconds, fractional seconds and both together. + * In any case, the actual number is required to end with a trailing \c 'sec' + * @par Example specifications +\verbatim + 12sec --> 12 * GAVL_TIME_SCALE + -4sec --> -4 * GAVL_TIME_SCALE + 5/4sec --> 1.25 * GAVL_TIME_SCALE + -5/25sec --> -0.2 * GAVL_TIME_SCALE + 1+1/2sec --> 1.5 * GAVL_TIME_SCALE + 1-1/25sec --> 0.96 * GAVL_TIME_SCALE + -12-1/4sec --> -11.75 * GAVL_TIME_SCALE +\endverbatim + * @param seconds string containing a time spec in seconds + * @param grid coordinate system the parsed value is based on + * @return the corresponding (opaque internal) lumiera time value + * @throw error::Invalid in case of parsing failure + * @note the string may contain any additional content, as long as a + * regular-expression search is able to pick out a suitable value + */ + TimeValue + Seconds::parse (string const& seconds, QuantR grid) + { + static regex fracSecs_parser ("(-?\\d+)(?:([\\-\\+]\\d+)?/(\\d+))?sec"); + + #define SUB_EXPR(N) lexical_cast (match[N]) + smatch match; + if (regex_search (seconds, match, fracSecs_parser)) + if (match[2].matched) + { + // complete spec with all parts + FSecs fractionalPart (SUB_EXPR(2), SUB_EXPR(3)); + long fullSeconds (SUB_EXPR(1)); + return grid.timeOf (fullSeconds + fractionalPart); + } + else + if (match[3].matched) + { + // only a fractional part was given + return grid.timeOf (FSecs (SUB_EXPR(1), SUB_EXPR(3))); + } + else + { + // just simple non-fractional seconds + return grid.timeOf (FSecs (SUB_EXPR(1))); + } + else + throw error::Invalid ("unable to parse \""+seconds+"\" as (fractional)seconds" + , LUMIERA_ERROR_INVALID_TIMECODE); + } + + + /** build up a frame count * by quantising the given time value diff --git a/src/lib/time/timecode.hpp b/src/lib/time/timecode.hpp index dbaef3829..412b14f3b 100644 --- a/src/lib/time/timecode.hpp +++ b/src/lib/time/timecode.hpp @@ -45,7 +45,7 @@ namespace time { /** * Interface: fixed format timecode specification. - * @see time::Format + * @see time::format * @todo WIP-WIP-WIP */ class TCode @@ -76,18 +76,25 @@ namespace time { /** * A frame counting timecode value. - * This is an hard-coded representation of - * TCode, with additional convenience - * constructors and conversions, which basically make - * FrameNr values interchangeable with integral numbers. + * This is the hard-coded standard representation of + * format::Frames, and is defined such as to make FrameNr + * values interchangeable with integral numbers. + * Like any concrete TCode subclass, it can be created + * based on a QuTime value. This way, not only the (raw) TimeValue + * is provided, but also the (frame)-Grid to base the frame count on. + * But contrary to a QuTime value, a FrameNr value is \em materialised + * (rounded) into a definite integral number, stripping the excess + * precision contained in the original (raw) TimeValue. + * As framecount values are implemented as single display field for an + * integral value (time::Digxel), they allow for simple presentation. */ class FrameNr : public TCode , public CountVal { - string show() const { return string(CountVal::show())+"fr"; } - Literal tcID() const { return "Frame-count"; } + string show() const { return string(CountVal::show())+"#"; } + Literal tcID() const { return "Framecount"; } TimeValue value() const { return Format::evaluate (*this, *quantiser_); } public: diff --git a/src/lib/util.hpp b/src/lib/util.hpp index 3fc2cb97b..037881883 100644 --- a/src/lib/util.hpp +++ b/src/lib/util.hpp @@ -315,7 +315,7 @@ namespace util { * will start with an alphanumeric character. * * @par Example Conversions -\verbatim +\verbatim "Word" --> 'Word' "a Sentence" --> 'a_Sentence' "trailing Withespace \t \n" --> 'trailing_Withespace' diff --git a/tests/lib/time/time-formats-test.cpp b/tests/lib/time/time-formats-test.cpp index fad531de4..b9f6d4def 100644 --- a/tests/lib/time/time-formats-test.cpp +++ b/tests/lib/time/time-formats-test.cpp @@ -97,7 +97,7 @@ namespace test{ void checkSeconds () { - UNIMPLEMENTED ("verify seconds as timecode format"); + UNIMPLEMENTED ("verify fractional seconds as timecode format"); } diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 2cca48ddd..a8d999fa7 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -6764,7 +6764,7 @@ At the level of individual timecode formats, we're lacking a common denominator; &rarr; Quantiser [[implementation details|QuantiserImpl]]
-
+
the following collection of usage situations helps to shape the details of the time values and time quantisation design. &rarr; see also  [[time quantisation|TimeQuant]]
 
 ;time position of an object
@@ -6808,7 +6808,7 @@ The ''problem with playback position'' is -- that indeed it's an attempt to conc
 Note that the ''display window might be treated as just an independent instance of quantisation''. This is similar to the approach taken above for modifying quantised time span values. We should provide a special kind of time grid, the display coordinates. The origin of these is always defined to the left (lower) side of the interval to be displayed, and they are gauged in screen units (pixels or similar, as used by the GUI toolkit set). The rest is handled by the general quantisation mechanisms. The problem of aligning the display should be transformed into a general facility to align grids, and solved for the general case. Doing so solves the remaining problems with quantised value changes and with ''specifying relative placements'' as well: If we choose to represent them as quantised values, we might (or might not) also choose to apply this //grid-alignment function.//
 
 !the complete time value usage cycle
-The way this time value and quantisation handling is designed in Lumiera creates a typical usage path, which actually is a one-way route. We might start out with a textual representation according to a specific ''timecode'' format. This timecode string may be parsed, and typically it will be ''materialised'', by invoking the {{{value()}}}-function. This brings us (back) to the very origin, which is a raw timevalue (''internal time'' value). Now, this time value might be manipulated, compared to other values, combined into a ''time span'' (time point and duration -- the most basic notion of an //object// to be manipulated in the Session). Anyway, at some point these time values need to be related to some ''time scale'' again, leading to ''quantised'' time values, which -- finally -- can be cast into a timecode format for external representation again, thus closing the circle.
+The way time value and quantisation handling is designed in Lumiera creates a typical usage path, which actually is a one-way route. We might start out with a textual representation according to a specific ''timecode'' format. Assumed we know the implicit underlying ''time grid'' (coordinate system, framerate), this timecode string may be parsed. This brings us (back) to the very origin, which is a raw ~TimeValue (''internal time'' value). Now, this time value might be manipulated, compared to other values, combined into a ''time span'' (time point and duration -- the most basic notion of an //object// to be manipulated in the Session). Anyway, at some point these time values need to be related to some ''time scale'' again, leading to ''quantised'' time values, which -- finally -- can be cast into a timecode format for external representation again, thus closing the circle.
 
 !substantial problems to be solved
 * how to [[align multiple grids|TimeGridAlignment]]
@@ -7092,14 +7092,14 @@ For now, as of 6/10, we use specialised QueryResolver instances explicitly and d
 &rarr; QueryRegistration
 
-
+
for showing output, three entities are involved
 * the [[Timeline]] holds the relevant part of the model, which gets rendered for playback
 * by connecting to a viewer component, an actual output target is established
 * the playback process itself is coordinated by a PlayController, which in turn creates a PlayProcess
 
 !the viewer connection
-A viewer element gets connected to a given timeline either by directly attaching it, or by //allocating an available free viewer.// Anyway, as a model element, the viewer is just like another set of global pipes cascaded behind the global pipes present in the timeline. The number and kind of pipes provided is a configurable property of the viewer element &mdash; more specifically: the viewer's SwitchBoard. Thus, connecting a viewer activates the same internal logic employed when connecting a sequence into a timeline or meta-clip: a default channel association is established, which can be overridden persistently (&rarr; OutputMapping). Each of the viewer's pipes in turn gets connected to a system output through an OutputManager slot &mdash; again an output mapping step.
+A viewer element gets connected to a given timeline either by directly attaching it, or by //allocating an available free viewer.// Anyway, as a model element, the viewer is just like another set of global pipes chained up after the global pipes present in the timeline. The number and kind of pipes provided is a configurable property of the viewer element &mdash; more specifically: the viewer's SwitchBoard. Thus, connecting a viewer activates the same internal logic employed when connecting a sequence into a timeline or meta-clip: a default channel association is established, which can be overridden persistently (&rarr; OutputMapping). Each of the viewer's pipes in turn gets connected to a system output through an OutputManager slot &mdash; again an output mapping step.
 
From c7bb7154a720071916737f5b4e2c4016d1a88cdc Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 13 May 2011 07:46:21 +0200 Subject: [PATCH 25/28] draft a test to cover parsing of timecode values --- src/lib/time/lumitime.cpp | 2 +- tests/40components.tests | 4 + tests/lib/time/time-parsing-test.cpp | 183 +++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 tests/lib/time/time-parsing-test.cpp diff --git a/src/lib/time/lumitime.cpp b/src/lib/time/lumitime.cpp index af3313772..bb1c0e1a0 100644 --- a/src/lib/time/lumitime.cpp +++ b/src/lib/time/lumitime.cpp @@ -58,7 +58,7 @@ namespace time { , uint hours ) : TimeValue(lumiera_build_time (millis,secs,mins,hours)) - { } + { } /** convenience constructor to build an Time value diff --git a/tests/40components.tests b/tests/40components.tests index 51b9c54bc..c8cd30aad 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -704,6 +704,10 @@ return: 0 END +PLANNED "Parsing time values" TimeParsing_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 "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "proc/asset/meta/time-grid.hpp" +#include "lib/time/quantiser.hpp" +//#include "lib/time/timequant.hpp" +#include "lib/time/timecode.hpp" +//#include "lib/time/display.hpp" +#include "lib/symbol.hpp" +#include "lib/util.hpp" + +//#include +//#include +//#include + +//using boost::lexical_cast; +//using util::isnil; +using lib::Symbol; +using util::cStr; +//using std::rand; +//using std::cout; +//using std::endl; + + +namespace lib { +namespace time{ +namespace test{ + + using asset::meta::TimeGrid; + using format::LUMIERA_ERROR_INVALID_TIMECODE; + + namespace { // Helper for writing test cases + + Symbol DEFAULT_GRID = "pal0"; + Symbol OFFSET_GRID = "pal10"; + + /** + * Expression builder + * for writing time value parsing tests + */ + template + class Parsing + { + string const& timeSpec_; + PQuant grid_; + + public: + Parsing (string const& toParse, Symbol gridID =DEFAULT_GRID) + : timeSpec_(toParse) + , grid_(Quantiser::retrieve(gridID)) + { } + + + void + should_yield (TimeValue const& expected) + { + TimeValue parsed = FMT::parse (timeSpec_, *grid_); + CHECK (parsed == expected, "parsing '%s' resulted in %s instead of %s" + , cStr(timeSpec_) + , cStr(Time(parsed)) + , cStr(Time(expected))); + } + + void + should_yield (FSecs const& expectedSecs) + { + should_yield (Time (expectedSecs)); + } + + void + should_fail () + { + VERIFY_ERROR (INVALID_TIMECODE, FMT::parse (timeSpec_, *grid_)); + } + + }; + + }//(End)Test case helper + + + + + /******************************************************** + * @test parse textual time specifications given + * in the supported timecode formats + */ + class TimeParsing_test : public Test + { + virtual void + run (Arg) + { + TimeGrid::build("pal0", FrameRate::PAL); + TimeGrid::build("pal10", FrameRate::PAL, Time(0,10)); + + parseFrames(); + parseFractionalSeconds(); +// parseHms(); +// parseSmpte(); +// parseDropFrame(); + } + + + void + parseFrames () + { + Parsing ("0#") .should_yield (0); + Parsing ("1#") .should_yield (FSecs(1,25) ); + Parsing ("-1#") .should_yield (FSecs(-1,25) ); + Parsing ("-0#") .should_yield (0); + Parsing ("25#") .should_yield (1 ); + Parsing ("26#") .should_yield (Time(40,1) ); + Parsing ("25#", OFFSET_GRID).should_yield (1+10 ); + Parsing ("-1#", OFFSET_GRID).should_yield (10 - FSecs(1,25)); + + Parsing ("23") .should_fail(); + Parsing ("23 #") .should_fail(); + Parsing ("23.#") .should_fail(); + Parsing ("23x#") .should_fail(); + + Parsing ("xxx25#xxx") .should_yield (1); + Parsing ("12 25#") .should_yield (1); + Parsing ("12 25# 33#") .should_yield (1); + Parsing ("12\n 25# \n 33#") .should_yield (1); + Parsing ("12.25#") .should_yield (1); + } + + + void + parseFractionalSeconds () + { + UNIMPLEMENTED ("verify reading fractional seconds as timecode format"); + } + + + void + parseHms () + { + UNIMPLEMENTED ("verify reading hour-minutes-seconds-millis time specs"); + } + + + void + parseSmpte () + { + } + + + void + parseDropFrame () + { + UNIMPLEMENTED ("verify especially SMPTE-drop-frame timecode"); + } + }; + + + /** Register this test class... */ + LAUNCHER (TimeParsing_test, "unit common"); + + + +}}} // namespace lib::time::test From f85c86d6c81613dcf84bd3fff41df67a4e22e61b Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 14 May 2011 04:17:22 +0200 Subject: [PATCH 26/28] parsing fractional seconds: unit test pass --- src/lib/time/timecode.cpp | 5 ++- tests/40components.tests | 2 +- tests/lib/time/time-parsing-test.cpp | 64 +++++++++++++++++++++------- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/src/lib/time/timecode.cpp b/src/lib/time/timecode.cpp index 3f0331e7e..4cce6fd66 100644 --- a/src/lib/time/timecode.cpp +++ b/src/lib/time/timecode.cpp @@ -67,7 +67,7 @@ namespace time { TimeValue Frames::parse (string const& frameNumber, QuantR frameGrid) { - static regex frameNr_parser ("(-?\\d+)#"); + static regex frameNr_parser ("(? (match[1])); @@ -115,7 +115,8 @@ namespace time { TimeValue Seconds::parse (string const& seconds, QuantR grid) { - static regex fracSecs_parser ("(-?\\d+)(?:([\\-\\+]\\d+)?/(\\d+))?sec"); + static regex fracSecs_parser ("(? (match[N]) smatch match; diff --git a/tests/40components.tests b/tests/40components.tests index c8cd30aad..320c283c3 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -704,7 +704,7 @@ return: 0 END -PLANNED "Parsing time values" TimeParsing_test < -//#include -//#include - -//using boost::lexical_cast; -//using util::isnil; using lib::Symbol; using util::cStr; -//using std::rand; -//using std::cout; -//using std::endl; namespace lib { @@ -51,6 +40,7 @@ namespace test{ using asset::meta::TimeGrid; using format::LUMIERA_ERROR_INVALID_TIMECODE; + namespace { // Helper for writing test cases Symbol DEFAULT_GRID = "pal0"; @@ -111,17 +101,25 @@ namespace test{ virtual void run (Arg) { - TimeGrid::build("pal0", FrameRate::PAL); - TimeGrid::build("pal10", FrameRate::PAL, Time(0,10)); + defineTestTimeGrids(); parseFrames(); parseFractionalSeconds(); +/////////////////////////////////////////////TODO // parseHms(); // parseSmpte(); // parseDropFrame(); } + void + defineTestTimeGrids() + { + TimeGrid::build(DEFAULT_GRID, FrameRate::PAL); + TimeGrid::build(OFFSET_GRID, FrameRate::PAL, Time(0,10)); + } + + void parseFrames () { @@ -141,16 +139,50 @@ namespace test{ Parsing ("xxx25#xxx") .should_yield (1); Parsing ("12 25#") .should_yield (1); - Parsing ("12 25# 33#") .should_yield (1); + Parsing ("12 25# 33#") .should_yield (1); // note pitfall: the first valid number is used Parsing ("12\n 25# \n 33#") .should_yield (1); - Parsing ("12.25#") .should_yield (1); + Parsing ("12.25#") .should_fail(); // rejected because of leading dot (ambiguity) } void parseFractionalSeconds () { - UNIMPLEMENTED ("verify reading fractional seconds as timecode format"); + Parsing ("0sec") .should_yield (0); + Parsing ("1sec") .should_yield (1); + Parsing ("10sec") .should_yield (10); + Parsing ("100sec") .should_yield (100); + Parsing ("-10sec") .should_yield (-10); + Parsing ("-0sec") .should_yield (0); + + Parsing ("1/2sec") .should_yield (Time(500,0) ); + Parsing ("1/25sec") .should_yield (Time( 40,0) ); + Parsing ("1/250sec") .should_yield (Time( 4,0) ); // no quantisation involved in parsing + Parsing ("1/250sec", OFFSET_GRID).should_yield (Time(4,10)); // ...but the origin of the grid is used + + Parsing ("10/2sec") .should_yield (5); + Parsing ("1000/200sec") .should_yield (5); + Parsing ("-10/2sec") .should_yield (-5); + Parsing ("10/-2sec") .should_fail(); // only leading sign allowed (ambiguity) + + Parsing ("1+1/2sec") .should_yield (Time(500,1) ); + Parsing ("1-1/2sec") .should_yield (Time(500,0) ); + Parsing ("-1-1/2sec") .should_yield (-Time(500,1) ); + Parsing ("-1+1/2sec") .should_yield (-Time(500,0) ); + Parsing ("-1+1/-2sec") .should_fail(); + + Parsing ("-12+24690/12345sec", OFFSET_GRID).should_yield(0); // origin=+10sec -12sec + 2/1sec == 0 + + Parsing ("1") .should_fail(); + Parsing ("1 sec") .should_fail(); + Parsing ("--1sec") .should_fail(); + Parsing ("/-1sec") .should_fail(); + Parsing ("1.2sec") .should_fail(); + Parsing ("1/.2sec") .should_fail(); + Parsing ("1 + 2 / 4 sec") .should_fail(); + Parsing ("1 + 2 / 4sec") .should_yield(4); // note pitfall: leading garbage not considered + Parsing ("xxx4secxxxx") .should_yield(4); + Parsing ("x1# 8/2sec 2sec").should_yield(4); // note pitfall: first valid number used } From 4bf53bba0f591f585690f23298fcd3ff3999f3f0 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 14 May 2011 21:18:13 +0200 Subject: [PATCH 27/28] code unit test to demonstrate full time/timecode usage cycle --- src/lib/time/timecode.hpp | 3 +- tests/40components.tests | 3 ++ tests/lib/time/time-formats-test.cpp | 75 +++++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/lib/time/timecode.hpp b/src/lib/time/timecode.hpp index 412b14f3b..fafe52d9c 100644 --- a/src/lib/time/timecode.hpp +++ b/src/lib/time/timecode.hpp @@ -102,7 +102,8 @@ namespace time { FrameNr (QuTime const& quantisedTime); - // CountVal implicitly convertible to long + using TCode::operator string; + // CountVal implicitly convertible to long }; diff --git a/tests/40components.tests b/tests/40components.tests index 320c283c3..ca4203a44 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -699,6 +699,9 @@ END TEST "Time formats and timecodes" TimeFormats_test < #include -//#include +#include +#include using boost::lexical_cast; using util::isnil; -//using std::rand; +using std::rand; +using std::string; using std::cout; using std::endl; @@ -45,6 +48,24 @@ namespace time{ namespace test{ using asset::meta::TimeGrid; + using format::Frames; + using format::Smpte; + + namespace{ + const int MAX_FRAME = 265*24*60*60*25; + + string + generateRandomFrameNr() + { + uint frameNr(0); + while (!frameNr) + frameNr = rand() % (2*MAX_FRAME) - MAX_FRAME; + + return lexical_cast(frameNr)+"#"; + } + } + + /******************************************************** * @test verify handling of grid aligned timecode values. @@ -60,7 +81,7 @@ namespace test{ { TimeGrid::build("pal0", FrameRate::PAL); -// checkTimecodeUsageCycle (); + checkTimecodeUsageCycle (); // checkFrames (); // checkSeconds (); // checkHms (); @@ -70,13 +91,54 @@ namespace test{ } + /** @test demonstrate a full usage cycle of timecode and time values. + * Starting with a textual representation according to a specific timecode format, + * and based on the knowledge of the implicit underlying time grid (coordinate system, + * here with origin=0 and framerate=25fps), this timecode string may be parsed. + * This brings us (back) to the very origin, which is a raw TimeValue (internal time). + * Now, this time value might be manipulated, compared to other values etc. + * Anyway, at some point these time values need to be related to some time scale again, + * leading to quantised time values, which — finally — can be cast into a timecode format + * for external representation again, thus closing the circle. + */ void checkTimecodeUsageCycle () { - UNIMPLEMENTED ("full usage cycle for a timecode value"); + string quellCode = generateRandomFrameNr(); + PQuant refScale = Quantiser::retrieve("pal0"); - /////////TODO: parse-funktion, wie? - /////////TODO: vieleicht doch irgendwo eine Funktion zum Materialisieren? + // get internal (raw) time value + TimeValue t1 = format::Frames::parse(quellCode, *refScale); + ENSURE (0 != t1); + + // manipulating + TimeVar v1(t1); + v1 += Time(FSecs(6,5)); + CHECK (t1 < v1); + + // quantising into an external grid + QuTime q1 (t1, "pal0"); + CHECK (q1 == t1); + + // further mutations (here nudge by +5 grid steps) + QuTime q2 = q1; + q2.accept (Mutation::nudge(+5)); + CHECK (q1 < q2); + + // converting (back) into a timecode format + FrameNr frames1(q1); + FrameNr frames2(q2); + CHECK (5 == frames2 - frames1); + + q2.accept (Mutation::changeTime(v1)); + CHECK (30 == q2.formatAs() - frames1); + + CHECK (quellCode == string(frames1)); + CHECK (quellCode != string(frames2)); + + showTimeCode (frames1); + showTimeCode (frames2); + showTimeCode (q2.formatAs()); } @@ -125,6 +187,7 @@ namespace test{ QuTime t1 (raw, "pal0"); SmpteTC smpte(t1); + cout << "----SMPTE-----" << endl; showTimeCode(smpte); CHECK (" 5:42:23:13" == string(smpte)); CHECK (raw - Time(35,0) == smpte.getTime()); // timecode value got quantised towards next lower frame From 3e28c870f5c5e1686ac26247161d6e1fe38ca65d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 14 May 2011 22:52:08 +0200 Subject: [PATCH 28/28] Finish basic Time mutations, unit test pass --- tests/lib/time/time-mutation-test.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tests/lib/time/time-mutation-test.cpp b/tests/lib/time/time-mutation-test.cpp index c21a58a7b..7ad09d379 100644 --- a/tests/lib/time/time-mutation-test.cpp +++ b/tests/lib/time/time-mutation-test.cpp @@ -27,22 +27,18 @@ #include "lib/time/timequant.hpp" #include "lib/time/mutation.hpp" #include "proc/asset/meta/time-grid.hpp" -//#include "lib/time/display.hpp" #include "lib/util.hpp" #include #include -//#include #include using boost::lexical_cast; using util::isnil; -//using std::rand; using std::cout; using std::endl; using std::string; -//using lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE; namespace lib { namespace time{ @@ -62,6 +58,8 @@ namespace test{ } + + /**************************************************************** * @test cover all basic cases for mutating a time specification. * - change to a given value @@ -75,7 +73,7 @@ namespace test{ random_or_get (string arg) { if (isnil(arg)) - return 1 + (rand() % 10000); + return gavl_time_t (1 + (rand() % 100000)) * GAVL_TIME_SCALE; else return lexical_cast (arg); } @@ -106,12 +104,12 @@ namespace test{ // using a 25fps-grid, but with an time origin offset by 1/50sec TimeGrid::build("test_grid", FrameRate::PAL, Time(FSecs(1,50))); - QuTime qVal (o, "test_grid"); - FrameNr count(qVal); + QuTime qChange (c, "test_grid"); + FrameNr count(qChange); mutate_by_Value (o, Time(c)); mutate_by_Offset (o, Offset(c)); - mutate_quantised (o, qVal); + mutate_quantised (o, qChange); mutate_by_Increment(o, count); } @@ -126,7 +124,7 @@ namespace test{ CHECK (t.span.start() != original); CHECK (t.span.start() == newStart); - // instead of invoking directly, we can store and copy mutation messages + // instead of invoking directly, we can store and copy mutation messages EncapsulatedMutation change_back(Mutation::changeTime (Time(original))); t.span.accept (change_back); CHECK (t.span.start() == original); @@ -199,7 +197,8 @@ namespace test{ // simulate what happened by explicit operations... Secs seconds = change.formatAs(); - Time materialised = seconds.getTime(); + PQuant quantiser(change); + Time materialised (quantiser->materialise(change)); CHECK (t.span == materialised); CHECK (t.span.duration() == original); // not affected by mutation as usual @@ -221,7 +220,7 @@ namespace test{ funny.accept (Mutation::materialise (change)); CHECK (funny == t.quant); // leading to the same raw value this far - Time doublyQuantised = Secs(funny).getTime(); + Time doublyQuantised (PQuant(funny)->materialise(funny)); CHECK (doublyQuantised != materialised); } @@ -259,8 +258,8 @@ namespace test{ t.quant.accept (Mutation::nudge (change)); CHECK (t.quant != original); long frameNr_after = t.quant.formatAs(); - CHECK (frameNr_after != frameNr + change); - // i.e., use the quantised time's own grid + CHECK (frameNr_after == frameNr + change); + //i.e. the quantised time's own grid is used } };