mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-02 02:49:28 +00:00
Merge pull request #1144 from TomHarte/Base144
Enhance mechanisms for display-style dispatch.
This commit is contained in:
commit
f30637a773
@ -7,6 +7,7 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
4281683A2A37AFB4008ECD27 /* DispatcherTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 428168392A37AFB4008ECD27 /* DispatcherTests.mm */; };
|
||||||
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; };
|
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; };
|
||||||
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; };
|
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; };
|
||||||
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
|
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
|
||||||
@ -1101,6 +1102,7 @@
|
|||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = "<group>"; };
|
428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = "<group>"; };
|
||||||
|
428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = "<group>"; };
|
||||||
42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
|
42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
|
||||||
42AD55302A0C4D5000ACE410 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = "<group>"; };
|
42AD55302A0C4D5000ACE410 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = "<group>"; };
|
||||||
42AD55312A0C4D5000ACE410 /* 68000Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = "<group>"; };
|
42AD55312A0C4D5000ACE410 /* 68000Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = "<group>"; };
|
||||||
@ -2780,10 +2782,10 @@
|
|||||||
4B3AF7CF2413470E00873C0B /* Reflection */ = {
|
4B3AF7CF2413470E00873C0B /* Reflection */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4B3AF7D02413470E00873C0B /* Enum.hpp */,
|
|
||||||
4B3AF7D12413472200873C0B /* Struct.hpp */,
|
|
||||||
4B47F6C4241C87A100ED06F7 /* Struct.cpp */,
|
|
||||||
4BDA7F8129C4C223007A10A5 /* Dispatcher.hpp */,
|
4BDA7F8129C4C223007A10A5 /* Dispatcher.hpp */,
|
||||||
|
4B3AF7D02413470E00873C0B /* Enum.hpp */,
|
||||||
|
4B47F6C4241C87A100ED06F7 /* Struct.cpp */,
|
||||||
|
4B3AF7D12413472200873C0B /* Struct.hpp */,
|
||||||
);
|
);
|
||||||
name = Reflection;
|
name = Reflection;
|
||||||
path = ../../Reflection;
|
path = ../../Reflection;
|
||||||
@ -4286,6 +4288,7 @@
|
|||||||
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */,
|
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */,
|
||||||
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
|
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
|
||||||
4BB0CAA627E51B6300672A88 /* DingusdevPowerPCTests.mm */,
|
4BB0CAA627E51B6300672A88 /* DingusdevPowerPCTests.mm */,
|
||||||
|
428168392A37AFB4008ECD27 /* DispatcherTests.mm */,
|
||||||
4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */,
|
4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */,
|
||||||
4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */,
|
4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */,
|
||||||
4B051CB2267D3FF800CA44E8 /* EnterpriseNickTests.mm */,
|
4B051CB2267D3FF800CA44E8 /* EnterpriseNickTests.mm */,
|
||||||
@ -4967,12 +4970,12 @@
|
|||||||
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */,
|
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */,
|
||||||
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
|
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
|
||||||
4B8A7E85212F988200F2BBC6 /* DeferredQueue.hpp */,
|
4B8A7E85212F988200F2BBC6 /* DeferredQueue.hpp */,
|
||||||
|
4B99EBD026BF2D9F00CA924D /* DeferredValue.hpp */,
|
||||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
|
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
|
||||||
4B80214322EE7C3E00068002 /* JustInTime.hpp */,
|
4B80214322EE7C3E00068002 /* JustInTime.hpp */,
|
||||||
4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */,
|
4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */,
|
||||||
4B449C942063389900A095C8 /* TimeTypes.hpp */,
|
4B449C942063389900A095C8 /* TimeTypes.hpp */,
|
||||||
4B996B2D2496DAC2001660EF /* VSyncPredictor.hpp */,
|
4B996B2D2496DAC2001660EF /* VSyncPredictor.hpp */,
|
||||||
4B99EBD026BF2D9F00CA924D /* DeferredValue.hpp */,
|
|
||||||
);
|
);
|
||||||
name = ClockReceiver;
|
name = ClockReceiver;
|
||||||
path = ../../ClockReceiver;
|
path = ../../ClockReceiver;
|
||||||
@ -6153,6 +6156,7 @@
|
|||||||
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */,
|
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */,
|
||||||
4B778F3823A5F11C0000D260 /* SegmentParser.cpp in Sources */,
|
4B778F3823A5F11C0000D260 /* SegmentParser.cpp in Sources */,
|
||||||
4B778F0723A5EC150000D260 /* CommodoreTAP.cpp in Sources */,
|
4B778F0723A5EC150000D260 /* CommodoreTAP.cpp in Sources */,
|
||||||
|
4281683A2A37AFB4008ECD27 /* DispatcherTests.mm in Sources */,
|
||||||
4B778F4123A5F19A0000D260 /* MemoryPacker.cpp in Sources */,
|
4B778F4123A5F19A0000D260 /* MemoryPacker.cpp in Sources */,
|
||||||
4B778F4423A5F1BE0000D260 /* CommodoreGCR.cpp in Sources */,
|
4B778F4423A5F1BE0000D260 /* CommodoreGCR.cpp in Sources */,
|
||||||
4B778EF923A5EB740000D260 /* MSA.cpp in Sources */,
|
4B778EF923A5EB740000D260 /* MSA.cpp in Sources */,
|
||||||
|
116
OSBindings/Mac/Clock SignalTests/DispatcherTests.mm
Normal file
116
OSBindings/Mac/Clock SignalTests/DispatcherTests.mm
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
//
|
||||||
|
// DispatcherTests.m
|
||||||
|
// Clock SignalTests
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 12/06/2023.
|
||||||
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <XCTest/XCTest.h>
|
||||||
|
|
||||||
|
#include "Dispatcher.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
@interface DispatcherTests : XCTestCase
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation DispatcherTests
|
||||||
|
|
||||||
|
struct DoStep {
|
||||||
|
static constexpr int max = 100;
|
||||||
|
template <int n> void perform() {
|
||||||
|
assert(n < max);
|
||||||
|
performed[n] = true;
|
||||||
|
}
|
||||||
|
std::array<bool, max> performed{};
|
||||||
|
};
|
||||||
|
|
||||||
|
- (void)testPoints {
|
||||||
|
DoStep stepper;
|
||||||
|
|
||||||
|
Reflection::RangeDispatcher<DoStep>::dispatch(stepper, 0, 10);
|
||||||
|
for(size_t c = 0; c < stepper.performed.size(); c++) {
|
||||||
|
XCTAssert(stepper.performed[c] == (c < 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
Reflection::RangeDispatcher<DoStep>::dispatch(stepper, 29, 100000);
|
||||||
|
for(size_t c = 0; c < stepper.performed.size(); c++) {
|
||||||
|
XCTAssert(stepper.performed[c] == (c < 10) || (c >= 29));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class RangeType {
|
||||||
|
Sync, Border
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RangeClassifier {
|
||||||
|
static constexpr int max = 200;
|
||||||
|
|
||||||
|
static constexpr RangeType region(int x) {
|
||||||
|
return x >= 10 && x < 20 ? RangeType::Sync : RangeType::Border;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RangeTarget {
|
||||||
|
struct Event {
|
||||||
|
enum class Type {
|
||||||
|
Begin, End, Advance
|
||||||
|
};
|
||||||
|
Type event_type;
|
||||||
|
RangeType range_type;
|
||||||
|
int length = 0;
|
||||||
|
|
||||||
|
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<Event> events;
|
||||||
|
|
||||||
|
template <RangeType type> void begin(int position) {
|
||||||
|
events.emplace_back(Event::Type::Begin, type, position);
|
||||||
|
}
|
||||||
|
template <RangeType type> void end(int position) {
|
||||||
|
events.emplace_back(Event::Type::End, type, position);
|
||||||
|
}
|
||||||
|
template <RangeType type> void advance(int length) {
|
||||||
|
events.emplace_back(Event::Type::Advance, type, length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
- (void)testRanges {
|
||||||
|
RangeTarget target;
|
||||||
|
using Dispatcher = Reflection::SubrangeDispatcher<RangeClassifier, RangeTarget>;
|
||||||
|
Dispatcher::dispatch(target, 0, 11);
|
||||||
|
|
||||||
|
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, 199);
|
||||||
|
Dispatcher::dispatch(target, 199, 200);
|
||||||
|
Dispatcher::dispatch(target, 200, 400); // Out of range.
|
||||||
|
|
||||||
|
XCTAssertEqual(target.events.size(), 13);
|
||||||
|
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, 99));
|
||||||
|
XCTAssert(target.events[11] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 1));
|
||||||
|
XCTAssert(target.events[12] == RangeTarget::Event(RangeTarget::Event::Type::End, RangeType::Border, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
@ -9,6 +9,7 @@
|
|||||||
#ifndef Dispatcher_hpp
|
#ifndef Dispatcher_hpp
|
||||||
#define Dispatcher_hpp
|
#define Dispatcher_hpp
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace Reflection {
|
namespace Reflection {
|
||||||
@ -34,6 +35,140 @@ template <typename TargetT, typename... Args> void dispatch(TargetT &t, uint8_t
|
|||||||
#undef Opt
|
#undef Opt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
#define index16(n) index8(n); index8(n+8);
|
||||||
|
#define index32(n) index16(n); index16(n+16);
|
||||||
|
#define index64(n) index32(n); index32(n+32);
|
||||||
|
#define index128(n) index64(n); index64(n+64);
|
||||||
|
#define index256(n) index128(n); index128(n+128);
|
||||||
|
#define index512(n) index256(n); index256(n+256);
|
||||||
|
#define index1024(n) index512(n); index512(n+512);
|
||||||
|
#define index2048(n) index1024(n); index1024(n+1024);
|
||||||
|
|
||||||
|
#define switch_indices(x) switch(x) { default: assert(false); index2048(0); }
|
||||||
|
static constexpr int switch_max = 2048;
|
||||||
|
|
||||||
|
/// 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 <typename SequencerT>
|
||||||
|
struct RangeDispatcher {
|
||||||
|
static_assert(SequencerT::max < switch_max);
|
||||||
|
|
||||||
|
/// Perform @c target.perform<n>() for the input range `begin <= n < end`.
|
||||||
|
template <typename... Args>
|
||||||
|
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<true>(target, begin, end, args...);
|
||||||
|
} else {
|
||||||
|
dispatch<false>(target, begin, end, args...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <bool use_end, typename... Args> 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<n>(args...); \
|
||||||
|
} \
|
||||||
|
[[fallthrough]];
|
||||||
|
|
||||||
|
switch_indices(begin);
|
||||||
|
|
||||||
|
#undef index
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Uses a classifier to divide a range into typed subranges and issues calls to a target of the form:
|
||||||
|
///
|
||||||
|
/// * begin<type>(location) upon entering a new region;
|
||||||
|
/// * end<type>(location) upon entering a region; and
|
||||||
|
/// * advance<type>(distance) in as many chunks as required to populate the inside of the region.
|
||||||
|
///
|
||||||
|
/// @c begin and @c end have iterator-style semantics: begin's location will be the first location in the relevant subrange and
|
||||||
|
/// end's will be the first location not in the relevant subrange.
|
||||||
|
template <typename ClassifierT, typename TargetT>
|
||||||
|
struct SubrangeDispatcher {
|
||||||
|
static_assert(ClassifierT::max < switch_max);
|
||||||
|
|
||||||
|
static void dispatch(TargetT &target, int begin, int end) {
|
||||||
|
#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<region>(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<region>(clipped_end - clipped_begin); \
|
||||||
|
\
|
||||||
|
if(clipped_end == n + 1) { \
|
||||||
|
target.template end<region>(n + 1); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
[[fallthrough]];
|
||||||
|
|
||||||
|
switch_indices(begin);
|
||||||
|
|
||||||
|
#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 switch_indices
|
||||||
|
|
||||||
|
#undef index2
|
||||||
|
#undef index4
|
||||||
|
#undef index8
|
||||||
|
#undef index16
|
||||||
|
#undef index32
|
||||||
|
#undef index64
|
||||||
|
#undef index128
|
||||||
|
#undef index256
|
||||||
|
#undef index512
|
||||||
|
#undef index1024
|
||||||
|
#undef index2048
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* Dispatcher_hpp */
|
#endif /* Dispatcher_hpp */
|
||||||
|
Loading…
Reference in New Issue
Block a user