/* Segmentation - Partitioning of a timeline for organising the render graph. Copyright (C) Lumiera.org 2023, 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 segmentation.cpp ** @todo stalled effort towards a session implementation from 2008 ** @todo 2016 likely to stay, but expect some extensive rework */ #include "lib/error.hpp" #include "steam/fixture/segmentation.hpp" //#include "steam/mobject/builder/fixture-change-detector.hpp" ///////////TODO #include "lib/time/timevalue.hpp" #include "lib/meta/function.hpp" #include namespace steam { namespace fixture { namespace error = lumiera::error; Segmentation::~Segmentation() { } // emit VTable here... namespace {// Implementation of Split-Splice algorithm using lib::meta::_Fun; template struct has_Sig : std::is_same::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_)); /** * 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 */ template class SplitSpliceAlgo : util::NonCopyable { enum Verb { NIL , DROP , TRUNC , INS_NOP , SEAMLESS }; Verb opPred_ = NIL, opSucc_ = NIL; POS pred_, succ_; ORD start_, after_; using OptORD = std::optional; /* ======= elementary operations ======= */ 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)) START getStart; AFTER getAfter; CREATE createSeg; EMPTY emptySeg; CLONE cloneSeg; DELETE discard; const ORD AXIS_END; public: /** * @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 */ 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} { 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 */ std::pair establishSplitPoint (POS startAll, POS afterAll ,OptORD start, OptORD after) { // nominal break point ORD sep = start? *start : after? *after : AXIS_END; // 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 ORD startSeg = start? *start : getAfter(pred_) < sep? getAfter(pred_) : getStart(pred_); ORD afterSeg = after? *after : getStart(succ_) > sep? getStart(succ_) : getAfter(succ_); ENSURE (startSeg != afterSeg); if (startSeg < afterSeg) return {startSeg,afterSeg}; else return {afterSeg,startSeg}; } /** * Stage-3 of the algorithm works out the precise relation of the * predecessor and successor segments to determine necessary adjustments */ void determineRelations() { ORD startPred = getStart (pred_), afterPred = getAfter (pred_); 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; } } ORD startSucc = getStart (succ_); if (startSucc < after_) { while (getAfter(succ_) < after_) ++succ_; ASSERT (getStart(succ_) < after_ // in case we dropped a successor completely spanned, ,"seamless segmentation"); // even the next one must start within the new segment if (after_ == getAfter(succ_)) opSucc_ = DROP; else if (after_ < getAfter(succ_)) opSucc_ = TRUNC; } else { if (after_ == startSucc) opSucc_ = SEAMLESS; else opSucc_ = INS_NOP; } } /** * 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()`) */ std::array performSplitSplice() { POS refPred = pred_, refSucc = succ_; 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_ POS n = createSeg (pred_, start_, after_); POS s = n; // // 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 POS e = discard (pred_, succ_); // indicate the range where changes happened return {s,n,e}; } }; }//(End)SlitSplice impl /** * @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) { using Iter = typename list::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 }; splicr.determineRelations(); auto [s,n,e] = splicr.performSplitSplice(); return *n; } }} // namespace steam::fixture