diff --git a/ClockReceiver/RangeDispatcher.hpp b/ClockReceiver/RangeDispatcher.hpp index a327b8e67..e3c81e067 100644 --- a/ClockReceiver/RangeDispatcher.hpp +++ b/ClockReceiver/RangeDispatcher.hpp @@ -13,52 +13,11 @@ namespace Reflection { -/// Provides glue for a run of calls like: -/// -/// SequencerT.perform<0>(...) -/// SequencerT.perform<1>(...) -/// SequencerT.perform<2>(...) -/// ...etc... -/// -/// Allowing the caller to execute any subrange of the calls. -template -struct RangeDispatcher { - - /// Perform @c target.perform() for the input range `start <= n < end`. - template - static void dispatch(SequencerT &target, int start, int end, Args&&... args) { - - // Minor optimisation: do a comparison with end once outside the loop and if it implies so - // then do no further comparisons within the loop. This is somewhat targetted at expected - // use cases. - if(end < SequencerT::max) { - dispatch(target, start, end, args...); - } else { - dispatch(target, start, end, args...); - } - } - -private: - template static void dispatch(SequencerT &target, int start, int end, Args&&... args) { - static_assert(SequencerT::max < 2048); - - // Yes, macros, yuck. But I want an actual switch statement for the dispatch to start - // and to allow a simple [[fallthrough]] through all subsequent steps up until end. - // So I don't think I have much in the way of options here. - // - // Sensible choices by the optimiser are assumed. - -#define index(n) \ - case n: \ - if constexpr (n <= SequencerT::max) { \ - if constexpr (n == SequencerT::max) return; \ - if constexpr (n < SequencerT::max) { \ - if(use_end && end == n) return; \ - } \ - target.template perform(start, end, args...); \ - } \ - [[fallthrough]]; - +// Yes, macros, yuck. But I want an actual switch statement for the dispatch to start +// and to allow a simple [[fallthrough]] through all subsequent steps up until end. +// So I don't think I have much in the way of options here. +// +// Sensible choices by the optimiser are assumed. #define index2(n) index(n); index(n+1); #define index4(n) index2(n); index2(n+2); #define index8(n) index4(n); index4(n+4); @@ -71,12 +30,106 @@ private: #define index1024(n) index512(n); index512(n+512); #define index2048(n) index1024(n); index1024(n+1024); - switch(start) { - default: assert(false); - index2048(0); +/// Provides glue for a run of calls like: +/// +/// SequencerT.perform<0>(...) +/// SequencerT.perform<1>(...) +/// SequencerT.perform<2>(...) +/// ...etc... +/// +/// Allowing the caller to execute any subrange of the calls. +template +struct RangeDispatcher { + static_assert(SequencerT::max < 2048); + + /// Perform @c target.perform() for the input range `begin <= n < end`. + template + static void dispatch(SequencerT &target, int begin, int end, Args&&... args) { + + // Minor optimisation: do a comparison with end once outside the loop and if it implies so + // then do no further comparisons within the loop. This is somewhat targetted at expected + // use cases. + if(end < SequencerT::max) { + dispatch(target, begin, end, args...); + } else { + dispatch(target, begin, end, args...); } + } + + private: + template static void dispatch(SequencerT &target, int begin, int end, Args&&... args) { +#define index(n) \ + case n: \ + if constexpr (n <= SequencerT::max) { \ + if constexpr (n == SequencerT::max) return; \ + if constexpr (n < SequencerT::max) { \ + if(use_end && end == n) return; \ + } \ + target.template perform(args...); \ + } \ + [[fallthrough]]; + + switch(begin) { + default: assert(false); + index2048(0); + } #undef index + } + +}; + +/// An optional target for a RangeDispatcher which uses a classifier to divide the input region into typed ranges, issuing calls to the target +/// only to begin and end each subrange, and for the number of cycles spent within. +template +struct SubrangeDispatcher { + static_assert(ClassifierT::max < 2048); + + template static void dispatch(TargetT &target, int begin, int end, Args&&... args) { +#define index(n) \ + case n: \ + if constexpr (n <= ClassifierT::max) { \ + constexpr auto region = ClassifierT::region(n); \ + if constexpr (n == find_begin(n)) { \ + if(n >= end) { \ + return; \ + } \ + target.template begin(n); \ + } \ + if constexpr (n == find_end(n) - 1) { \ + const auto clipped_begin = std::max(begin, find_begin(n)); \ + const auto clipped_end = std::min(end, find_end(n)); \ + target.template advance(clipped_end - clipped_begin); \ + \ + if(clipped_end == n + 1) { \ + target.template end(n + 1); \ + } \ + } \ + } \ + [[fallthrough]]; + + switch(begin) { + default: assert(false); + index2048(0); + } + +#undef index + } + + private: + static constexpr int find_begin(int n) { + const auto type = ClassifierT::region(n); + while(n && ClassifierT::region(n - 1) == type) --n; + return n; + } + + static constexpr int find_end(int n) { + const auto type = ClassifierT::region(n); + while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n; + return n; + } +}; + #undef index2 #undef index4 #undef index8 @@ -88,51 +141,6 @@ private: #undef index512 #undef index1024 #undef index2048 - } - -}; - -/// An optional target for a RangeDispatcher which uses a classifier to divide the input region into typed ranges, issuing calls to the target -/// only to begin and end each subrange, and for the number of cycles spent within. -template -struct SubrangeDispatcher { - static constexpr int max = ClassifierT::max; - - template SubrangeDispatcher(Args&&... args) : - target(std::forward(args)...) {} - - template - void perform(int begin, int end) { - constexpr auto region = ClassifierT::region(n); - const auto clipped_start = std::max(begin, find_begin(n)); - const auto clipped_end = std::min(end, find_end(n)); - - if constexpr (n == find_begin(n)) { - target.template begin(clipped_start); - } - - target.template advance(clipped_end - clipped_start); - - if constexpr (n + 1 == find_end(n)) { - target.template end(clipped_end); - } - } - - private: - static constexpr int find_begin(int n) { - const auto type = ClassifierT::region(n); - while(n && ClassifierT::region(n - 1) == type) --n; - return n; - } - - static constexpr int find_end(int n) { - const auto type = ClassifierT::region(n); - while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n; - return n; - } - - TargetT target; -}; } diff --git a/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm index f5545ac75..af22e1ab0 100644 --- a/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm +++ b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm @@ -28,7 +28,7 @@ struct DoStep { static constexpr int max = 100; - template void perform(int, int) { + template void perform() { assert(n < max); performed[n] = true; } @@ -54,7 +54,7 @@ enum class RangeType { }; struct RangeClassifier { - static constexpr int max = 50; + static constexpr int max = 200; static constexpr RangeType region(int x) { return x >= 10 && x < 20 ? RangeType::Sync : RangeType::Border; @@ -70,16 +70,21 @@ struct RangeTarget { RangeType range_type; int length = 0; - Event(Type event_type, RangeType range_type) : event_type(event_type), range_type(range_type) {} Event(Type event_type, RangeType range_type, int length) : event_type(event_type), range_type(range_type), length(length) {} + + bool operator ==(const Event &rhs) const { + if(rhs.event_type != event_type) return false; + if(rhs.range_type != range_type) return false; + return rhs.length == length; + } }; std::vector events; - template void begin(int) { - events.emplace_back(Event::Type::Begin, type); + template void begin(int position) { + events.emplace_back(Event::Type::Begin, type, position); } - template void end(int) { - events.emplace_back(Event::Type::End, type); + template void end(int position) { + events.emplace_back(Event::Type::End, type, position); } template void advance(int length) { events.emplace_back(Event::Type::Advance, type, length); @@ -87,11 +92,29 @@ struct RangeTarget { }; - (void)testRanges { + RangeTarget target; using Dispatcher = Reflection::SubrangeDispatcher; - Dispatcher dispatcher; - Reflection::RangeDispatcher::dispatch(dispatcher, 0, 10); + Dispatcher::dispatch(target, 0, 11); - printf(""); + XCTAssertEqual(target.events.size(), 5); + XCTAssert(target.events[0] == RangeTarget::Event(RangeTarget::Event::Type::Begin, RangeType::Border, 0)); + XCTAssert(target.events[1] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 10)); + XCTAssert(target.events[2] == RangeTarget::Event(RangeTarget::Event::Type::End, RangeType::Border, 10)); + XCTAssert(target.events[3] == RangeTarget::Event(RangeTarget::Event::Type::Begin, RangeType::Sync, 10)); + XCTAssert(target.events[4] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Sync, 1)); + + Dispatcher::dispatch(target, 11, 75); + Dispatcher::dispatch(target, 75, 100); + Dispatcher::dispatch(target, 100, 200); + + XCTAssertEqual(target.events.size(), 12); + XCTAssert(target.events[5] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Sync, 9)); + XCTAssert(target.events[6] == RangeTarget::Event(RangeTarget::Event::Type::End, RangeType::Sync, 20)); + XCTAssert(target.events[7] == RangeTarget::Event(RangeTarget::Event::Type::Begin, RangeType::Border, 20)); + XCTAssert(target.events[8] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 55)); + XCTAssert(target.events[9] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 25)); + XCTAssert(target.events[10] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 100)); + XCTAssert(target.events[11] == RangeTarget::Event(RangeTarget::Event::Type::End, RangeType::Border, 200)); } @end