mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-03 08:05:40 +00:00
Merge pull request #907 from TomHarte/TMSSequencePoints
Makes the TMS a sequence-point-generating JustInTimeActor.
This commit is contained in:
commit
dc25a60b9b
@ -10,7 +10,9 @@
|
|||||||
#define ClockReceiver_hpp
|
#define ClockReceiver_hpp
|
||||||
|
|
||||||
#include "ForceInline.hpp"
|
#include "ForceInline.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Informal pattern for all classes that run from a clock cycle:
|
Informal pattern for all classes that run from a clock cycle:
|
||||||
@ -176,6 +178,9 @@ class Cycles: public WrappedInt<Cycles> {
|
|||||||
public:
|
public:
|
||||||
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
|
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
|
||||||
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
|
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
|
||||||
|
forceinline static constexpr Cycles max() {
|
||||||
|
return Cycles(std::numeric_limits<IntType>::max());
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend WrappedInt;
|
friend WrappedInt;
|
||||||
@ -195,6 +200,9 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
|||||||
public:
|
public:
|
||||||
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
|
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
|
||||||
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
|
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
|
||||||
|
forceinline static constexpr HalfCycles max() {
|
||||||
|
return HalfCycles(std::numeric_limits<IntType>::max());
|
||||||
|
}
|
||||||
|
|
||||||
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
|
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
|
||||||
|
|
||||||
|
@ -105,7 +105,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
|||||||
if constexpr (has_sequence_points<T>::value) {
|
if constexpr (has_sequence_points<T>::value) {
|
||||||
time_until_event_ -= rhs;
|
time_until_event_ -= rhs;
|
||||||
if(time_until_event_ <= LocalTimeScale(0)) {
|
if(time_until_event_ <= LocalTimeScale(0)) {
|
||||||
|
time_overrun_ = time_until_event_;
|
||||||
flush();
|
flush();
|
||||||
|
update_sequence_point();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,6 +176,12 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
|||||||
return did_flush;
|
return did_flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @returns a number in the range [-max, 0] indicating the offset of the most recent sequence
|
||||||
|
/// point from the final time at the end of the += that triggered the sequence point.
|
||||||
|
[[nodiscard]] forceinline LocalTimeScale last_sequence_point_overrun() {
|
||||||
|
return time_overrun_;
|
||||||
|
}
|
||||||
|
|
||||||
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
|
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
|
||||||
/// supports sequence points; @c LocalTimeScale() otherwise.
|
/// supports sequence points; @c LocalTimeScale() otherwise.
|
||||||
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
|
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
|
||||||
@ -203,7 +211,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
T object_;
|
T object_;
|
||||||
LocalTimeScale time_since_update_, time_until_event_;
|
LocalTimeScale time_since_update_, time_until_event_, time_overrun_;
|
||||||
bool is_flushed_ = true;
|
bool is_flushed_ = true;
|
||||||
bool did_flush_ = false;
|
bool did_flush_ = false;
|
||||||
|
|
||||||
|
@ -708,9 +708,9 @@ HalfCycles Base::half_cycles_before_internal_cycles(int internal_cycles) {
|
|||||||
return HalfCycles(((internal_cycles << 2) + (2 - cycles_error_)) / 3);
|
return HalfCycles(((internal_cycles << 2) + (2 - cycles_error_)) / 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
HalfCycles TMS9918::get_time_until_interrupt() {
|
HalfCycles TMS9918::get_next_sequence_point() {
|
||||||
if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles(-1);
|
if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles::max();
|
||||||
if(get_interrupt_line()) return HalfCycles(0);
|
if(get_interrupt_line()) return HalfCycles::max();
|
||||||
|
|
||||||
// Calculate the amount of time until the next end-of-frame interrupt.
|
// Calculate the amount of time until the next end-of-frame interrupt.
|
||||||
const int frame_length = 342 * mode_timing_.total_lines;
|
const int frame_length = 342 * mode_timing_.total_lines;
|
||||||
|
@ -75,13 +75,13 @@ class TMS9918: public Base {
|
|||||||
void latch_horizontal_counter();
|
void latch_horizontal_counter();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns the amount of time until @c get_interrupt_line would next return true if
|
Returns the amount of time until @c get_interrupt_line would next change if
|
||||||
there are no interceding calls to @c write or to @c read.
|
there are no interceding calls to @c write or to @c read.
|
||||||
|
|
||||||
If get_interrupt_line is true now, returns zero. If get_interrupt_line would
|
If get_interrupt_line is true now of if get_interrupt_line would
|
||||||
never return true, returns -1.
|
never return true, returns HalfCycles::max().
|
||||||
*/
|
*/
|
||||||
HalfCycles get_time_until_interrupt();
|
HalfCycles get_next_sequence_point();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns the amount of time until the nominated line interrupt position is
|
Returns the amount of time until the nominated line interrupt position is
|
||||||
|
@ -576,7 +576,7 @@ class MemoryMap {
|
|||||||
if(region.write) { \
|
if(region.write) { \
|
||||||
region.write[address] = *value; \
|
region.write[address] = *value; \
|
||||||
const bool _mm_is_shadowed = IsShadowed(map, region, address); \
|
const bool _mm_is_shadowed = IsShadowed(map, region, address); \
|
||||||
map.shadow_base[is_shadowed][(®ion.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \
|
map.shadow_base[_mm_is_shadowed][(®ion.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick notes on ::IsShadowed contortions:
|
// Quick notes on ::IsShadowed contortions:
|
||||||
|
@ -215,7 +215,9 @@ class ConcreteMachine:
|
|||||||
}
|
}
|
||||||
const HalfCycles length = cycle.length + penalty;
|
const HalfCycles length = cycle.length + penalty;
|
||||||
|
|
||||||
vdp_ += length;
|
if(vdp_ += length) {
|
||||||
|
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||||
|
}
|
||||||
time_since_sn76489_update_ += length;
|
time_since_sn76489_update_ += length;
|
||||||
|
|
||||||
// Act only if necessary.
|
// Act only if necessary.
|
||||||
@ -263,7 +265,6 @@ class ConcreteMachine:
|
|||||||
case 5:
|
case 5:
|
||||||
*cycle.value = vdp_->read(address);
|
*cycle.value = vdp_->read(address);
|
||||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 7: {
|
case 7: {
|
||||||
@ -304,7 +305,6 @@ class ConcreteMachine:
|
|||||||
case 5:
|
case 5:
|
||||||
vdp_->write(address, *cycle.value);
|
vdp_->write(address, *cycle.value);
|
||||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 7:
|
case 7:
|
||||||
@ -341,13 +341,6 @@ class ConcreteMachine:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(time_until_interrupt_ > 0) {
|
|
||||||
time_until_interrupt_ -= length;
|
|
||||||
if(time_until_interrupt_ <= HalfCycles(0)) {
|
|
||||||
z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return penalty;
|
return penalty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +401,6 @@ class ConcreteMachine:
|
|||||||
bool joysticks_in_keypad_mode_ = false;
|
bool joysticks_in_keypad_mode_ = false;
|
||||||
|
|
||||||
HalfCycles time_since_sn76489_update_;
|
HalfCycles time_since_sn76489_update_;
|
||||||
HalfCycles time_until_interrupt_;
|
|
||||||
|
|
||||||
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
|
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
|
||||||
int pc_zero_accesses_ = 0;
|
int pc_zero_accesses_ = 0;
|
||||||
|
@ -419,7 +419,9 @@ class ConcreteMachine:
|
|||||||
// but otherwise runs without pause.
|
// but otherwise runs without pause.
|
||||||
const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
|
const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
|
||||||
const HalfCycles total_length = addition + cycle.length;
|
const HalfCycles total_length = addition + cycle.length;
|
||||||
vdp_ += total_length;
|
if(vdp_ += total_length) {
|
||||||
|
z80_.set_interrupt_line(vdp_->get_interrupt_line(), vdp_.last_sequence_point_overrun());
|
||||||
|
}
|
||||||
time_since_ay_update_ += total_length;
|
time_since_ay_update_ += total_length;
|
||||||
memory_slots_[0].cycles_since_update += total_length;
|
memory_slots_[0].cycles_since_update += total_length;
|
||||||
memory_slots_[1].cycles_since_update += total_length;
|
memory_slots_[1].cycles_since_update += total_length;
|
||||||
@ -520,7 +522,6 @@ class ConcreteMachine:
|
|||||||
case 0x98: case 0x99:
|
case 0x98: case 0x99:
|
||||||
*cycle.value = vdp_->read(address);
|
*cycle.value = vdp_->read(address);
|
||||||
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
||||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xa2:
|
case 0xa2:
|
||||||
@ -545,7 +546,6 @@ class ConcreteMachine:
|
|||||||
case 0x98: case 0x99:
|
case 0x98: case 0x99:
|
||||||
vdp_->write(address, *cycle.value);
|
vdp_->write(address, *cycle.value);
|
||||||
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
||||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xa0: case 0xa1:
|
case 0xa0: case 0xa1:
|
||||||
@ -606,12 +606,6 @@ class ConcreteMachine:
|
|||||||
if(!tape_player_is_sleeping_)
|
if(!tape_player_is_sleeping_)
|
||||||
tape_player_.run_for(int(cycle.length.as_integral()));
|
tape_player_.run_for(int(cycle.length.as_integral()));
|
||||||
|
|
||||||
if(time_until_interrupt_ > 0) {
|
|
||||||
time_until_interrupt_ -= total_length;
|
|
||||||
if(time_until_interrupt_ <= HalfCycles(0)) {
|
|
||||||
z80_.set_interrupt_line(true, time_until_interrupt_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addition;
|
return addition;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -785,7 +779,6 @@ class ConcreteMachine:
|
|||||||
uint8_t unpopulated_[8192];
|
uint8_t unpopulated_[8192];
|
||||||
|
|
||||||
HalfCycles time_since_ay_update_;
|
HalfCycles time_since_ay_update_;
|
||||||
HalfCycles time_until_interrupt_;
|
|
||||||
|
|
||||||
uint8_t key_states_[16];
|
uint8_t key_states_[16];
|
||||||
int selected_key_line_ = 0;
|
int selected_key_line_ = 0;
|
||||||
|
@ -214,7 +214,9 @@ class ConcreteMachine:
|
|||||||
}
|
}
|
||||||
|
|
||||||
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||||
vdp_ += cycle.length;
|
if(vdp_ += cycle.length) {
|
||||||
|
z80_.set_interrupt_line(vdp_->get_interrupt_line(), vdp_.last_sequence_point_overrun());
|
||||||
|
}
|
||||||
time_since_sn76489_update_ += cycle.length;
|
time_since_sn76489_update_ += cycle.length;
|
||||||
|
|
||||||
if(cycle.is_terminal()) {
|
if(cycle.is_terminal()) {
|
||||||
@ -266,7 +268,6 @@ class ConcreteMachine:
|
|||||||
case 0x80: case 0x81:
|
case 0x80: case 0x81:
|
||||||
*cycle.value = vdp_->read(address);
|
*cycle.value = vdp_->read(address);
|
||||||
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
||||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
|
||||||
break;
|
break;
|
||||||
case 0xc0: {
|
case 0xc0: {
|
||||||
if(memory_control_ & 0x4) {
|
if(memory_control_ & 0x4) {
|
||||||
@ -330,7 +331,6 @@ class ConcreteMachine:
|
|||||||
case 0x80: case 0x81: // i.e. ports 0x80–0xbf.
|
case 0x80: case 0x81: // i.e. ports 0x80–0xbf.
|
||||||
vdp_->write(address, *cycle.value);
|
vdp_->write(address, *cycle.value);
|
||||||
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
||||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
|
||||||
break;
|
break;
|
||||||
case 0xc1: case 0xc0: // i.e. ports 0xc0–0xff.
|
case 0xc1: case 0xc0: // i.e. ports 0xc0–0xff.
|
||||||
if(has_fm_audio_) {
|
if(has_fm_audio_) {
|
||||||
@ -374,13 +374,6 @@ class ConcreteMachine:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(time_until_interrupt_ > 0) {
|
|
||||||
time_until_interrupt_ -= cycle.length;
|
|
||||||
if(time_until_interrupt_ <= HalfCycles(0)) {
|
|
||||||
z80_.set_interrupt_line(true, time_until_interrupt_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The pause button is debounced and takes effect only one line before pixels
|
// The pause button is debounced and takes effect only one line before pixels
|
||||||
// begin; time_until_debounce_ keeps track of the time until then.
|
// begin; time_until_debounce_ keeps track of the time until then.
|
||||||
time_until_debounce_ -= cycle.length;
|
time_until_debounce_ -= cycle.length;
|
||||||
@ -505,7 +498,6 @@ class ConcreteMachine:
|
|||||||
bool reset_is_pressed_ = false, pause_is_pressed_ = false;
|
bool reset_is_pressed_ = false, pause_is_pressed_ = false;
|
||||||
|
|
||||||
HalfCycles time_since_sn76489_update_;
|
HalfCycles time_since_sn76489_update_;
|
||||||
HalfCycles time_until_interrupt_;
|
|
||||||
HalfCycles time_until_debounce_;
|
HalfCycles time_until_debounce_;
|
||||||
|
|
||||||
uint8_t ram_[8*1024];
|
uint8_t ram_[8*1024];
|
||||||
|
@ -34,10 +34,10 @@
|
|||||||
vdp.write(1, 0x8a);
|
vdp.write(1, 0x8a);
|
||||||
|
|
||||||
// Get time until interrupt.
|
// Get time until interrupt.
|
||||||
auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral() - 1;
|
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
|
||||||
|
|
||||||
// Check that an interrupt is now scheduled.
|
// Check that an interrupt is now scheduled.
|
||||||
NSAssert(time_until_interrupt != -2, @"No interrupt scheduled");
|
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt scheduled");
|
||||||
NSAssert(time_until_interrupt > 0, @"Interrupt is scheduled in the past");
|
NSAssert(time_until_interrupt > 0, @"Interrupt is scheduled in the past");
|
||||||
|
|
||||||
// Check interrupt flag isn't set prior to the reported time.
|
// Check interrupt flag isn't set prior to the reported time.
|
||||||
@ -53,7 +53,7 @@
|
|||||||
NSAssert(!vdp.get_interrupt_line(), @"Interrupt wasn't reset by status read");
|
NSAssert(!vdp.get_interrupt_line(), @"Interrupt wasn't reset by status read");
|
||||||
|
|
||||||
// Check interrupt flag isn't set prior to the reported time.
|
// Check interrupt flag isn't set prior to the reported time.
|
||||||
time_until_interrupt = vdp.get_time_until_interrupt().as_integral() - 1;
|
time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
|
||||||
vdp.run_for(HalfCycles(time_until_interrupt));
|
vdp.run_for(HalfCycles(time_until_interrupt));
|
||||||
NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [2]");
|
NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [2]");
|
||||||
|
|
||||||
@ -80,10 +80,10 @@
|
|||||||
|
|
||||||
// Clear the pending interrupt and ask about the next one (i.e. the first one).
|
// Clear the pending interrupt and ask about the next one (i.e. the first one).
|
||||||
vdp.read(1);
|
vdp.read(1);
|
||||||
auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral() - 1;
|
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
|
||||||
|
|
||||||
// Check that an interrupt is now scheduled.
|
// Check that an interrupt is now scheduled.
|
||||||
NSAssert(time_until_interrupt != -2, @"No interrupt scheduled");
|
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt scheduled");
|
||||||
NSAssert(time_until_interrupt > 0, @"Interrupt is scheduled in the past");
|
NSAssert(time_until_interrupt > 0, @"Interrupt is scheduled in the past");
|
||||||
|
|
||||||
// Check interrupt flag isn't set prior to the reported time.
|
// Check interrupt flag isn't set prior to the reported time.
|
||||||
@ -114,20 +114,24 @@
|
|||||||
|
|
||||||
// Now run through an entire frame...
|
// Now run through an entire frame...
|
||||||
int half_cycles = 262*228*2;
|
int half_cycles = 262*228*2;
|
||||||
auto last_time_until_interrupt = vdp.get_time_until_interrupt().as_integral();
|
auto last_time_until_interrupt = vdp.get_next_sequence_point().as_integral();
|
||||||
while(half_cycles--) {
|
while(half_cycles--) {
|
||||||
// Validate that an interrupt happened if one was expected, and clear anything that's present.
|
// Validate that an interrupt happened if one was expected, and clear anything that's present.
|
||||||
NSAssert(vdp.get_interrupt_line() == (last_time_until_interrupt == 0), @"Unexpected interrupt state change; expected %d but got %d; position %d %d @ %d", (last_time_until_interrupt == 0), vdp.get_interrupt_line(), c, with_eof, half_cycles);
|
NSAssert(vdp.get_interrupt_line() == (last_time_until_interrupt == HalfCycles::max().as_integral()), @"Unexpected interrupt state change; expected %d but got %d; position %d %d @ %d", (last_time_until_interrupt == 0), vdp.get_interrupt_line(), c, with_eof, half_cycles);
|
||||||
|
|
||||||
|
if(vdp.get_interrupt_line()) {
|
||||||
vdp.read(1);
|
vdp.read(1);
|
||||||
|
last_time_until_interrupt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
vdp.run_for(HalfCycles(1));
|
vdp.run_for(HalfCycles(1));
|
||||||
|
|
||||||
// Get the time until interrupt.
|
// Get the time until interrupt.
|
||||||
auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral();
|
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral();
|
||||||
NSAssert(time_until_interrupt != -1, @"No interrupt scheduled; position %d %d @ %d", c, with_eof, half_cycles);
|
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() || vdp.get_interrupt_line(), @"No interrupt scheduled; position %d %d @ %d", c, with_eof, half_cycles);
|
||||||
NSAssert(time_until_interrupt >= 0, @"Interrupt is scheduled in the past; position %d %d @ %d", c, with_eof, half_cycles);
|
NSAssert(time_until_interrupt >= 0, @"Interrupt is scheduled in the past; position %d %d @ %d", c, with_eof, half_cycles);
|
||||||
|
|
||||||
if(last_time_until_interrupt) {
|
if(last_time_until_interrupt > 1) {
|
||||||
NSAssert(
|
NSAssert(
|
||||||
time_until_interrupt == (last_time_until_interrupt - 1),
|
time_until_interrupt == (last_time_until_interrupt - 1),
|
||||||
@"Discontinuity found in interrupt prediction; from %@ to %@; position %d %d @ %d",
|
@"Discontinuity found in interrupt prediction; from %@ to %@; position %d %d @ %d",
|
||||||
|
Loading…
Reference in New Issue
Block a user