diff --git a/source/simple/motion/algorithm.hpp b/source/simple/motion/algorithm.hpp index 8e15229e1294d638e091e24a8f6960ab31557a2d..e7f3f2863c6f921a74bdcca8643501b49eb53a20 100644 --- a/source/simple/motion/algorithm.hpp +++ b/source/simple/motion/algorithm.hpp @@ -7,31 +7,32 @@ namespace simple::motion { // welcome to proper looping -// TODO: for known total duration mod(remaining, total) optimization template <typename Motion, typename Target> int loop(Target&& target, Motion& motion, typename Motion::duration delta) { int count = 0; - // better way to write this?? - while(true) if(auto [success, remaining] = - motion.move(std::forward<Target>(target), delta); - !success - ) + while(auto result = + motion.move(std::forward<Target>(target), delta)) { motion.reset(); - delta = remaining; + delta = result.remaining; ++count; } - else break; return count; } -template <typename It, typename Duration, typename Function> +// and proper sequencing +// NOTE: The done flag indicates that the last updated motion is done, not necessarily the whole input sequence. +template <typename Stepper, typename Duration, typename Function> constexpr -multi_advance_result<Duration, It> sequence(It begin, It end, Duration delta, Function&& advance) +multi_advance_result<Duration, Stepper> +sequence(Stepper begin, Stepper end, Duration delta, Function&& advance) { - support::range<It> updated{begin, begin}; - for(; begin != end; ++begin) + assert(begin != end); + + support::range<Stepper> updated{begin, begin}; + + do { auto result = std::apply( std::forward<Function>(advance), @@ -39,13 +40,15 @@ multi_advance_result<Duration, It> sequence(It begin, It end, Duration delta, Fu ); ++updated.upper(); - delta = result.remaining; + if(result.remaining <= Duration{}) + return {result, updated}; - if(delta <= Duration{}) - return {true, delta, updated}; + delta = result.remaining; + ++begin; } + while(begin != end); - return {false, delta, updated}; + return {true, delta, updated}; } } // namespace simple::motion diff --git a/source/simple/motion/common.hpp b/source/simple/motion/common.hpp index add029bdb1eb76dd2eff1ba4e392bf7cea76ea5d..15abc907c7c40d193562dcae0a677deb68f31fb6 100644 --- a/source/simple/motion/common.hpp +++ b/source/simple/motion/common.hpp @@ -8,9 +8,9 @@ namespace simple::motion template <typename Duration> struct advance_result { - bool success = false; + bool done; Duration remaining{}; - explicit operator bool() { return success; } + explicit operator bool() { return done; } // negate? }; template <typename Duration, typename It = size_t> diff --git a/source/simple/motion/melody.hpp b/source/simple/motion/melody.hpp index 29e2f5933b1e5c752e79ce125633aa69ff1f7ae3..adeda2a643711bc55f45ef46cf87eb56339cdbba 100644 --- a/source/simple/motion/melody.hpp +++ b/source/simple/motion/melody.hpp @@ -15,24 +15,21 @@ class melody public: using duration = std::common_type_t<typename Motions::duration...>; + using result_t = multi_advance_result<duration>; melody() = default; melody(Motions... motions) : movements{motions...} {} bool done() { - return std::get<sizeof...(Motions) - 1>(movements).done(); + return current_index == sizeof...(Motions); } - // auto advance(duration delta) - // { - // return advance(delta, {current_index, current_index + 1}); - // } - // WIP: TODO: need complete restructure can't nest melodies - multi_advance_result<duration> advance(duration delta) + result_t advance(duration delta) { - return sequence + assert(!done()); + auto r = sequence ( current_index, sizeof...(Motions), delta, @@ -47,6 +44,14 @@ class melody ); } ); + + bool last_done = r.done; + current_index = r.updated.upper() - !last_done; + + if(last_done && current_index == sizeof...(Motions)) + return {true, r.remaining, r.updated}; + + return {false, r.remaining, r.updated}; } template <size_t... I> @@ -67,8 +72,11 @@ class melody } template <typename T> - advance_result<duration> move(T& target, duration delta) + result_t move(T& target, duration delta) { + if(done()) + return {true, delta}; + auto result = advance(delta); support::apply_for(result.updated.upper()-1, [&target](auto&& movement) { @@ -79,8 +87,11 @@ class melody template <typename... T, std::enable_if_t<sizeof...(T) == sizeof...(Motions)>* = nullptr> - advance_result<duration> move(std::tuple<T...>&& targets, duration delta) + result_t move(std::tuple<T...>&& targets, duration delta) { + if(done()) + return {true, delta}; + auto r = advance(delta); support::apply_for(r.updated, [](auto&& movement, auto&& target) { @@ -91,9 +102,12 @@ class melody template <typename T, size_t Size, std::enable_if_t<Size == sizeof...(Motions)>* = nullptr> - advance_result<duration> move(std::array<T,Size>& targets, duration delta) + result_t move(std::array<T,Size>& targets, duration delta) { //TODO: mostly same as tuple version above + if(done()) + return {true, delta}; + auto r = advance(delta); support::apply_for(r.updated, [](auto&& movement, auto&& target) { diff --git a/source/simple/motion/movement.hpp b/source/simple/motion/movement.hpp index 87bcf13b194ef8a89a010c1cee8d13e146831969..1f55999760f8153381c6848a14b7d60a69713a9c 100644 --- a/source/simple/motion/movement.hpp +++ b/source/simple/motion/movement.hpp @@ -27,7 +27,8 @@ struct movement decltype(auto) value() { - auto ratio = total == Duration{} ? Ratio{} : + assert(total != Duration{}); // nice + auto ratio = (Ratio{} + elapsed.count()) / (Ratio{} + total.count()); using support::way; @@ -41,14 +42,15 @@ struct movement advance_result<Duration> advance(Duration delta) { + assert(!done()); elapsed += delta; if(done()) { auto remaining = elapsed - total; elapsed = total; - return {false, remaining}; + return {true, remaining}; } - return {true}; + return {false}; } void reset() @@ -58,6 +60,9 @@ struct movement advance_result<Duration> move(Type& target, Duration delta) { + if(done()) + return {true, delta}; + auto result = advance(delta); target = value(); return result; diff --git a/source/simple/motion/symphony.hpp b/source/simple/motion/symphony.hpp index f1f22624b24aabe9e8398e8008acd56661c5f1f2..bc2dcb2cb06f7973378ab4611a3fe8f938059d93 100644 --- a/source/simple/motion/symphony.hpp +++ b/source/simple/motion/symphony.hpp @@ -34,38 +34,63 @@ struct variant : public std::variant<Motions...> }; -template <typename Motion, - template<typename T> typename Range = - std::vector> +template <typename Range> class symphony { public: - using duration = typename Motion::duration; - using iterator = typename Range<Motion>::iterator; + using iterator = decltype(std::begin(std::declval<Range&>())); + using motion = std::remove_reference_t<decltype(*std::declval<iterator>())>; + using duration = typename motion::duration; + using result_t = multi_advance_result<duration,iterator>; - explicit symphony(Range<Motion> motions) : + explicit symphony(Range motions) : motions(std::move(motions)), current(std::begin(this->motions)) { } - multi_advance_result<duration, iterator> - advance(duration delta) + bool done() { - return sequence + return current == std::end(motions); + } + + void reset() + { + for(auto&& motion : motions) + motion.reset(); + current = std::begin(motions); + } + + result_t advance(duration delta) + { + assert(!done()); + auto r = sequence ( - current, motions.end(), + current, std::end(motions), delta, - [this](auto current, auto delta) + [](auto current, auto delta) { - return (*current).advance(delta); + return current->advance(delta); } ); + + // TODO follows a duplicate code from melody melody + // maybe make another function sequence_update + bool last_done = r.done; + current = r.updated.upper() - !last_done; + + if(last_done && current == std::end(motions)) + return {true, r.remaining, r.updated}; + + return {false, r.remaining, r.updated}; } template <typename TargetRange> - advance_result<duration> move(TargetRange&& target, duration delta) + result_t move(TargetRange&& target, duration delta) { + if(done()) + return {true, delta}; + using std::begin; using std::end; using support::map_range; @@ -82,8 +107,13 @@ class symphony return r; } + const Range& range() + { + return motions; + } + private: - Range<Motion> motions; + Range motions; iterator current; }; diff --git a/unit_tests/loop.cpp b/unit_tests/loop.cpp index 474328f437d4835098397ee05b7960493d25b3a2..f41ef0d57d81fd6b9e9ee282d8539a8d860c4607 100644 --- a/unit_tests/loop.cpp +++ b/unit_tests/loop.cpp @@ -1,12 +1,13 @@ #include <chrono> #include "simple/motion/movement.hpp" #include "simple/motion/melody.hpp" +#include "simple/motion/symphony.hpp" #include "simple/motion/algorithm.hpp" using namespace std::literals; -using duration = std::chrono::duration<float>; +// using duration = std::chrono::duration<float>; +using duration = std::chrono::steady_clock::duration; using simple::support::way; -using simple::motion::melody; // TODO: use rational, can't rely on float using movement = simple::motion::movement<duration,float,float>; @@ -25,15 +26,33 @@ void BasicLoop() void LoopDontSkip() { - auto sequence = melody( - movement{10ms, 0,1}, - movement{10ms, 0,1}, - movement{10ms, 0,1}, - movement{10ms, 0,1} - ); - std::array<float,4> values{}; - assert( 1 == loop(values, sequence, 60ms) ); // TODO: do 55ms with rational - assert(( values == std::array{1.f, 1.f, 1.f, 1.f} )); + { + auto sequence = simple::motion::melody( + movement{10ms, 0,1}, + movement{10ms, 0,1}, + movement{10ms, 0,1}, + movement{10ms, 0,1} + ); + std::array<float,4> values{}; + assert( 1 == loop(values, sequence, 60ms) ); // TODO: do 55ms with rational + assert(( values == std::array{1.f, 1.f, 1.f, 1.f} )); + assert( 2 == loop(values, sequence, 90ms) ); + assert(( values == std::array{1.f, 1.f, 1.f, 1.f} )); + } + + { + auto sequence = simple::motion::symphony(std::array{ + movement{10ms, 0,1}, + movement{10ms, 0,1}, + movement{10ms, 0,1}, + movement{10ms, 0,1} + }); + std::array<float,4> values{}; + assert( 1 == loop(values, sequence, 60ms) ); // TODO: do 55ms with rational + assert(( values == std::array{1.f, 1.f, 1.f, 1.f} )); + assert( 2 == loop(values, sequence, 90ms) ); + assert(( values == std::array{1.f, 1.f, 1.f, 1.f} )); + } } int main() diff --git a/unit_tests/melody.cpp b/unit_tests/melody.cpp index 509f9703c627a52b3f26a1dea259e76f81ac9716..1a9eadeff0ff2e4d7fa52a603845f37f0d43dcba 100644 --- a/unit_tests/melody.cpp +++ b/unit_tests/melody.cpp @@ -9,11 +9,12 @@ using duration = std::chrono::duration<float>; using simple::support::way; using simple::motion::melody; -// TODO: use rational, can't rely on float -using movement = simple::motion::movement<duration,float,float>; void SharpTurn() { + // TODO: use rational, can't rely on float + using movement = simple::motion::movement<duration,float,float>; + struct float2 { float x, y; }; float2 block{0,0}; @@ -23,22 +24,89 @@ void SharpTurn() ); - sharp_turn.move(std::forward_as_tuple(block.x, block.y), 500ms); + auto range = sharp_turn.move(std::forward_as_tuple(block.x, block.y), 500ms).updated; assert( block.x == way(0.f,1.f, 0.5f / 1.25f) ); assert( block.y == 0.f ); + assert( range.upper() - range.lower() == 1 ); - sharp_turn.move(std::forward_as_tuple(block.x, block.y), 500ms); + range = sharp_turn.move(std::forward_as_tuple(block.x, block.y), 500ms).updated; assert( block.x == way(0.f,1.f, 1.f / 1.25f) ); assert( block.y == 0.f ); + assert( range.upper() - range.lower() == 1 ); - sharp_turn.move(std::forward_as_tuple(block.x, block.y), 500ms); + range = sharp_turn.move(std::forward_as_tuple(block.x, block.y), 500ms).updated; assert( block.x == way(0.f,1.f, 1.f) ); assert( block.y == way(0.f,1.f, 0.25f / 1.25f) ); + assert( range.upper() - range.lower() == 2 ); + + range = sharp_turn.move(std::forward_as_tuple(block.x, block.y), 500ms).updated; + assert( block.x == way(0.f,1.f, 1.f) ); + assert( block.y == way(0.f,1.f, 0.75f / 1.25f) ); + assert( range.upper() - range.lower() == 1 ); + +} + +void Advance() +{ + // TODO: use rational, can't rely on float + using movement = simple::motion::movement<std::chrono::milliseconds,float,float>; + + auto sequence = melody( + movement{10ms, 0,1}, + movement{10ms, 0,1}, + movement{10ms, 0,1}, + movement{10ms, 0,1} + ); + + auto result = sequence.advance(10ms); + assert( not result.done ); + assert( result.updated.lower() == 0 ); + assert( result.updated.upper() == 1 ); + assert( result.remaining == 0ms ); + + result = sequence.advance(10ms); + assert( not result.done ); + assert( result.updated.lower() == 1 ); + assert( result.updated.upper() == 2 ); + assert( result.remaining == 0ms ); + + result = sequence.advance(10ms); + assert( not result.done ); + assert( result.updated.lower() == 2 ); + assert( result.updated.upper() == 3 ); + assert( result.remaining == 0ms ); + + result = sequence.advance(10ms); + assert( result.done ); + assert( result.updated.lower() == 3 ); + assert( result.updated.upper() == 4 ); + assert( result.remaining == 0ms ); + + sequence.reset(); + + result = sequence.advance(15ms); + assert( not result.done ); + assert( result.updated.lower() == 0 ); + assert( result.updated.upper() == 2 ); + assert( result.remaining == 0ms ); + + result = sequence.advance(15ms); + assert( not result.done ); + assert( result.updated.lower() == 1 ); + assert( result.updated.upper() == 3 ); + assert( result.remaining == 0ms ); + + result = sequence.advance(15ms); + assert( result.done ); + assert( result.updated.lower() == 3 ); + assert( result.updated.upper() == 4 ); + assert( result.remaining == 5ms ); } int main() { SharpTurn(); + Advance(); return 0; } diff --git a/unit_tests/symphony.cpp b/unit_tests/symphony.cpp index 29992e888ab76eef3b2c93b51ae59e8fb9083f56..b6c35a63a421f85d32dd00fc1fff1a43e9bfe746 100644 --- a/unit_tests/symphony.cpp +++ b/unit_tests/symphony.cpp @@ -8,13 +8,14 @@ using duration = std::chrono::duration<float>; using simple::support::way; using simple::motion::symphony; -// TODO: use rational, can't rely on float -using movement = simple::motion::movement<duration,float,float>; + +template <typename T> class show_type; void SharpTurn() { - using float2 = std::array<float,2>; - float2 block{0,0}; + // TODO: use rational, can't rely on float + using movement = simple::motion::movement<duration,float,float>; + std::array<float,2> block{0,0}; auto sharp_turn = symphony(std::vector<movement>{ movement{1250ms, 0,1}, @@ -22,22 +23,91 @@ void SharpTurn() }); - sharp_turn.move(block, 500ms); + auto range = sharp_turn.move(block, 500ms).updated; assert( block[0] == way(0.f,1.f, 0.5f / 1.25f) ); assert( block[1] == 0.f ); + assert( range.end() - range.begin() == 1 ); - sharp_turn.move(block, 500ms); + range = sharp_turn.move(block, 500ms).updated; assert( block[0] == way(0.f,1.f, 1.f / 1.25f) ); assert( block[1] == 0.f ); + assert( range.end() - range.begin() == 1 ); - sharp_turn.move(block, 500ms); + range = sharp_turn.move(block, 500ms).updated; assert( block[0] == way(0.f,1.f, 1.f) ); assert( block[1] == way(0.f,1.f, 0.25f / 1.25f) ); + assert( range.end() - range.begin() == 2 ); + + range = sharp_turn.move(block, 500ms).updated; + assert( block[0] == way(0.f,1.f, 1.f) ); + assert( block[1] == way(0.f,1.f, 0.75f / 1.25f) ); + assert( range.end() - range.begin() == 1 ); + +} + +void Advance() +{ + // TODO: use rational, can't rely on float + using movement = simple::motion::movement<std::chrono::milliseconds,float,float>; + + auto sequence = symphony(std::vector{ + movement{10ms, 0,1}, + movement{10ms, 0,1}, + movement{10ms, 0,1}, + movement{10ms, 0,1} + }); + + const auto begin = sequence.range().begin(); + + auto result = sequence.advance(10ms); + assert( not result.done ); + assert( result.updated.lower() == begin+0 ); + assert( result.updated.upper() == begin+1 ); + assert( result.remaining == 0ms ); + + result = sequence.advance(10ms); + assert( not result.done ); + assert( result.updated.lower() == begin+1 ); + assert( result.updated.upper() == begin+2 ); + assert( result.remaining == 0ms ); + + result = sequence.advance(10ms); + assert( not result.done ); + assert( result.updated.lower() == begin+2 ); + assert( result.updated.upper() == begin+3 ); + assert( result.remaining == 0ms ); + + result = sequence.advance(10ms); + assert( result.done ); + assert( result.updated.lower() == begin+3 ); + assert( result.updated.upper() == begin+4 ); + assert( result.remaining == 0ms ); + + sequence.reset(); + + result = sequence.advance(15ms); + assert( not result.done ); + assert( result.updated.lower() == begin+0 ); + assert( result.updated.upper() == begin+2 ); + assert( result.remaining == 0ms ); + + result = sequence.advance(15ms); + assert( not result.done ); + assert( result.updated.lower() == begin+1 ); + assert( result.updated.upper() == begin+3 ); + assert( result.remaining == 0ms ); + + result = sequence.advance(15ms); + assert( result.done ); + assert( result.updated.lower() == begin+3 ); + assert( result.updated.upper() == begin+4 ); + assert( result.remaining == 5ms ); } int main() { SharpTurn(); + Advance(); return 0; }