replace SimpleTimer with DeltaQueue (#136)

* replace SimpleTimer with DeltaQueue
* pull Arduino timer methods up
* update library dependencies
This commit is contained in:
Stephen Crane authored and GitHub committed 2026-04-22 14:01:32 +01:00
1 parent a3b1b96e5f
commit f9e8aaade3
10 files changed
+159 -37

No files matched your search

-1
View File
@@ -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)
+3 -3
View File
@@ -1,11 +1,11 @@
name=r65emu
version=1.2.1
version=1.5.3
author=Stephen Crane <jscrane@gmail.com>
maintainer=Stephen Crane <jscrane@gmail.com>
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
-22
View File
@@ -1,5 +1,4 @@
#include <Arduino.h>
#include <SimpleTimer.h>
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>
@@ -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<void(void)> cb) {
return timers.setInterval(interval, cb);
}
int Arduino::oneshot_timer(uint32_t interval, std::function<void(void)> cb) {
return timers.setTimeout(interval, cb);
}
void Arduino::cancel_timer(int timer) {
timers.deleteTimer(timer);
}
uint32_t Arduino::microseconds() { return micros(); }
-3
View File
@@ -33,9 +33,6 @@ public:
_reset_handler = handler;
}
int interval_timer(uint32_t micros, std::function<void(void)> cb) override;
int oneshot_timer(uint32_t micros, std::function<void(void)> cb) override;
void cancel_timer(int timer) override;
uint32_t microseconds() override;
void sleep(uint32_t dt) override;
void yield() override;
+96
View File
@@ -0,0 +1,96 @@
#include <stdint.h>
#include <functional>
#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;
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
// this class was written by Gemini under direction
class DeltaQueue {
public:
typedef std::function<void()> 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); }
};
+17
View File
@@ -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<void(void)> cb) {
return timers.setInterval(interval, cb);
}
int8_t Machine::oneshot_timer(uint32_t interval, std::function<void(void)> cb) {
return timers.setTimeout(interval, cb);
}
void Machine::cancel_timer(int8_t timer) {
timers.cancel(timer);
}
+4 -3
View File
@@ -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<void(void)> cb) =0;
virtual int oneshot_timer(uint32_t micros, std::function<void(void)> cb) =0;
virtual void cancel_timer(int timer) =0;
int8_t interval_timer(uint32_t micros, std::function<void(void)> cb);
int8_t oneshot_timer(uint32_t micros, std::function<void(void)> cb);
void cancel_timer(int8_t timer);
virtual uint32_t microseconds() =0;
virtual void sleep(uint32_t) =0;
virtual void yield() =0;
+1 -1
View File
@@ -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
-4
View File
@@ -17,10 +17,6 @@ class TestMachine: public Machine {
public:
TestMachine(CPU &c): Machine(c) {}
int interval_timer(uint32_t micros, std::function<void(void)> cb) override { return -1; }
int oneshot_timer(uint32_t micros, std::function<void(void)> cb) override { return -1; }
void cancel_timer(int timer) override {}
uint32_t microseconds() override { return 0; }
void sleep(uint32_t) override {}
void yield() override {}