summaryrefslogtreecommitdiff
path: root/src/main/smp/sys/time
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
committerCliff Click <cliffc@acm.org>2026-11-14 16:23:39 -0800
commit207febef9924c8fa5391185e75d42a7ea480bb38 (patch)
tree68ec6225a89fa1178c32fdf659b9b4f2c3327288 /src/main/smp/sys/time
parent259f60b105eb770bc6f7e3bd36d91f2b217e9315 (diff)
downloadSimple-207febef9924c8fa5391185e75d42a7ea480bb38.tar.gz
Simple-207febef9924c8fa5391185e75d42a7ea480bb38.zip
Chapter 31: Software Drawing, Time, and Graphical Gameslinear
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki <jun784@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/main/smp/sys/time')
-rw-r--r--src/main/smp/sys/time/DateTime.smp13
-rw-r--r--src/main/smp/sys/time/Timers.smp80
2 files changed, 93 insertions, 0 deletions
diff --git a/src/main/smp/sys/time/DateTime.smp b/src/main/smp/sys/time/DateTime.smp
new file mode 100644
index 00000000..53b6688e
--- /dev/null
+++ b/src/main/smp/sys/time/DateTime.smp
@@ -0,0 +1,13 @@
+// Calendar view of an instant. Month/day start at 1; weekday is Sunday=0.
+// year==0 means out of the supported calendar range (years 1 through 9999).
+
+// All values default zero/false
+struct DateTime {
+ int year, month, day;
+ int hour, minute, second, millisecond;
+ int weekday, offsetSeconds;
+ bool daylight;
+};
+
+// Factory in the defining unit, so callers need no forward instance type.
+val empty = { -> new DateTime(); };
diff --git a/src/main/smp/sys/time/Timers.smp b/src/main/smp/sys/time/Timers.smp
new file mode 100644
index 00000000..7cca2e9c
--- /dev/null
+++ b/src/main/smp/sys/time/Timers.smp
@@ -0,0 +1,80 @@
+// Cooperative timers. poll returns IDs; callbacks never run on another thread.
+// IDs are u32; 0 means failure or no timer due. Millisecond values use _ms.
+
+// The
+
+
+struct Timers {
+ // Collection of timers (as struct-of-arrays)
+ u32[] !_ids = new u32[8]; // Which timer id for this slot
+ i64[] !_due_ms = new i64[8]; // Monotonic deadline in milliseconds
+ i64[] !_period_ms = new i64[8]; // When does it repeat
+ // Next available timer ID; 0 means all IDs have been used.
+ u32 !_next = 1;
+
+ // Add a new timer
+ { i64 i64 i64 -> u32 } _add = { i64 current_ms, i64 delay_ms, i64 period_ms ->
+ if( current_ms < 0 || delay_ms < 0 || current_ms > sys.integer.MAX_I64-delay_ms || _next == 0 ) return 0;
+ // Ugly linear scan for an available slot
+ int slot = 0;
+ while( slot < _ids# && _ids[slot] != 0 ) slot++;
+ // Double size for lots of timers
+ if( slot == _ids# ) {
+ if( _ids# > sys.integer.MAX_I32/2 ) return 0;
+ val ids = new u32[_ids#*2];
+ val due_ms = new i64[_ids#*2], periods_ms = new i64[_ids#*2];
+ for( int i = 0; i < _ids#; i++ ) { ids[i] = _ids[i]; due_ms[i] = _due_ms[i]; periods_ms[i] = _period_ms[i]; }
+ _ids = ids; _due_ms = due_ms; _period_ms = periods_ms;
+ // Retry against the newly published arrays after growing.
+ return _add(current_ms,delay_ms,period_ms);
+ }
+ // Next available timer id
+ u32 id = _next;
+ _next = id == sys.integer.MAX_U32 ? 0 : id+1;
+ _ids[slot] = id; _due_ms[slot] = current_ms+delay_ms; _period_ms[slot] = period_ms;
+ return id;
+ };
+
+ // Explicit-clock versions make game logic and tests deterministic.
+ { i64 i64 -> u32 } afterAt = { i64 current_ms, i64 delay_ms -> _add(current_ms,delay_ms,0); };
+ { i64 i64 -> u32 } everyAt = { i64 current_ms, i64 period_ms -> period_ms <= 0 ? 0 : _add(current_ms,period_ms,period_ms); };
+ { i64 -> u32 } after = { i64 delay_ms -> afterAt(time.monotonic(),delay_ms ); };
+ { i64 -> u32 } every = { i64 period_ms -> everyAt(time.monotonic(),period_ms); };
+
+ // Remove a timer
+ val cancel = { u32 id ->
+ if( id == 0 ) return false;
+ for( int i = 0; i < _ids#; i++ ) if( _ids[i] == id ) { _ids[i] = 0; return true; }
+ return false;
+ };
+
+ // Earliest absolute monotonic deadline in milliseconds, or -1 when empty.
+ val nextDeadline = { ->
+ i64 next_ms = -1;
+ // Ugly linear scan again, but short
+ for( int i = 0; i < _ids#; i++ )
+ if( _ids[i] != 0 && (next_ms < 0 || _due_ms[i] < next_ms) ) next_ms = _due_ms[i];
+ return next_ms;
+ };
+
+ // Next firing timer.
+
+ // Earliest deadline first; equal deadlines use registration order.
+ // Repeats skip missed ticks and retain their phase, avoiding bursts/drift.
+ { i64 -> u32 } poll = { i64 current_ms ->
+ int slot = -1;
+ for( int i = 0; i < _ids#; i++ )
+ if( _ids[i] != 0 && _due_ms[i] <= current_ms && (slot < 0 || _due_ms[i] < _due_ms[slot] ||
+ (_due_ms[i] == _due_ms[slot] && _ids[i] < _ids[slot])) ) slot = i;
+ if( slot < 0 ) return 0;
+ u32 id = _ids[slot];
+ i64 period_ms = _period_ms[slot];
+ if( period_ms == 0 ) _ids[slot] = 0;
+ else {
+ i64 steps = (current_ms-_due_ms[slot])/period_ms;
+ if( steps >= (sys.integer.MAX_I64-_due_ms[slot])/period_ms ) _ids[slot] = 0;
+ else _due_ms[slot] += (steps+1)*period_ms;
+ }
+ return id;
+ };
+};