From 299addd03094389bb622e0fe1d7ad585b51e146d Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Fri, 13 Nov 2026 13:10:15 -0800 Subject: Cleanp the new time & graphics APIs --- chapter31/docs/drawing.md | 3 + chapter31/docs/examples/Clock.smp | 76 +++++++++++++++----------- chapter31/docs/examples/TicTacToeGraphical.smp | 1 + chapter31/docs/time.md | 28 +++++++--- 4 files changed, 67 insertions(+), 41 deletions(-) (limited to 'chapter31/docs') diff --git a/chapter31/docs/drawing.md b/chapter31/docs/drawing.md index af0a7f06..b99c1bf8 100644 --- a/chapter31/docs/drawing.md +++ b/chapter31/docs/drawing.md @@ -50,6 +50,9 @@ Packed colors use hexadecimal literals (`0xRRGGBB`); `rgb(r,g,b)` remains handy for computed components. The font masks use leading-zero octal, with five digits after the prefix: each digit is one three-pixel row, top to bottom. For example, `A` is `025755`: rows `010`, `101`, `111`, `101`, `101`. +The font includes all printable ASCII punctuation except `@`, `&`, and `$`, +which use the question-mark fallback. Colons and slashes support clock and date +labels; quotes, brackets, arithmetic signs, and other small symbols fit in 3×5. ## Button widget diff --git a/chapter31/docs/examples/Clock.smp b/chapter31/docs/examples/Clock.smp index 900126e1..2dea18ec 100644 --- a/chapter31/docs/examples/Clock.smp +++ b/chapter31/docs/examples/Clock.smp @@ -1,50 +1,61 @@ // Local clocks; click the button or press Space to switch. Q/Escape exits. // Any argument runs ten frames, exercising both faces for smoke tests. u32 GREEN=0x62ff70, DIM=0x123d20, BACKGROUND=0x020b05; + val main = { u8[~][] args -> - val screen=new sys.draw.Canvas(720,540); + // The main Canvas + val screen = new sys.draw.Canvas(720,540); if( sys.gfx.open("Simple digital and analog clocks",screen.width,screen.height)!=sys.gfx.OK ) return 1; - val button=new sys.draw.Button(220,454,280,52,"ANALOG CLOCK"); - val timers=new sys.time.Timers(); - i64 frame=timers.every(33); - if( frame<0 ) { sys.gfx.close(); return 1; } + + // Button to flip clock style + val button = new sys.draw.Button(220,454,280,52,"ANALOG CLOCK"); + val timers = new sys.time.Timers(); + u32 frame_id = timers.every(33); + if( frame_id==0 ) { sys.gfx.close(); return 1; } + bool analog=false, held=false; int frames=0, status=sys.gfx.OK; + + // Main event loop while( status==sys.gfx.OK ) { - bool space=sys.gfx.key(' '); - bool down=sys.gfx.button(sys.gfx.LEFT); - bool click=button.update(sys.gfx.mouseX(),sys.gfx.mouseY(),down); + bool space = sys.gfx.key(' '); + bool down = sys.gfx.button(sys.gfx.LEFT); + bool click = button.update(sys.gfx.mouseX(),sys.gfx.mouseY(),down); if( click || (space && !held) ) analog=!analog; - held=space; - button.label=analog ? "DIGITAL CLOCK" : "ANALOG CLOCK"; + held = space; + button.label = analog ? "DIGITAL CLOCK" : "ANALOG CLOCK"; if( sys.gfx.key('Q') ) break; - if( timers.poll(sys.time.monotonic())==frame ) { + if( timers.poll(sys.time.monotonic())==frame_id ) { _draw(screen,sys.time.now(),analog); button.draw(screen,3,GREEN,BACKGROUND,DIM); - status=sys.gfx.present(screen.pixels); + status = sys.gfx.present(screen.pixels); if( args#>1 ) { frames++; if( frames==5 ) analog=true; if( frames==10 ) break; } } - if( status==sys.gfx.OK ) status=sys.gfx.wait(sys.time.remaining(timers.nextDeadline(),sys.time.monotonic())); + if( status!=sys.gfx.OK ) break; + // Stall until next timer event + status = sys.gfx.wait(sys.time.remaining(timers.nextDeadline(),sys.time.monotonic())); } sys.gfx.close(); return status==sys.gfx.ERROR ? 1 : 0; }; -val _draw = { !sys.draw.Canvas screen, i64 wall, i64 analog -> - val d=sys.time.local(wall); + +val _draw = { !sys.draw.Canvas screen, i64 wall_ms, i64 analog -> + val d = sys.time.local(wall_ms); screen.clear(BACKGROUND); screen.text(266,24,"SIMPLE CLOCK",4,GREEN); - if( analog ) _analog_noInline(screen,d.hour,d.minute,d.second,d.millisecond); - else _digital_noInline(screen,d.hour,d.minute,d.second); - screen.text(298,412,"%(sys.time.formatLocal(wall,sys.time.DATE))",4,GREEN); + if( analog ) _analog(screen,d.hour,d.minute,d.second,d.millisecond); + else _digital(screen,d.hour,d.minute,d.second); + screen.text(298,412,"%(sys.time.formatLocal(wall_ms,sys.time.DATE))",4,GREEN); screen.text(249,520,"SPACE SWITCH Q OR ESC QUIT",2,GREEN); }; + // a..g bits: top, upper-right, lower-right, bottom, lower-left, upper-left, // middle. Faint unlit bars retain the old LED figure-eight silhouette. -val _digit_noInline = { !sys.draw.Canvas c, int x, int y, i64 digit -> +val _digit = { !sys.draw.Canvas c, int x, int y, i64 digit -> int bits=digit==0 ? 0x3f : digit==1 ? 0x06 : digit==2 ? 0x5b : digit==3 ? 0x4f : digit==4 ? 0x66 : digit==5 ? 0x6d : digit==6 ? 0x7d : digit==7 ? 0x07 : digit==8 ? 0x7f : digit==9 ? 0x6f : 0; @@ -60,10 +71,11 @@ val _digit_noInline = { !sys.draw.Canvas c, int x, int y, i64 digit -> } } }; -val _digital_noInline = { !sys.draw.Canvas c, int hour, int minute, int second -> - _digit_noInline(c,68,166,hour/10); _digit_noInline(c,154,166,hour-hour/10*10); - _digit_noInline(c,284,166,minute/10); _digit_noInline(c,370,166,minute-minute/10*10); - _digit_noInline(c,500,166,second/10); _digit_noInline(c,586,166,second-second/10*10); + +val _digital = { !sys.draw.Canvas c, int hour, int minute, int second -> + _digit(c,68,166,hour/10); _digit(c,154,166,hour-hour/10*10); + _digit(c,284,166,minute/10); _digit(c,370,166,minute-minute/10*10); + _digit(c,500,166,second/10); _digit(c,586,166,second-second/10*10); for( int i=0; i<2; i++ ) { c.fillCircle(254+i*216,214,6,GREEN); c.fillCircle(254+i*216,262,6,GREEN); @@ -72,7 +84,7 @@ val _digital_noInline = { !sys.draw.Canvas c, int hour, int minute, int second - }; // Rational sine approximation, accurate enough for pixel clock geometry. // Arguments are nonnegative turns; no additional native math dependency. -val _sin_noInline = { f64 turn -> +val _sin = { f64 turn -> i64 whole=turn; turn-=whole; bool negative=turn>0.5; f64 x=(negative ? turn-0.5 : turn)*6.283185307179586; @@ -80,26 +92,26 @@ val _sin_noInline = { f64 turn -> f64 numerator=negative ? -16.0 : 16.0; return numerator*product/(49.34802200544679-4*product); }; -val _hand_noInline = { !sys.draw.Canvas c, f64 turn, int length, int thickness, u32 color -> - int x=360+_sin_noInline(turn)*length, y=232-_sin_noInline(turn+0.25)*length; +val _hand = { !sys.draw.Canvas c, f64 turn, int length, int thickness, u32 color -> + int x=360+_sin(turn)*length, y=232-_sin(turn+0.25)*length; c.line(360,232,x,y,thickness,color); }; -val _analog_noInline = { !sys.draw.Canvas c, int hour, int minute, int second, int millisecond -> +val _analog = { !sys.draw.Canvas c, int hour, int minute, int second, int millisecond -> c.circle(360,232,154,3,GREEN); for( int i=0; i<60; i++ ) { f64 turn=i/60.0; bool major=i-i/5*5==0; int inner=major ? 133 : 143; - int x0=360+_sin_noInline(turn)*inner, y0=232-_sin_noInline(turn+0.25)*inner; - int x1=360+_sin_noInline(turn)*149, y1=232-_sin_noInline(turn+0.25)*149; + int x0=360+_sin(turn)*inner, y0=232-_sin(turn+0.25)*inner; + int x1=360+_sin(turn)*149, y1=232-_sin(turn+0.25)*149; c.line(x0,y0,x1,y1,major ? 3 : 1,GREEN); } c.text(349,100,"12",2,GREEN); c.text(484,227,"3",2,GREEN); c.text(357,354,"6",2,GREEN); c.text(230,227,"9",2,GREEN); f64 seconds=second+millisecond/1000.0; f64 minutes=minute+seconds/60.0; - _hand_noInline(c,(hour-hour/12*12+minutes/60.0)/12.0,78,8,GREEN); - _hand_noInline(c,minutes/60.0,112,5,GREEN); - _hand_noInline(c,seconds/60.0,128,2,0xc3ffcb); + _hand(c,(hour-hour/12*12+minutes/60.0)/12.0,78,8,GREEN); + _hand(c,minutes/60.0,112,5,GREEN); + _hand(c,seconds/60.0,128,2,0xc3ffcb); c.fillCircle(360,232,6,GREEN); }; diff --git a/chapter31/docs/examples/TicTacToeGraphical.smp b/chapter31/docs/examples/TicTacToeGraphical.smp index fddcf254..da77e8f6 100644 --- a/chapter31/docs/examples/TicTacToeGraphical.smp +++ b/chapter31/docs/examples/TicTacToeGraphical.smp @@ -12,6 +12,7 @@ val main = { u8[~][] args -> int result = sys.gfx.present(screen.pixels); bool mouseHeld = false, keyHeld = false, replayHeld = false; int hover = -1, frames = 0; + while( result==sys.gfx.OK ) { result = sys.gfx.wait(17); if( result!=sys.gfx.OK ) break; diff --git a/chapter31/docs/time.md b/chapter31/docs/time.md index 04865626..10733006 100644 --- a/chapter31/docs/time.md +++ b/chapter31/docs/time.md @@ -12,9 +12,9 @@ monotonic deadline. | `time.now()` | Unix-epoch milliseconds; may jump when the system clock changes. | | `time.monotonic()` | Monotonic milliseconds for elapsed time and deadlines. | | `time.nanoTime()` | Monotonic nanoseconds, with the clock's available resolution. | -| `time.sleep(ms)` | Sleep for a nonnegative duration. | -| `time.sleepUntil(deadline)` | Wait for a monotonic millisecond deadline; returns immediately if past. | -| `time.remaining(deadline, current)` | Nonnegative difference, saturating on overflow. | +| `time.sleep(duration_ms)` | Sleep for a nonnegative duration. | +| `time.sleepUntil(deadline_ms)` | Wait for a monotonic millisecond deadline; returns immediately if past. | +| `time.remaining(deadline_ms, current_ms)` | Nonnegative difference, saturating on overflow. | Clock errors return `-1`; sleeps return zero on success or `-1` on error, with `sys.libc.errno()` available. Interrupted sleeps resume rather than @@ -27,7 +27,11 @@ to milliseconds. `toSeconds(ms)`, `toMinutes(ms)`, `toHours(ms)`, and `toDays(ms)` truncate toward zero. A duration day is exactly 24 hours, independent of daylight saving. `toNanos(ms)` and `fromNanos(ns)` convert between millisecond and nanosecond units. Multiplication overflow returns -`-1`. Constants `SECOND`, `MINUTE`, `HOUR`, and `DAY` are also available. +`-1`. Constants `SECOND`, `MINUTE`, `HOUR`, and `DAY` are also available, +defined as `1000`, `60*SECOND`, `60*MINUTE`, and `24*HOUR` respectively. +`NANOS_PER_MS` is `1000*1000`. Overflow checks use the shared +`sys.integer.MIN_I64` and `MAX_I64` bounds; `MAX_I32` and `MAX_U32` in that +module also describe the library's array and timer-ID limits. ## String interpolation @@ -83,22 +87,28 @@ formatting and calendar conversion still support that instant. Create `val timers = new sys.time.Timers();` on the game thread: +Timer IDs are `u32`, with `0` reserved for registration failure or no timer due. +Deadlines, delays, and periods remain `i64` milliseconds; their parameter, +field, and local names end in `_ms` to distinguish them from IDs. + | Operation | Meaning | | --- | --- | -| `after(delay)` | Register a one-shot timer; return its positive ID, or `-1`. | -| `every(period)` | Register a repeating timer; period must be positive. | -| `afterAt(current, delay)`, `everyAt(current, period)` | Supply a monotonic reading explicitly, useful for deterministic tests. | +| `after(delay_ms)` | Register a one-shot timer; return its positive `u32` ID, or `0`. | +| `every(period_ms)` | Register a repeating timer; period must be positive. Returns a `u32` ID, or `0`. | +| `afterAt(current_ms, delay_ms)`, `everyAt(current_ms, period_ms)` | Supply a monotonic reading explicitly, useful for deterministic tests. | | `cancel(id)` | Remove a timer; return whether it was present. | | `nextDeadline()` | Earliest monotonic deadline, or `-1` if empty. | -| `poll(current)` | Return the next due ID, or `-1` if none. | +| `poll(current_ms)` | Return the next due `u32` ID, or `0` if none. | Poll repeatedly to drain due timers. Deadlines order delivery, then registration ID breaks ties. Repeating timers advance from their previous deadline, skipping missed ticks to the first future deadline rather than drifting or replaying a backlog. Deadline overflow rejects registration or cancels a repeating timer. +IDs are never reused within a collection; registration fails once all nonzero +`u32` IDs have been issued. There are no callbacks or timer threads: the caller handles each returned ID. -`gfx.wait(timeoutMs)` processes events after waiting for input or a timeout: +`gfx.wait(timeout_ms)` processes events after waiting for input or a timeout: zero polls, `-1` waits indefinitely, values below `-1` fail. It returns the same `OK`, `CLOSED`, or `ERROR` statuses as `gfx.poll`, and may wake early. Re-read the monotonic clock after each wake. This allows one thread to handle timers, -- cgit v1.2.3