diff --git a/README.md b/README.md index 88372c2..ed861b9 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ Some emulations require a filesystem image containing programs to be uploaded: Libraries --------- -- [SimpleTimer](https://github.com/jscrane/SimpleTimer) - [PS2KeyAdvanced](https://github.com/techpaul/PS2KeyAdvanced), [PS2KeyRaw](https://github.com/techpaul/PS2KeyRaw) and [PS2KeyMap](https://github.com/techpaul/PS2KeyMap) diff --git a/library.properties b/library.properties index 5681aa7..f297520 100644 --- a/library.properties +++ b/library.properties @@ -1,11 +1,11 @@ name=r65emu -version=1.2.1 +version=1.5.3 author=Stephen Crane maintainer=Stephen Crane sentence=8-bit Microcomputer Emulation Library paragraph=Supports 6502, 8080 and Z80 processors category=Other url=https://github.com/jscrane/r65emu -architectures=esp32, esp8266, tivac, rp2040 -depends=PS2KeyAdvanced, PS2KeyMap, PS2KeyRaw, SimpleTimer +architectures=esp32, esp8266, rp2040 +depends=PS2KeyAdvanced, PS2KeyMap, PS2KeyRaw includes=r65emu.h diff --git a/src/arduinomachine.cpp b/src/arduinomachine.cpp index 9ec1933..0fd6d03 100644 --- a/src/arduinomachine.cpp +++ b/src/arduinomachine.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -31,8 +30,6 @@ spiram sram(SPIRAM_SIZE); #endif -static SimpleTimer timers; - bool Arduino::reset() { DBG_INI("machine reset"); @@ -85,11 +82,6 @@ bool Arduino::reset() { return success; } -static class simple_timer_pollable: public Pollable { -public: - void poll() { timers.run(); } -} stp; - void Arduino::begin() { #if DEBUGGING != DEBUG_NONE @@ -125,20 +117,6 @@ void Arduino::begin() { pinMode(SPIRAM_CS, OUTPUT); digitalWrite(SPIRAM_CS, HIGH); #endif - - register_pollable(stp); -} - -int Arduino::interval_timer(uint32_t interval, std::function cb) { - return timers.setInterval(interval, cb); -} - -int Arduino::oneshot_timer(uint32_t interval, std::function cb) { - return timers.setTimeout(interval, cb); -} - -void Arduino::cancel_timer(int timer) { - timers.deleteTimer(timer); } uint32_t Arduino::microseconds() { return micros(); } diff --git a/src/arduinomachine.h b/src/arduinomachine.h index 83ad3dd..0deb617 100644 --- a/src/arduinomachine.h +++ b/src/arduinomachine.h @@ -33,9 +33,6 @@ public: _reset_handler = handler; } - int interval_timer(uint32_t micros, std::function cb) override; - int oneshot_timer(uint32_t micros, std::function cb) override; - void cancel_timer(int timer) override; uint32_t microseconds() override; void sleep(uint32_t dt) override; void yield() override; diff --git a/src/deltaq.cpp b/src/deltaq.cpp new file mode 100644 index 0000000..29493a5 --- /dev/null +++ b/src/deltaq.cpp @@ -0,0 +1,96 @@ +#include +#include + +#include "deltaq.h" + +void DeltaQueue::cancel(int8_t id) { + if (id < 0 || id >= 32 || (freeSlots & (1UL << id))) return; + + int8_t prev = -1, curr = head; + while (curr != -1 && curr != id) { + prev = curr; + curr = pool[curr].next; + } + + if (curr == id) { + if (prev == -1) head = pool[id].next; + else pool[prev].next = pool[id].next; + + if (pool[id].next != -1) pool[pool[id].next].delta += pool[id].delta; + releaseNode(id); + } +} + +void DeltaQueue::update(uint32_t currentTime) { + uint32_t elapsed = currentTime - lastTime; + lastTime = currentTime; + + while (head != -1 && elapsed >= pool[head].delta) { + int8_t id = head; + elapsed -= pool[id].delta; + head = pool[id].next; + + if (pool[id].callback) pool[id].callback(); + + // Re-insert the SAME node to keep the ID constant + if (pool[id].period > 0) + insertNode(id, pool[id].period); + else + releaseNode(id); + } + if (head != -1) + pool[head].delta -= elapsed; +} + +void DeltaQueue::insertNode(int8_t id, uint32_t delay) { + int8_t prev = -1, curr = head; + uint32_t remaining = delay; + + while (curr != -1 && remaining >= pool[curr].delta) { + remaining -= pool[curr].delta; + prev = curr; + curr = pool[curr].next; + } + + pool[id].delta = remaining; + pool[id].next = curr; + + if (prev == -1) head = id; + else pool[prev].next = id; + + if (curr != -1) pool[curr].delta -= remaining; +} + +int8_t DeltaQueue::insert(uint32_t delay, uint32_t period, Callback cb) { + int8_t id = allocateNode(); + if (id == -1) return -1; + + pool[id].period = period; + pool[id].callback = cb; + + int8_t prev = -1, curr = head; + uint32_t remaining = delay; + + while (curr != -1 && remaining >= pool[curr].delta) { + remaining -= pool[curr].delta; + prev = curr; + curr = pool[curr].next; + } + + pool[id].delta = remaining; + pool[id].next = curr; + + if (prev == -1) head = id; + else pool[prev].next = id; + + if (curr != -1) pool[curr].delta -= remaining; + return id; +} + +int8_t DeltaQueue::allocateNode() { + if (freeSlots == 0) return -1; + // __builtin_ctz finds the index of the first '1' bit (O(1) on most CPUs) + int8_t id = __builtin_ctz(freeSlots); + freeSlots &= ~(1UL << id); + return id; +} diff --git a/src/deltaq.h b/src/deltaq.h new file mode 100644 index 0000000..9a48386 --- /dev/null +++ b/src/deltaq.h @@ -0,0 +1,38 @@ +#pragma once + +// this class was written by Gemini under direction +class DeltaQueue { +public: + typedef std::function Callback; + + DeltaQueue(): head(-1), freeSlots(0xFFFFFFFF), lastTime(0) {} + + int8_t setTimeout(uint32_t delay, Callback cb) { return insert(delay, 0, cb); } + + int8_t setInterval(uint32_t period, Callback cb) { return insert(period, period, cb); } + + void cancel(int8_t id); + + void update(uint32_t currentTime); + +private: + struct { + uint32_t delta; + uint32_t period; + Callback callback; + int8_t next; + } pool[32]; + + int8_t head; + uint32_t freeSlots; // Bitmask: 1 = free, 0 = used + uint32_t lastTime; + + int8_t insert(uint32_t delay, uint32_t period, Callback cb); + + void insertNode(int8_t id, uint32_t delay); + + int8_t allocateNode(); + + inline void releaseNode(int8_t id) { freeSlots |= (1UL << id); } +}; + diff --git a/src/machine.cpp b/src/machine.cpp index f4294e8..f663a32 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -5,9 +5,12 @@ #include "machine.h" #include "memory.h" #include "CPU.h" +#include "deltaq.h" Machine *_machine; +static DeltaQueue timers; + Machine::Machine(CPU &cpu): _cpu(cpu) { _cpu_debug = debug_never; _machine = this; @@ -37,6 +40,8 @@ void Machine::run(uint32_t clock_speed_hz) { uint32_t start_time = microseconds(); + timers.update(start_time); + for (uint8_t i = 0; i < num_pollable; i++) devices[i]->poll(); @@ -69,3 +74,15 @@ void Machine::run(uint32_t clock_speed_hz) { _speed = (_cpu.cycles() - start_cycles) * (1000000 / (microseconds() - start_time)); #endif } + +int8_t Machine::interval_timer(uint32_t interval, std::function cb) { + return timers.setInterval(interval, cb); +} + +int8_t Machine::oneshot_timer(uint32_t interval, std::function cb) { + return timers.setTimeout(interval, cb); +} + +void Machine::cancel_timer(int8_t timer) { + timers.cancel(timer); +} diff --git a/src/machine.h b/src/machine.h index 0e09a7d..6e62f05 100644 --- a/src/machine.h +++ b/src/machine.h @@ -57,9 +57,10 @@ public: void run(uint32_t clock_speed_hz = CLK_MAX); uint32_t current_speed() const { return _speed; } - virtual int interval_timer(uint32_t micros, std::function cb) =0; - virtual int oneshot_timer(uint32_t micros, std::function cb) =0; - virtual void cancel_timer(int timer) =0; + int8_t interval_timer(uint32_t micros, std::function cb); + int8_t oneshot_timer(uint32_t micros, std::function cb); + void cancel_timer(int8_t timer); + virtual uint32_t microseconds() =0; virtual void sleep(uint32_t) =0; virtual void yield() =0; diff --git a/src/z80test/Makefile b/src/z80test/Makefile index a94a2a3..881424a 100644 --- a/src/z80test/Makefile +++ b/src/z80test/Makefile @@ -1,6 +1,6 @@ CPPFLAGS:= -I.. -DUNDOCUMENTED_OPS -DDEBUGGING=0x40 CXXFLAGS:= -g -fno-operator-names -Wall -OBJECTS := z80test.o z80.o memory.o machine.o CPU.o +OBJECTS := z80test.o z80.o memory.o machine.o deltaq.o all: z80test tests.me sum diff --git a/src/z80test/z80test.cc b/src/z80test/z80test.cc index 4b74f9f..df5e930 100644 --- a/src/z80test/z80test.cc +++ b/src/z80test/z80test.cc @@ -17,10 +17,6 @@ class TestMachine: public Machine { public: TestMachine(CPU &c): Machine(c) {} - int interval_timer(uint32_t micros, std::function cb) override { return -1; } - int oneshot_timer(uint32_t micros, std::function cb) override { return -1; } - void cancel_timer(int timer) override {} - uint32_t microseconds() override { return 0; } void sleep(uint32_t) override {} void yield() override {}