2008-11-05 05:03:03 +01:00
|
|
|
/*
|
2023-04-25 18:27:16 +02:00
|
|
|
Segmentation - Partitioning of a timeline for organising the render graph.
|
2010-12-17 23:28:49 +01:00
|
|
|
|
2008-11-05 05:03:03 +01:00
|
|
|
Copyright (C) Lumiera.org
|
2023-05-03 04:42:17 +02:00
|
|
|
2023, Hermann Vosseler <Ichthyostega@web.de>
|
2010-12-17 23:28:49 +01:00
|
|
|
|
2008-11-05 05:03:03 +01:00
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
|
modify it under the terms of the GNU General Public License as
|
2010-12-17 23:28:49 +01:00
|
|
|
published by the Free Software Foundation; either version 2 of
|
|
|
|
|
the License, or (at your option) any later version.
|
|
|
|
|
|
2008-11-05 05:03:03 +01:00
|
|
|
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.
|
2010-12-17 23:28:49 +01:00
|
|
|
|
2008-11-05 05:03:03 +01:00
|
|
|
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.
|
2010-12-17 23:28:49 +01:00
|
|
|
|
2008-11-05 05:03:03 +01:00
|
|
|
* *****************************************************/
|
|
|
|
|
|
2016-11-03 18:20:10 +01:00
|
|
|
|
2016-11-03 18:22:31 +01:00
|
|
|
/** @file segmentation.cpp
|
2016-11-09 22:22:55 +01:00
|
|
|
** @todo stalled effort towards a session implementation from 2008
|
|
|
|
|
** @todo 2016 likely to stay, but expect some extensive rework
|
2016-11-03 18:20:10 +01:00
|
|
|
*/
|
|
|
|
|
|
2017-04-02 04:22:51 +02:00
|
|
|
#include "lib/error.hpp"
|
2023-04-25 18:27:16 +02:00
|
|
|
#include "steam/fixture/segmentation.hpp"
|
|
|
|
|
//#include "steam/mobject/builder/fixture-change-detector.hpp" ///////////TODO
|
2023-05-02 21:24:26 +02:00
|
|
|
#include "lib/time/timevalue.hpp"
|
2023-05-03 04:42:17 +02:00
|
|
|
#include "lib/meta/function.hpp"
|
2008-11-05 05:03:03 +01:00
|
|
|
|
2023-05-03 03:32:49 +02:00
|
|
|
#include <array>
|
2008-11-05 05:03:03 +01:00
|
|
|
|
|
|
|
|
|
2018-11-15 23:55:13 +01:00
|
|
|
namespace steam {
|
2023-04-25 18:27:16 +02:00
|
|
|
namespace fixture {
|
2011-12-02 16:10:03 +01:00
|
|
|
|
2017-04-02 04:22:51 +02:00
|
|
|
namespace error = lumiera::error;
|
|
|
|
|
|
|
|
|
|
|
2023-04-25 13:40:20 +02:00
|
|
|
Segmentation::~Segmentation() { } // emit VTable here...
|
|
|
|
|
|
2023-05-03 04:42:17 +02:00
|
|
|
|
2023-05-02 21:24:26 +02:00
|
|
|
namespace {// Implementation of Split-Splice algorithm
|
|
|
|
|
|
2023-05-03 04:42:17 +02:00
|
|
|
using lib::meta::_Fun;
|
|
|
|
|
|
|
|
|
|
template<typename FUN, typename SIG>
|
|
|
|
|
struct has_Sig
|
|
|
|
|
: std::is_same<SIG, typename _Fun<FUN>::Sig>
|
|
|
|
|
{ };
|
|
|
|
|
|
|
|
|
|
/** verify the installed functors or lambdas expose the expected signature */
|
|
|
|
|
#define ASSERT_VALID_SIGNATURE(_FUN_, _SIG_) \
|
|
|
|
|
static_assert (has_Sig<_FUN_, _SIG_>::value, "Function " STRINGIFY(_FUN_) " unsuitable, expected signature: " STRINGIFY(_SIG_));
|
|
|
|
|
|
2023-05-02 21:24:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Descriptor and working context to split/splice in a new Interval.
|
|
|
|
|
* The »Split-Splice« algorithm works on a seamless segmentation of
|
|
|
|
|
* an ordered working axis, represented as sequence of intervals.
|
|
|
|
|
* The purpose is to integrate a new Segment / interval, thereby
|
|
|
|
|
* truncating / splitting / filling adjacent intervals to fit
|
|
|
|
|
*/
|
2023-05-03 04:42:17 +02:00
|
|
|
template<class ORD ///< order value for the segmentation
|
|
|
|
|
,class POS ///< iterator to work with elements of the segmentation
|
|
|
|
|
,class START ///< function to access the start value for a segment
|
|
|
|
|
,class AFTER ///< function to access the after-end value for a segment
|
|
|
|
|
,class CREATE ///< function to create a new segment: `createSeg(pos, start,after)`
|
|
|
|
|
,class EMPTY ///< function to create empty segment: `emptySeg (pos, start,after)`
|
|
|
|
|
,class CLONE ///< function to clone/modify segment: `cloneSeg (pos, start,after, src)`
|
|
|
|
|
,class DELETE ///< function to discard a segment: `discard (start,after)`
|
|
|
|
|
>
|
2023-05-02 21:24:26 +02:00
|
|
|
class SplitSpliceAlgo
|
|
|
|
|
: util::NonCopyable
|
|
|
|
|
{
|
|
|
|
|
enum Verb { NIL
|
|
|
|
|
, DROP
|
|
|
|
|
, TRUNC
|
|
|
|
|
, INS_NOP
|
|
|
|
|
, SEAMLESS
|
|
|
|
|
};
|
|
|
|
|
|
2023-05-03 03:32:49 +02:00
|
|
|
Verb opPred_ = NIL,
|
|
|
|
|
opSucc_ = NIL;
|
2023-05-02 21:24:26 +02:00
|
|
|
|
2023-05-03 04:42:17 +02:00
|
|
|
POS pred_, succ_;
|
|
|
|
|
ORD start_, after_;
|
|
|
|
|
|
|
|
|
|
using OptORD = std::optional<ORD>;
|
2023-05-02 21:24:26 +02:00
|
|
|
|
2023-05-03 03:32:49 +02:00
|
|
|
|
2023-05-03 04:42:17 +02:00
|
|
|
/* ======= elementary operations ======= */
|
2023-05-02 21:24:26 +02:00
|
|
|
|
2023-05-03 04:42:17 +02:00
|
|
|
ASSERT_VALID_SIGNATURE (START, ORD(POS))
|
|
|
|
|
ASSERT_VALID_SIGNATURE (AFTER, ORD(POS))
|
|
|
|
|
ASSERT_VALID_SIGNATURE (CREATE, POS(POS,ORD,ORD))
|
|
|
|
|
ASSERT_VALID_SIGNATURE (EMPTY, POS(POS,ORD,ORD))
|
|
|
|
|
ASSERT_VALID_SIGNATURE (CLONE, POS(POS,ORD,ORD,POS))
|
|
|
|
|
ASSERT_VALID_SIGNATURE (DELETE, POS(POS,POS))
|
2023-05-02 21:24:26 +02:00
|
|
|
|
2023-05-03 04:42:17 +02:00
|
|
|
START getStart;
|
|
|
|
|
AFTER getAfter;
|
|
|
|
|
CREATE createSeg;
|
|
|
|
|
EMPTY emptySeg;
|
|
|
|
|
CLONE cloneSeg;
|
|
|
|
|
DELETE discard;
|
2023-05-03 03:32:49 +02:00
|
|
|
|
2023-05-03 04:42:17 +02:00
|
|
|
const ORD AXIS_END;
|
2023-05-03 03:32:49 +02:00
|
|
|
|
2023-05-02 21:24:26 +02:00
|
|
|
public:
|
2023-05-03 03:32:49 +02:00
|
|
|
/**
|
|
|
|
|
* @param startAll (forward) iterator pointing at the overall Segmentation begin
|
|
|
|
|
* @param afterAll (forward) iterator indicating point-after-end of Segmentation
|
|
|
|
|
* @param start (optional) specification of new segment's start point
|
|
|
|
|
* @param after (optional) specification of new segment's end point
|
|
|
|
|
*/
|
2023-05-03 04:42:17 +02:00
|
|
|
SplitSpliceAlgo (START fun_getStart
|
|
|
|
|
,AFTER fun_getAfter
|
|
|
|
|
,CREATE fun_createSeg
|
|
|
|
|
,EMPTY fun_emptySeg
|
|
|
|
|
,CLONE fun_cloneSeg
|
|
|
|
|
,DELETE fun_discard
|
|
|
|
|
,const ORD axisEnd
|
|
|
|
|
,POS startAll, POS afterAll
|
|
|
|
|
,OptORD start, OptORD after)
|
|
|
|
|
: getStart{fun_getStart}
|
|
|
|
|
, getAfter{fun_getAfter}
|
|
|
|
|
, createSeg{fun_createSeg}
|
|
|
|
|
, emptySeg{fun_emptySeg}
|
|
|
|
|
, cloneSeg{fun_cloneSeg}
|
|
|
|
|
, discard {fun_discard}
|
|
|
|
|
, AXIS_END{axisEnd}
|
2023-05-03 03:32:49 +02:00
|
|
|
{
|
|
|
|
|
auto [start_,after_] = establishSplitPoint (startAll,afterAll, start,after);
|
|
|
|
|
|
|
|
|
|
// Postcondition: ordered start and end times
|
|
|
|
|
ENSURE (pred_ != afterAll);
|
|
|
|
|
ENSURE (succ_ != afterAll);
|
|
|
|
|
ENSURE (start_ < after_);
|
|
|
|
|
ENSURE (getStart(pred_) <= start_);
|
|
|
|
|
ENSURE (start_ <= getStart(succ_) or pred_ == succ_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stage-1 and Stage-2 of the algorithm determine the insert point
|
|
|
|
|
* and establish the actual start and end point of the new segment
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
2023-05-03 04:42:17 +02:00
|
|
|
std::pair<ORD,ORD>
|
|
|
|
|
establishSplitPoint (POS startAll, POS afterAll
|
|
|
|
|
,OptORD start, OptORD after)
|
|
|
|
|
{ // nominal break point
|
|
|
|
|
ORD sep = start? *start
|
|
|
|
|
: after? *after
|
|
|
|
|
: AXIS_END;
|
2023-05-03 03:32:49 +02:00
|
|
|
|
|
|
|
|
// find largest Predecessor with start before separator
|
|
|
|
|
for (succ_ = startAll, pred_ = afterAll
|
|
|
|
|
;succ_ != afterAll and getStart(succ_) < sep
|
|
|
|
|
;++succ_)
|
|
|
|
|
{
|
|
|
|
|
pred_ = succ_;
|
|
|
|
|
}
|
|
|
|
|
REQUIRE (pred_ != succ_, "non-empty segmentation required");
|
|
|
|
|
if (succ_ == afterAll) succ_=pred_;
|
|
|
|
|
if (pred_ == afterAll) pred_=succ_; // separator touches bounds
|
|
|
|
|
|
|
|
|
|
// Stage-2 : establish start and end point of new segment
|
|
|
|
|
|
2023-05-03 04:42:17 +02:00
|
|
|
ORD startSeg = start? *start
|
|
|
|
|
: getAfter(pred_) < sep? getAfter(pred_)
|
|
|
|
|
: getStart(pred_);
|
|
|
|
|
ORD afterSeg = after? *after
|
|
|
|
|
: getStart(succ_) > sep? getStart(succ_)
|
|
|
|
|
: getAfter(succ_);
|
2023-05-03 03:32:49 +02:00
|
|
|
ENSURE (startSeg != afterSeg);
|
|
|
|
|
if (startSeg < afterSeg)
|
|
|
|
|
return {startSeg,afterSeg};
|
2023-05-03 04:42:17 +02:00
|
|
|
else
|
2023-05-03 03:32:49 +02:00
|
|
|
return {afterSeg,startSeg};
|
2023-05-02 21:24:26 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-03 03:32:49 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stage-3 of the algorithm works out the precise relation of the
|
|
|
|
|
* predecessor and successor segments to determine necessary adjustments
|
|
|
|
|
*/
|
2023-05-02 21:24:26 +02:00
|
|
|
void
|
|
|
|
|
determineRelations()
|
|
|
|
|
{
|
2023-05-03 04:42:17 +02:00
|
|
|
ORD startPred = getStart (pred_),
|
|
|
|
|
afterPred = getAfter (pred_);
|
2023-05-03 03:32:49 +02:00
|
|
|
|
|
|
|
|
if (startPred < start_)
|
|
|
|
|
{
|
|
|
|
|
if (afterPred < start_) opPred_ = INS_NOP;
|
|
|
|
|
else
|
|
|
|
|
if (afterPred == start_) opPred_ = SEAMLESS;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
opPred_ = TRUNC;
|
|
|
|
|
if (afterPred > after_)
|
|
|
|
|
{ // predecessor actually spans the new segment
|
|
|
|
|
// thus use it also as successor and truncate both (=SPLIT)
|
|
|
|
|
succ_ = pred_;
|
|
|
|
|
opSucc_ = TRUNC;
|
|
|
|
|
return;
|
|
|
|
|
} } }
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
REQUIRE (startPred == start_, "predecessor does not precede start point");
|
|
|
|
|
opPred_ = DROP;
|
|
|
|
|
if (after_ < afterPred )
|
|
|
|
|
{ // predecessor coincides with start of new segment
|
|
|
|
|
// thus use it rather as successor and truncate at start
|
|
|
|
|
succ_ = pred_;
|
|
|
|
|
opSucc_ = TRUNC;
|
|
|
|
|
return;
|
|
|
|
|
} }
|
|
|
|
|
|
2023-05-03 04:42:17 +02:00
|
|
|
ORD startSucc = getStart (succ_);
|
2023-05-03 03:32:49 +02:00
|
|
|
if (startSucc < after_)
|
|
|
|
|
{
|
2023-05-03 04:42:17 +02:00
|
|
|
while (getAfter(succ_) < after_)
|
|
|
|
|
++succ_;
|
|
|
|
|
ASSERT (getStart(succ_) < after_ // in case we dropped a successor completely spanned,
|
2023-05-03 03:32:49 +02:00
|
|
|
,"seamless segmentation"); // even the next one must start within the new segment
|
|
|
|
|
|
2023-05-03 04:42:17 +02:00
|
|
|
if (after_ == getAfter(succ_)) opSucc_ = DROP;
|
2023-05-03 03:32:49 +02:00
|
|
|
else
|
2023-05-03 04:42:17 +02:00
|
|
|
if (after_ < getAfter(succ_)) opSucc_ = TRUNC;
|
2023-05-03 03:32:49 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (after_ == startSucc) opSucc_ = SEAMLESS;
|
|
|
|
|
else opSucc_ = INS_NOP;
|
|
|
|
|
}
|
2023-05-02 21:24:26 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-03 03:32:49 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stage-4 of the algorithm performs the actual insert and deleting of segments
|
|
|
|
|
* @return `(s,n,e)` to indicate where changes happened
|
|
|
|
|
* - s the first changed element
|
|
|
|
|
* - n the new main segment (may be identical to s)
|
|
|
|
|
* - e the first unaltered element after the changed range (may be `end()`)
|
|
|
|
|
*/
|
2023-05-03 04:42:17 +02:00
|
|
|
std::array<POS, 3>
|
2023-05-02 21:24:26 +02:00
|
|
|
performSplitSplice()
|
|
|
|
|
{
|
2023-05-03 04:42:17 +02:00
|
|
|
POS refPred = pred_, refSucc = succ_;
|
2023-05-03 03:32:49 +02:00
|
|
|
REQUIRE (opPred_ != NIL and opSucc_ != NIL);
|
|
|
|
|
|
|
|
|
|
// deletions are done by skipping the complete range around the insertion point;
|
|
|
|
|
// thus to retain a predecessor or successor, this range has to be reduced
|
|
|
|
|
if (opPred_ == INS_NOP or opPred_ == SEAMLESS)
|
|
|
|
|
++pred_;
|
|
|
|
|
if (opSucc_ == DROP or opSucc_ == TRUNC)
|
|
|
|
|
++succ_;
|
|
|
|
|
|
|
|
|
|
// insert the new elements /before/ the range to be dropped, i.e. at pred_
|
2023-05-03 04:42:17 +02:00
|
|
|
POS n = createSeg (pred_, start_, after_);
|
|
|
|
|
POS s = n;
|
2023-05-03 03:32:49 +02:00
|
|
|
//
|
|
|
|
|
// possibly adapt the predecessor
|
|
|
|
|
if (opPred_ == INS_NOP)
|
|
|
|
|
s = emptySeg (n, getAfter(refPred), start_);
|
|
|
|
|
else
|
|
|
|
|
if (opPred_ == TRUNC)
|
|
|
|
|
s = cloneSeg (n, getStart(refPred), start_, refPred);
|
|
|
|
|
//
|
|
|
|
|
// possibly adapt the successor
|
|
|
|
|
if (opSucc_ == INS_NOP)
|
|
|
|
|
emptySeg (pred_, after_, getStart(refSucc));
|
|
|
|
|
else
|
|
|
|
|
if (opPred_ == TRUNC)
|
|
|
|
|
cloneSeg (pred_, after_, getAfter(refSucc), refSucc);
|
|
|
|
|
|
|
|
|
|
// finally discard superseded segments
|
2023-05-03 04:42:17 +02:00
|
|
|
POS e = discard (pred_, succ_);
|
2023-05-03 03:32:49 +02:00
|
|
|
|
|
|
|
|
// indicate the range where changes happened
|
|
|
|
|
return {s,n,e};
|
2023-05-02 21:24:26 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}//(End)SlitSplice impl
|
2017-04-02 04:22:51 +02:00
|
|
|
|
|
|
|
|
|
2023-05-02 04:16:39 +02:00
|
|
|
/**
|
|
|
|
|
* @param start (optional) definition of the new Segment's start point (inclusive)
|
|
|
|
|
* @param after (optional) definition of the end point (exclusive)
|
|
|
|
|
* @param jobTicket specification of provided render functionality for the new Segment
|
|
|
|
|
* @remarks missing definitions will be derived or interpolated according to context
|
|
|
|
|
* - if start point is omitted, the new Segment will start seamlessly after
|
|
|
|
|
* any preceding Segment's end, in case this preceding Segment ends earlier
|
|
|
|
|
* - otherwise the preceding Segment's start point will be used, thereby effectively
|
|
|
|
|
* replacing and expanding or trimming or inserting into the preceding Segment
|
|
|
|
|
* - similar for the end point: if the definition is omitted, the new Segment
|
|
|
|
|
* will cover the time range until the next Segmen's start
|
|
|
|
|
* - if upper/lower boundaries can not be established, the covered range will be
|
|
|
|
|
* expanded from Time::ANYTIME up to Time::ANYTIME in as fitting current context
|
|
|
|
|
* - after start and end point have been established by the above rules, the actual
|
|
|
|
|
* splicing operation will be determined; either an existing Segment is replaced
|
|
|
|
|
* altogether, or it is trimmed to fit, or the new Segment is inserted, thereby
|
|
|
|
|
* creating a second (copied) part of the encompassing old Segment.
|
|
|
|
|
* - in case the JobTicket is omitted, the new Segment will be marked as _passive_
|
|
|
|
|
* and any job created from such a Segment will then be a »NOP-job«
|
|
|
|
|
*/
|
|
|
|
|
Segment const&
|
|
|
|
|
Segmentation::splitSplice (OptTime start, OptTime after, const engine::JobTicket* jobTicket)
|
|
|
|
|
{
|
2023-05-03 04:42:17 +02:00
|
|
|
using Iter = typename list<Segment>::iterator;
|
|
|
|
|
|
|
|
|
|
auto getStart = [](Iter elm) -> Time { return elm->start(); };
|
|
|
|
|
auto getAfter = [](Iter elm) -> Time { return elm->after(); };
|
|
|
|
|
auto createSeg= [](Iter pos, Time start, Time after) -> Iter { UNIMPLEMENTED ("create new Segment"); };
|
|
|
|
|
auto emptySeg = [](Iter pos, Time start, Time after) -> Iter { UNIMPLEMENTED ("create empty Segment");};
|
|
|
|
|
auto cloneSeg = [](Iter pos, Time start, Time after, Iter src) -> Iter { UNIMPLEMENTED ("clone Segment and modify time"); };
|
|
|
|
|
auto discard = [](Iter pos, Iter after) -> Iter { UNIMPLEMENTED ("discard Segments"); };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SplitSpliceAlgo splicr{ getStart
|
|
|
|
|
, getAfter
|
|
|
|
|
, createSeg
|
|
|
|
|
, emptySeg
|
|
|
|
|
, cloneSeg
|
|
|
|
|
, discard
|
|
|
|
|
, Time::NEVER
|
|
|
|
|
, segments_.begin(),segments_.end()
|
|
|
|
|
, start,after
|
|
|
|
|
};
|
2023-05-02 21:24:26 +02:00
|
|
|
splicr.determineRelations();
|
2023-05-03 03:32:49 +02:00
|
|
|
auto [s,n,e] = splicr.performSplitSplice();
|
|
|
|
|
return *n;
|
2023-05-02 04:16:39 +02:00
|
|
|
}
|
2017-04-02 04:22:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-04-25 18:27:16 +02:00
|
|
|
}} // namespace steam::fixture
|