wip(0.S3): the runtime migrated - ludic migrate state --runtime <every program>: 331 vars into 25 states (RtInputState, RtGlState, ...), 2 lets; its states are made before it boots; no module-level var is let through outside --globals

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 16:02:12 +03:00
parent 7b17b4a1b9
commit 02448e176c
38 changed files with 53877 additions and 53251 deletions

View file

@ -27,21 +27,31 @@ const ATLAS_MAX_SHEET: int = 32
const ATLAS_MAX_SPR: int = 1024 const ATLAS_MAX_SPR: int = 1024
const ATLAS_MAX_NAME: int = 512 const ATLAS_MAX_NAME: int = 512
var at_sheet_img: words = null # image id per sheet export state RtAtlasState {
var at_sheet_cw: words = null # cell width per sheet at_sheet_img: words = null # image id per sheet
var at_sheet_ch: words = null # cell height per sheet at_sheet_cw: words = null # cell width per sheet
var at_nsheet: int = 0 at_sheet_ch: words = null # cell height per sheet
at_nsheet: int = 0
at_spr_sheet: words = null # owning sheet per atlas sprite
at_spr_sx: words = null # source x/y (px) of the sub-rect
at_spr_sy: words = null
at_spr_w: words = null # sub-rect width/height (px)
at_spr_h: words = null
at_nspr: int = 0
at_name_str: pointers = null # name per named sprite
at_name_id: words = null # atlas id per name
at_nname: int = 0
at_q_name: pointers = null # name to register the loaded sprite under
at_q_path: pointers = null # file path to load
at_q_n: int = 0 # how many enqueued
at_q_pos: int = 0 # how many loaded so far
at_q_announced: bool = false
at_font_name: pointers = null
at_font_id: words = null
at_font_n: int = 0
}
var at_spr_sheet: words = null # owning sheet per atlas sprite
var at_spr_sx: words = null # source x/y (px) of the sub-rect
var at_spr_sy: words = null
var at_spr_w: words = null # sub-rect width/height (px)
var at_spr_h: words = null
var at_nspr: int = 0
var at_name_str: pointers = null # name per named sprite
var at_name_id: words = null # atlas id per name
var at_nname: int = 0
# #82 — an incremental preload queue: enqueue named image files, then load a # #82 — an incremental preload queue: enqueue named image files, then load a
# bounded number per frame (Assets.pump) so a loading scene stays responsive and # bounded number per frame (Assets.pump) so a loading scene stays responsive and
@ -50,136 +60,132 @@ var at_nname: int = 0
import "audio.ludic" # the preload queue feeds .wav/.mp3 into the sound bank import "audio.ludic" # the preload queue feeds .wav/.mp3 into the sound bank
const ATLAS_MAX_QUEUE: int = 512 const ATLAS_MAX_QUEUE: int = 512
var at_q_name: pointers = null # name to register the loaded sprite under
var at_q_path: pointers = null # file path to load
var at_q_n: int = 0 # how many enqueued
var at_q_pos: int = 0 # how many loaded so far
function atlas_init() -> void { function atlas_init(rt_atlas_st: mut RtAtlasState) -> void {
if at_sheet_img != null { return } if rt_atlas_st.at_sheet_img != null { return }
at_sheet_img = words(ATLAS_MAX_SHEET) rt_atlas_st.at_sheet_img = words(ATLAS_MAX_SHEET)
at_sheet_cw = words(ATLAS_MAX_SHEET) rt_atlas_st.at_sheet_cw = words(ATLAS_MAX_SHEET)
at_sheet_ch = words(ATLAS_MAX_SHEET) rt_atlas_st.at_sheet_ch = words(ATLAS_MAX_SHEET)
at_spr_sheet = words(ATLAS_MAX_SPR) rt_atlas_st.at_spr_sheet = words(ATLAS_MAX_SPR)
at_spr_sx = words(ATLAS_MAX_SPR) rt_atlas_st.at_spr_sx = words(ATLAS_MAX_SPR)
at_spr_sy = words(ATLAS_MAX_SPR) rt_atlas_st.at_spr_sy = words(ATLAS_MAX_SPR)
at_spr_w = words(ATLAS_MAX_SPR) rt_atlas_st.at_spr_w = words(ATLAS_MAX_SPR)
at_spr_h = words(ATLAS_MAX_SPR) rt_atlas_st.at_spr_h = words(ATLAS_MAX_SPR)
at_name_str = pointers(ATLAS_MAX_NAME) # a pointer (8 bytes) per name slot rt_atlas_st.at_name_str = pointers(ATLAS_MAX_NAME) # a pointer (8 bytes) per name slot
at_name_id = words(ATLAS_MAX_NAME) rt_atlas_st.at_name_id = words(ATLAS_MAX_NAME)
at_q_name = pointers(ATLAS_MAX_QUEUE) rt_atlas_st.at_q_name = pointers(ATLAS_MAX_QUEUE)
at_q_path = pointers(ATLAS_MAX_QUEUE) rt_atlas_st.at_q_path = pointers(ATLAS_MAX_QUEUE)
} }
# register (name -> atlas id) in the name table so Assets.get / Sprite.named find it. # register (name -> atlas id) in the name table so Assets.get / Sprite.named find it.
function atlas_register_name(name: pointer, id: int) -> void { function atlas_register_name(rt_atlas_st: mut RtAtlasState, name: pointer, id: int) -> void {
atlas_init() atlas_init(rt_atlas_st)
if at_nname >= ATLAS_MAX_NAME { return } if rt_atlas_st.at_nname >= ATLAS_MAX_NAME { return }
at_name_str[at_nname] = name rt_atlas_st.at_name_str[rt_atlas_st.at_nname] = name
at_name_id[at_nname] = id rt_atlas_st.at_name_id[rt_atlas_st.at_nname] = id
at_nname += 1 rt_atlas_st.at_nname += 1
} }
# load a spritesheet PNG whose cells are cw x ch px; returns a sheet handle (>= 0). # load a spritesheet PNG whose cells are cw x ch px; returns a sheet handle (>= 0).
function atlas_sheet(path: pointer, cw: int, ch: int) -> int { function atlas_sheet(rt_atlas_st: mut RtAtlasState, rt_image_st: mut RtImageState, rt_inflate_st: mut RtInflateState, path: pointer, cw: int, ch: int) -> int {
atlas_init() atlas_init(rt_atlas_st)
if at_nsheet >= ATLAS_MAX_SHEET { return -1 } if rt_atlas_st.at_nsheet >= ATLAS_MAX_SHEET { return -1 }
let img = rt_image_load(path) let img = rt_image_load(rt_image_st, rt_inflate_st, path)
if img < 0 { return -1 } if img < 0 { return -1 }
let h = at_nsheet let h = rt_atlas_st.at_nsheet
at_sheet_img[h] = img rt_atlas_st.at_sheet_img[h] = img
at_sheet_cw[h] = cw rt_atlas_st.at_sheet_cw[h] = cw
at_sheet_ch[h] = ch rt_atlas_st.at_sheet_ch[h] = ch
at_nsheet += 1 rt_atlas_st.at_nsheet += 1
return h return h
} }
# register an atlas sprite for a sub-rect (px) of a sheet's image. # register an atlas sprite for a sub-rect (px) of a sheet's image.
function atlas_make(sheet: int, sx: int, sy: int, w: int, h: int) -> int { function atlas_make(rt_atlas_st: mut RtAtlasState, sheet: int, sx: int, sy: int, w: int, h: int) -> int {
atlas_init() atlas_init(rt_atlas_st)
if at_nspr >= ATLAS_MAX_SPR { return -1 } if rt_atlas_st.at_nspr >= ATLAS_MAX_SPR { return -1 }
let id = at_nspr let id = rt_atlas_st.at_nspr
at_spr_sheet[id] = sheet rt_atlas_st.at_spr_sheet[id] = sheet
at_spr_sx[id] = sx rt_atlas_st.at_spr_sx[id] = sx
at_spr_sy[id] = sy rt_atlas_st.at_spr_sy[id] = sy
at_spr_w[id] = w rt_atlas_st.at_spr_w[id] = w
at_spr_h[id] = h rt_atlas_st.at_spr_h[id] = h
at_nspr += 1 rt_atlas_st.at_nspr += 1
return id return id
} }
# one cell (col, row) of a sheet, addressed by grid coords. # one cell (col, row) of a sheet, addressed by grid coords.
function atlas_cell(sheet: int, col: int, row: int) -> int { function atlas_cell(rt_atlas_st: mut RtAtlasState, sheet: int, col: int, row: int) -> int {
if (sheet < 0) or (sheet >= at_nsheet) { return -1 } if (sheet < 0) or (sheet >= rt_atlas_st.at_nsheet) { return -1 }
let cw = at_sheet_cw[sheet] let cw = rt_atlas_st.at_sheet_cw[sheet]
let ch = at_sheet_ch[sheet] let ch = rt_atlas_st.at_sheet_ch[sheet]
return atlas_make(sheet, col * cw, row * ch, cw, ch) return atlas_make(rt_atlas_st, sheet, col * cw, row * ch, cw, ch)
} }
# a sprite spanning cols x rows cells from (col, row) — some sprites cover more # a sprite spanning cols x rows cells from (col, row) — some sprites cover more
# than one cell (a tall character, a wide object). # than one cell (a tall character, a wide object).
function atlas_cell_span(sheet: int, col: int, row: int, cols: int, rows: int) -> int { function atlas_cell_span(rt_atlas_st: mut RtAtlasState, sheet: int, col: int, row: int, cols: int, rows: int) -> int {
if (sheet < 0) or (sheet >= at_nsheet) { return -1 } if (sheet < 0) or (sheet >= rt_atlas_st.at_nsheet) { return -1 }
let cw = at_sheet_cw[sheet] let cw = rt_atlas_st.at_sheet_cw[sheet]
let ch = at_sheet_ch[sheet] let ch = rt_atlas_st.at_sheet_ch[sheet]
return atlas_make(sheet, col * cw, row * ch, cols * cw, rows * ch) return atlas_make(rt_atlas_st, sheet, col * cw, row * ch, cols * cw, rows * ch)
} }
# name a cell so a game can look it up by name; returns the atlas id. # name a cell so a game can look it up by name; returns the atlas id.
function atlas_define(name: pointer, sheet: int, col: int, row: int) -> int { function atlas_define(rt_atlas_st: mut RtAtlasState, name: pointer, sheet: int, col: int, row: int) -> int {
atlas_init() atlas_init(rt_atlas_st)
let id = atlas_cell(sheet, col, row) let id = atlas_cell(rt_atlas_st, sheet, col, row)
if id < 0 { return -1 } if id < 0 { return -1 }
if at_nname < ATLAS_MAX_NAME { if rt_atlas_st.at_nname < ATLAS_MAX_NAME {
at_name_str[at_nname] = name rt_atlas_st.at_name_str[rt_atlas_st.at_nname] = name
at_name_id[at_nname] = id rt_atlas_st.at_name_id[rt_atlas_st.at_nname] = id
at_nname += 1 rt_atlas_st.at_nname += 1
} }
return id return id
} }
# look up a named sprite's atlas id (names compare by content), or -1. # look up a named sprite's atlas id (names compare by content), or -1.
function atlas_named(name: pointer) -> int { function atlas_named(rt_atlas_st: mut RtAtlasState, name: pointer) -> int {
atlas_init() atlas_init(rt_atlas_st)
var i = 0 var i = 0
while i < at_nname { while i < rt_atlas_st.at_nname {
if at_name_str[i] == name { return at_name_id[i] } if rt_atlas_st.at_name_str[i] == name { return rt_atlas_st.at_name_id[i] }
i += 1 i += 1
} }
return -1 return -1
} }
# load a whole image file as one atlas sprite (a 1x1 sheet the size of the image). # load a whole image file as one atlas sprite (a 1x1 sheet the size of the image).
function atlas_image(path: pointer) -> int { function atlas_image(rt_atlas_st: mut RtAtlasState, rt_image_st: mut RtImageState, rt_inflate_st: mut RtInflateState, path: pointer) -> int {
atlas_init() atlas_init(rt_atlas_st)
if at_nsheet >= ATLAS_MAX_SHEET { return -1 } if rt_atlas_st.at_nsheet >= ATLAS_MAX_SHEET { return -1 }
let img = rt_image_load(path) let img = rt_image_load(rt_image_st, rt_inflate_st, path)
if img < 0 { return -1 } if img < 0 { return -1 }
let w = img_w[img] let w = rt_image_st.img_w[img]
let h = img_h[img] let h = rt_image_st.img_h[img]
let sh = at_nsheet let sh = rt_atlas_st.at_nsheet
at_sheet_img[sh] = img rt_atlas_st.at_sheet_img[sh] = img
at_sheet_cw[sh] = w rt_atlas_st.at_sheet_cw[sh] = w
at_sheet_ch[sh] = h rt_atlas_st.at_sheet_ch[sh] = h
at_nsheet += 1 rt_atlas_st.at_nsheet += 1
return atlas_make(sh, 0, 0, w, h) return atlas_make(rt_atlas_st, sh, 0, 0, w, h)
} }
# blit an atlas sprite at (dx, dy) through rt_put_px (camera / zoom / clip apply). # blit an atlas sprite at (dx, dy) through rt_put_px (camera / zoom / clip apply).
# Only sufficiently-opaque pixels are drawn (the sprite's transparent border is # Only sufficiently-opaque pixels are drawn (the sprite's transparent border is
# skipped), matching Screen.sprite. # skipped), matching Screen.sprite.
function atlas_draw(id: int, dx: int, dy: int) -> void { function atlas_draw(rt_atlas_st: mut RtAtlasState, rt_core_st: mut RtCoreState, rt_image_st: RtImageState, id: int, dx: int, dy: int) -> void {
atlas_init() atlas_init(rt_atlas_st)
if (id < 0) or (id >= at_nspr) { return } if (id < 0) or (id >= rt_atlas_st.at_nspr) { return }
let sheet = at_spr_sheet[id] let sheet = rt_atlas_st.at_spr_sheet[id]
let img = at_sheet_img[sheet] let img = rt_atlas_st.at_sheet_img[sheet]
let iw = img_w[img] let iw = rt_image_st.img_w[img]
let ih = img_h[img] let ih = rt_image_st.img_h[img]
let s: words = img_px[img] let s: words = rt_image_st.img_px[img]
let sx = at_spr_sx[id] let sx = rt_atlas_st.at_spr_sx[id]
let sy = at_spr_sy[id] let sy = rt_atlas_st.at_spr_sy[id]
let w = at_spr_w[id] let w = rt_atlas_st.at_spr_w[id]
let h = at_spr_h[id] let h = rt_atlas_st.at_spr_h[id]
var y = 0 var y = 0
while y < h { while y < h {
let srcy = sy + y let srcy = sy + y
@ -189,7 +195,7 @@ function atlas_draw(id: int, dx: int, dy: int) -> void {
let srcx = sx + x let srcx = sx + x
if (srcx >= 0) and (srcx < iw) { if (srcx >= 0) and (srcx < iw) {
let px = s[srcy * iw + srcx] let px = s[srcy * iw + srcx]
if ((px >> 24) & 255) >= 128 { rt_put_px(dx + x, dy + y, px & 16777215) } if ((px >> 24) & 255) >= 128 { rt_put_px(rt_core_st, dx + x, dy + y, px & 16777215) }
} }
x += 1 x += 1
} }
@ -199,19 +205,19 @@ function atlas_draw(id: int, dx: int, dy: int) -> void {
} }
# blit an atlas sprite scaled by an integer factor (through rt_fill_rect blocks). # blit an atlas sprite scaled by an integer factor (through rt_fill_rect blocks).
function atlas_draw_scaled(id: int, dx: int, dy: int, sc: int) -> void { function atlas_draw_scaled(rt_atlas_st: mut RtAtlasState, rt_core_st: mut RtCoreState, rt_image_st: RtImageState, id: int, dx: int, dy: int, sc: int) -> void {
atlas_init() atlas_init(rt_atlas_st)
if (id < 0) or (id >= at_nspr) { return } if (id < 0) or (id >= rt_atlas_st.at_nspr) { return }
if sc < 1 { return } if sc < 1 { return }
let sheet = at_spr_sheet[id] let sheet = rt_atlas_st.at_spr_sheet[id]
let img = at_sheet_img[sheet] let img = rt_atlas_st.at_sheet_img[sheet]
let iw = img_w[img] let iw = rt_image_st.img_w[img]
let ih = img_h[img] let ih = rt_image_st.img_h[img]
let s: words = img_px[img] let s: words = rt_image_st.img_px[img]
let sx = at_spr_sx[id] let sx = rt_atlas_st.at_spr_sx[id]
let sy = at_spr_sy[id] let sy = rt_atlas_st.at_spr_sy[id]
let w = at_spr_w[id] let w = rt_atlas_st.at_spr_w[id]
let h = at_spr_h[id] let h = rt_atlas_st.at_spr_h[id]
var y = 0 var y = 0
while y < h { while y < h {
let srcy = sy + y let srcy = sy + y
@ -221,7 +227,7 @@ function atlas_draw_scaled(id: int, dx: int, dy: int, sc: int) -> void {
let srcx = sx + x let srcx = sx + x
if (srcx >= 0) and (srcx < iw) { if (srcx >= 0) and (srcx < iw) {
let px = s[srcy * iw + srcx] let px = s[srcy * iw + srcx]
if ((px >> 24) & 255) >= 128 { rt_fill_rect(dx + x * sc, dy + y * sc, sc, sc, px & 16777215) } if ((px >> 24) & 255) >= 128 { rt_fill_rect(rt_core_st, dx + x * sc, dy + y * sc, sc, sc, px & 16777215) }
} }
x += 1 x += 1
} }
@ -233,20 +239,20 @@ function atlas_draw_scaled(id: int, dx: int, dy: int, sc: int) -> void {
# blit an atlas sprite with the full Sprite-component treatment (#90): integer scale, # blit an atlas sprite with the full Sprite-component treatment (#90): integer scale,
# horizontal flip, and a solid tint (non-zero = every opaque pixel in that colour). # horizontal flip, and a solid tint (non-zero = every opaque pixel in that colour).
# This is what the engine sprite-render system calls for `Sprite { atlas: 1 }`. # This is what the engine sprite-render system calls for `Sprite { atlas: 1 }`.
function atlas_draw_ex(id: int, dx: int, dy: int, sc0: int, flip: int, tint: int) -> void { function atlas_draw_ex(rt_atlas_st: mut RtAtlasState, rt_core_st: mut RtCoreState, rt_image_st: RtImageState, id: int, dx: int, dy: int, sc0: int, flip: int, tint: int) -> void {
atlas_init() atlas_init(rt_atlas_st)
if (id < 0) or (id >= at_nspr) { return } if (id < 0) or (id >= rt_atlas_st.at_nspr) { return }
var sc = sc0 var sc = sc0
if sc < 1 { sc = 1 } if sc < 1 { sc = 1 }
let sheet = at_spr_sheet[id] let sheet = rt_atlas_st.at_spr_sheet[id]
let img = at_sheet_img[sheet] let img = rt_atlas_st.at_sheet_img[sheet]
let iw = img_w[img] let iw = rt_image_st.img_w[img]
let ih = img_h[img] let ih = rt_image_st.img_h[img]
let s: words = img_px[img] let s: words = rt_image_st.img_px[img]
let sx = at_spr_sx[id] let sx = rt_atlas_st.at_spr_sx[id]
let sy = at_spr_sy[id] let sy = rt_atlas_st.at_spr_sy[id]
let w = at_spr_w[id] let w = rt_atlas_st.at_spr_w[id]
let h = at_spr_h[id] let h = rt_atlas_st.at_spr_h[id]
var y = 0 var y = 0
while y < h { while y < h {
let srcy = sy + y let srcy = sy + y
@ -261,8 +267,8 @@ function atlas_draw_ex(id: int, dx: int, dy: int, sc0: int, flip: int, tint: int
if ((px >> 24) & 255) >= 128 { if ((px >> 24) & 255) >= 128 {
var col = px & 16777215 var col = px & 16777215
if tint != 0 { col = tint & 16777215 } if tint != 0 { col = tint & 16777215 }
if sc == 1 { rt_put_px(dx + x, dy + y, col) } if sc == 1 { rt_put_px(rt_core_st, dx + x, dy + y, col) }
else { rt_fill_rect(dx + x * sc, dy + y * sc, sc, sc, col) } else { rt_fill_rect(rt_core_st, dx + x * sc, dy + y * sc, sc, sc, col) }
} }
} }
x += 1 x += 1
@ -274,11 +280,11 @@ function atlas_draw_ex(id: int, dx: int, dy: int, sc0: int, flip: int, tint: int
# a strip of `count` frames starting at (col,row), each `rows` cells tall, registered # a strip of `count` frames starting at (col,row), each `rows` cells tall, registered
# as consecutive ids so `first + SpriteAnim.frame` addresses the current frame. # as consecutive ids so `first + SpriteAnim.frame` addresses the current frame.
function atlas_strip(sheet: int, col: int, row: int, count: int, rows: int) -> int { function atlas_strip(rt_atlas_st: mut RtAtlasState, sheet: int, col: int, row: int, count: int, rows: int) -> int {
var first = -1 var first = -1
var i = 0 var i = 0
while i < count { while i < count {
let id = atlas_cell_span(sheet, col + i, row, 1, rows) let id = atlas_cell_span(rt_atlas_st, sheet, col + i, row, 1, rows)
if first < 0 { first = id } if first < 0 { first = id }
i += 1 i += 1
} }
@ -286,26 +292,26 @@ function atlas_strip(sheet: int, col: int, row: int, count: int, rows: int) -> i
} }
# an atlas sprite's pixel size — handy for centering / layout. # an atlas sprite's pixel size — handy for centering / layout.
function atlas_width(id: int) -> int { atlas_init(); if (id < 0) or (id >= at_nspr) { return 0 }; return at_spr_w[id] } function atlas_width(rt_atlas_st: mut RtAtlasState, id: int) -> int { atlas_init(rt_atlas_st); if (id < 0) or (id >= rt_atlas_st.at_nspr) { return 0 }; return rt_atlas_st.at_spr_w[id] }
function atlas_height(id: int) -> int { atlas_init(); if (id < 0) or (id >= at_nspr) { return 0 }; return at_spr_h[id] } function atlas_height(rt_atlas_st: mut RtAtlasState, id: int) -> int { atlas_init(rt_atlas_st); if (id < 0) or (id >= rt_atlas_st.at_nspr) { return 0 }; return rt_atlas_st.at_spr_h[id] }
# Sprite.draw_meter: `value` of `max` as a row of icons — full, half or empty per # Sprite.draw_meter: `value` of `max` as a row of icons — full, half or empty per
# `per_icon` points (hearts, stars, ammo pips), `spacing` px apart # `per_icon` points (hearts, stars, ammo pips), `spacing` px apart
function atlas_draw_meter(x: int, y: int, value: int, max: int, per_icon: int, spacing: int, full: int, half: int, empty: int) -> void { function atlas_draw_meter(rt_atlas_st: mut RtAtlasState, rt_core_st: mut RtCoreState, rt_image_st: RtImageState, x: int, y: int, value: int, max: int, per_icon: int, spacing: int, full: int, half: int, empty: int) -> void {
if per_icon <= 0 { return } if per_icon <= 0 { return }
var i = 0 var i = 0
while i < max / per_icon { while i < max / per_icon {
var id = empty var id = empty
if value >= i * per_icon + per_icon / 2 { id = half } if value >= i * per_icon + per_icon / 2 { id = half }
if value >= (i + 1) * per_icon { id = full } if value >= (i + 1) * per_icon { id = full }
atlas_draw(id, x + i * spacing, y) atlas_draw(rt_atlas_st, rt_core_st, rt_image_st, id, x + i * spacing, y)
i += 1 i += 1
} }
} }
# Assets.enqueue_dir: every file of a directory, named by its file name without the # Assets.enqueue_dir: every file of a directory, named by its file name without the
# extension (assets/audio/hit.wav -> "hit"); the queue sorts by extension as usual # extension (assets/audio/hit.wav -> "hit"); the queue sorts by extension as usual
function assets_enqueue_dir(dir: pointer) -> void { function assets_enqueue_dir(rt_atlas_st: mut RtAtlasState, dir: pointer) -> void {
let names = Fs.list(dir) let names = Fs.list(dir)
if names == null { return } if names == null { return }
var i = 0 var i = 0
@ -313,44 +319,43 @@ function assets_enqueue_dir(dir: pointer) -> void {
let file = names[i] let file = names[i]
var dot = len(file) - 1 var dot = len(file) - 1
while (dot > 0) and (file[dot] != '.') { dot -= 1 } while (dot > 0) and (file[dot] != '.') { dot -= 1 }
if dot > 0 { assets_enqueue(file[0..dot], dir + ("/") + file) } if dot > 0 { assets_enqueue(rt_atlas_st, file[0..dot], dir + ("/") + file) }
i += 1 i += 1
} }
} }
# ---- incremental preload (#82) -------------------------------------------- # ---- incremental preload (#82) --------------------------------------------
# Enqueue a named image file to load later (does not load it now). # Enqueue a named image file to load later (does not load it now).
function assets_enqueue(name: pointer, path: pointer) -> void { function assets_enqueue(rt_atlas_st: mut RtAtlasState, name: pointer, path: pointer) -> void {
atlas_init() atlas_init(rt_atlas_st)
if at_q_n >= ATLAS_MAX_QUEUE { return } if rt_atlas_st.at_q_n >= ATLAS_MAX_QUEUE { return }
at_q_name[at_q_n] = name rt_atlas_st.at_q_name[rt_atlas_st.at_q_n] = name
at_q_path[at_q_n] = path rt_atlas_st.at_q_path[rt_atlas_st.at_q_n] = path
at_q_n += 1 rt_atlas_st.at_q_n += 1
} }
# Load up to `max` queued assets this frame, registering each under its name, and # Load up to `max` queued assets this frame, registering each under its name, and
# return how many were loaded by this call. Call it each frame in a loading scene # return how many were loaded by this call. Call it each frame in a loading scene
# (a small `max` keeps the frame short); Assets.ready() flips true when done. # (a small `max` keeps the frame short); Assets.ready() flips true when done.
event AssetsReady { } # fired once, the frame the queue finishes event AssetsReady { } # fired once, the frame the queue finishes
var at_q_announced: bool = false function assets_pump(rt_atlas_st: mut RtAtlasState, rt_audio_st: mut RtAudioState, rt_image_st: mut RtImageState, rt_inflate_st: mut RtInflateState, rt_truetype_st: mut RtTruetypeState, max: int) -> int {
function assets_pump(max: int) -> int { atlas_init(rt_atlas_st)
atlas_init()
var done = 0 var done = 0
while (done < max) and (at_q_pos < at_q_n) { while (done < max) and (rt_atlas_st.at_q_pos < rt_atlas_st.at_q_n) {
assets_load_one(at_q_name[at_q_pos], at_q_path[at_q_pos]) assets_load_one(rt_atlas_st, rt_audio_st, rt_image_st, rt_inflate_st, rt_truetype_st, rt_atlas_st.at_q_name[rt_atlas_st.at_q_pos], rt_atlas_st.at_q_path[rt_atlas_st.at_q_pos])
at_q_pos += 1 rt_atlas_st.at_q_pos += 1
done += 1 done += 1
} }
if (at_q_pos >= at_q_n) and (not at_q_announced) { at_q_announced = true; emit AssetsReady() } if (rt_atlas_st.at_q_pos >= rt_atlas_st.at_q_n) and (not rt_atlas_st.at_q_announced) { rt_atlas_st.at_q_announced = true; emit AssetsReady() }
return done return done
} }
# the default loading bar of a `scene X loads then Y`: centred, a fifth of the screen wide # the default loading bar of a `scene X loads then Y`: centred, a fifth of the screen wide
function assets_draw_progress() -> void { function assets_draw_progress(rt_atlas_st: mut RtAtlasState, rt_core_st: mut RtCoreState) -> void {
let w = rt_fbw / 2 let w = rt_fbw / 2
let x = rt_fbw / 4 let x = rt_fbw / 4
let y = rt_fbh / 2 - 4 let y = rt_fbh / 2 - 4
rt_fill_rect(x, y, w, 8, 0x303030) rt_fill_rect(rt_core_st, x, y, w, 8, 0x303030)
rt_fill_rect(x, y, w * assets_progress() / 100, 8, 0xffcc44) rt_fill_rect(rt_core_st, x, y, w * assets_progress(rt_atlas_st) / 100, 8, 0xffcc44)
} }
# what kind of asset a path is, by its extension: sounds and fonts go to their own # what kind of asset a path is, by its extension: sounds and fonts go to their own
@ -367,42 +372,39 @@ function path_has_suffix(path: pointer, suffix: pointer) -> bool {
return true return true
} }
const ATLAS_MAX_FONT: int = 16 const ATLAS_MAX_FONT: int = 16
var at_font_name: pointers = null function assets_load_one(rt_atlas_st: mut RtAtlasState, rt_audio_st: mut RtAudioState, rt_image_st: mut RtImageState, rt_inflate_st: mut RtInflateState, rt_truetype_st: mut RtTruetypeState, name: pointer, path: pointer) -> void {
var at_font_id: words = null
var at_font_n: int = 0
function assets_load_one(name: pointer, path: pointer) -> void {
if path_has_suffix(path, ".wav") or path_has_suffix(path, ".mp3") or path_has_suffix(path, ".ogg") { if path_has_suffix(path, ".wav") or path_has_suffix(path, ".mp3") or path_has_suffix(path, ".ogg") {
audio_define(name, path) audio_define(rt_audio_st, name, path)
return return
} }
if path_has_suffix(path, ".ttf") or path_has_suffix(path, ".ttc") { if path_has_suffix(path, ".ttf") or path_has_suffix(path, ".ttc") {
if at_font_name == null { at_font_name = pointers(ATLAS_MAX_FONT); at_font_id = words(ATLAS_MAX_FONT) } if rt_atlas_st.at_font_name == null { rt_atlas_st.at_font_name = pointers(ATLAS_MAX_FONT); rt_atlas_st.at_font_id = words(ATLAS_MAX_FONT) }
if at_font_n < ATLAS_MAX_FONT { if rt_atlas_st.at_font_n < ATLAS_MAX_FONT {
at_font_name[at_font_n] = name rt_atlas_st.at_font_name[rt_atlas_st.at_font_n] = name
at_font_id[at_font_n] = rt_font_load(path) rt_atlas_st.at_font_id[rt_atlas_st.at_font_n] = rt_font_load(rt_image_st, rt_truetype_st, path)
at_font_n += 1 rt_atlas_st.at_font_n += 1
} }
return return
} }
atlas_register_name(name, atlas_image(path)) atlas_register_name(rt_atlas_st, name, atlas_image(rt_atlas_st, rt_image_st, rt_inflate_st, path))
} }
# the font handle registered under `name` (Assets.font), or 0 # the font handle registered under `name` (Assets.font), or 0
function assets_font(name: pointer) -> int { function assets_font(rt_atlas_st: RtAtlasState, name: pointer) -> int {
var i = 0 var i = 0
while i < at_font_n { while i < rt_atlas_st.at_font_n {
if at_font_name[i] == name { return at_font_id[i] } if rt_atlas_st.at_font_name[i] == name { return rt_atlas_st.at_font_id[i] }
i += 1 i += 1
} }
return 0 return 0
} }
function assets_total() -> int { atlas_init(); return at_q_n } function assets_total(rt_atlas_st: mut RtAtlasState) -> int { atlas_init(rt_atlas_st); return rt_atlas_st.at_q_n }
function assets_loaded() -> int { atlas_init(); return at_q_pos } function assets_loaded(rt_atlas_st: mut RtAtlasState) -> int { atlas_init(rt_atlas_st); return rt_atlas_st.at_q_pos }
function assets_ready() -> bool { atlas_init(); return at_q_pos >= at_q_n } function assets_ready(rt_atlas_st: mut RtAtlasState) -> bool { atlas_init(rt_atlas_st); return rt_atlas_st.at_q_pos >= rt_atlas_st.at_q_n }
# loading progress as a whole-number percent (0..100); an empty queue is 100. # loading progress as a whole-number percent (0..100); an empty queue is 100.
function assets_progress() -> int { function assets_progress(rt_atlas_st: mut RtAtlasState) -> int {
atlas_init() atlas_init(rt_atlas_st)
if at_q_n <= 0 { return 100 } if rt_atlas_st.at_q_n <= 0 { return 100 }
return at_q_pos * 100 / at_q_n return rt_atlas_st.at_q_pos * 100 / rt_atlas_st.at_q_n
} }

View file

@ -19,67 +19,69 @@
const AUDIO_CAP: int = 64 # max simultaneously-loaded sounds const AUDIO_CAP: int = 64 # max simultaneously-loaded sounds
var snd_ready: bool = false export state RtAudioState {
var snd_tab: pointers = null # handle (1-based) -> AVAudioPlayer pointer snd_ready: bool = false
var snd_master: fixed = 1.0 # master volume, applied to every play (and to audio_play_at's gain) snd_tab: pointers = null # handle (1-based) -> AVAudioPlayer pointer
var snd_rate: fixed = 1.0 # playback rate / pitch (1.0 = normal) snd_master: fixed = 1.0 # master volume, applied to every play (and to audio_play_at's gain)
var snd_music: int = 0 # the handle currently playing as music (0 = none) snd_rate: fixed = 1.0 # playback rate / pitch (1.0 = normal)
snd_music: int = 0 # the handle currently playing as music (0 = none)
snd_bank_name: pointers = null
snd_bank_id: words = null
snd_bank_n: int = 0
}
# the sound bank: sounds registered by name (Audio.define), played by name (Audio.play(name:)) # the sound bank: sounds registered by name (Audio.define), played by name (Audio.play(name:))
var snd_bank_name: pointers = null
var snd_bank_id: words = null
var snd_bank_n: int = 0
function audio_init() -> void { function audio_init(rt_audio_st: mut RtAudioState) -> void {
if snd_ready { return } if rt_audio_st.snd_ready { return }
snd_tab = pointers(AUDIO_CAP) # one 8-byte pointer slot per handle rt_audio_st.snd_tab = pointers(AUDIO_CAP) # one 8-byte pointer slot per handle
fill(snd_tab, 0, AUDIO_CAP * 8) # malloc does not zero; empty slots must read null fill(rt_audio_st.snd_tab, 0, AUDIO_CAP * 8) # malloc does not zero; empty slots must read null
snd_bank_name = pointers(AUDIO_CAP) rt_audio_st.snd_bank_name = pointers(AUDIO_CAP)
snd_bank_id = words(AUDIO_CAP) rt_audio_st.snd_bank_id = words(AUDIO_CAP)
snd_ready = true rt_audio_st.snd_ready = true
} }
# ---- the sound bank: Audio.define(name, path) then Audio.play(name: "…") ------ # ---- the sound bank: Audio.define(name, path) then Audio.play(name: "…") ------
# Load a sound and remember it under a name; returns the handle (0 headless). # Load a sound and remember it under a name; returns the handle (0 headless).
function audio_define(name: pointer, path: pointer) -> int { function audio_define(rt_audio_st: mut RtAudioState, name: pointer, path: pointer) -> int {
audio_init() audio_init(rt_audio_st)
let id = audio_load(path) let id = audio_load(rt_audio_st, path)
if snd_bank_n < AUDIO_CAP { if rt_audio_st.snd_bank_n < AUDIO_CAP {
snd_bank_name[snd_bank_n] = name rt_audio_st.snd_bank_name[rt_audio_st.snd_bank_n] = name
snd_bank_id[snd_bank_n] = id rt_audio_st.snd_bank_id[rt_audio_st.snd_bank_n] = id
snd_bank_n += 1 rt_audio_st.snd_bank_n += 1
} }
return id return id
} }
# the handle registered under `name`, or 0 # the handle registered under `name`, or 0
function audio_named(name: pointer) -> int { function audio_named(rt_audio_st: mut RtAudioState, name: pointer) -> int {
audio_init() audio_init(rt_audio_st)
var i = 0 var i = 0
while i < snd_bank_n { while i < rt_audio_st.snd_bank_n {
if snd_bank_name[i] == name { return snd_bank_id[i] } if rt_audio_st.snd_bank_name[i] == name { return rt_audio_st.snd_bank_id[i] }
i += 1 i += 1
} }
return 0 return 0
} }
function audio_play_named(name: pointer) -> void { audio_play(audio_named(name)) } function audio_play_named(rt_audio_st: mut RtAudioState, name: pointer) -> void { audio_play(rt_audio_st, audio_named(rt_audio_st, name)) }
function audio_play_music_named(name: pointer) -> void { audio_play_music(audio_named(name)) } function audio_play_music_named(rt_audio_st: mut RtAudioState, name: pointer) -> void { audio_play_music(rt_audio_st, audio_named(rt_audio_st, name)) }
# Resolve a 1-based handle to its player pointer (null if out of range / empty). # Resolve a 1-based handle to its player pointer (null if out of range / empty).
function audio_get(id: int) -> pointer { function audio_get(rt_audio_st: mut RtAudioState, id: int) -> pointer {
audio_init() audio_init(rt_audio_st)
if (id < 1) or (id > AUDIO_CAP) { return null } if (id < 1) or (id > AUDIO_CAP) { return null }
return snd_tab[id - 1] return rt_audio_st.snd_tab[id - 1]
} }
# Load a sound file and return its handle (>= 1), or 0 on failure / headless. # Load a sound file and return its handle (>= 1), or 0 on failure / headless.
function audio_load(path: pointer) -> int { function audio_load(rt_audio_st: mut RtAudioState, path: pointer) -> int {
audio_init() audio_init(rt_audio_st)
if not is_windowed() { return 0 } if not is_windowed() { return 0 }
var i = 0 var i = 0
while i < AUDIO_CAP { while i < AUDIO_CAP {
if snd_tab[i] == null { if rt_audio_st.snd_tab[i] == null {
let p = snd_load(path) let p = snd_load(path)
if p == null { return 0 } if p == null { return 0 }
snd_tab[i] = p rt_audio_st.snd_tab[i] = p
return i + 1 return i + 1
} }
i += 1 i += 1
@ -88,12 +90,12 @@ function audio_load(path: pointer) -> int {
} }
# Fire a one-shot sound from the start. # Fire a one-shot sound from the start.
function audio_play(id: int) -> void { function audio_play(rt_audio_st: mut RtAudioState, id: int) -> void {
if not is_windowed() { return } if not is_windowed() { return }
let p = audio_get(id) let p = audio_get(rt_audio_st, id)
if p == null { return } if p == null { return }
snd_set_pan(p, 0.0) snd_set_pan(p, 0.0)
snd_play(p, 0, snd_rate, snd_master) snd_play(p, 0, rt_audio_st.snd_rate, rt_audio_st.snd_master)
} }
# Fire a one-shot with its OWN gain, pitch and stereo position, leaving the master # Fire a one-shot with its OWN gain, pitch and stereo position, leaving the master
@ -111,9 +113,9 @@ function audio_play(id: int) -> void {
# The one honest limitation: a sound is one player, so firing the same handle again # The one honest limitation: a sound is one player, so firing the same handle again
# restarts it rather than layering a second copy. Load a handle per variant when several # restarts it rather than layering a second copy. Load a handle per variant when several
# need to overlap - which is what a game does anyway to stop a repeated sound machine-gunning. # need to overlap - which is what a game does anyway to stop a repeated sound machine-gunning.
function audio_play_at(id: int, gain: fixed, pitch: fixed, pan: fixed) -> void { function audio_play_at(rt_audio_st: mut RtAudioState, id: int, gain: fixed, pitch: fixed, pan: fixed) -> void {
if not is_windowed() { return } if not is_windowed() { return }
let p = audio_get(id) let p = audio_get(rt_audio_st, id)
if p == null { return } if p == null { return }
var g = gain var g = gain
if g < 0.0 { g = 0.0 } if g < 0.0 { g = 0.0 }
@ -125,80 +127,80 @@ function audio_play_at(id: int, gain: fixed, pitch: fixed, pan: fixed) -> void {
if pt < 0.25 { pt = 0.25 } # AVAudioPlayer's own rate range if pt < 0.25 { pt = 0.25 } # AVAudioPlayer's own rate range
if pt > 4.0 { pt = 4.0 } if pt > 4.0 { pt = 4.0 }
snd_set_pan(p, pn) snd_set_pan(p, pn)
snd_play(p, 0, pt, g * snd_master) snd_play(p, 0, pt, g * rt_audio_st.snd_master)
} }
# Play a sound as looping background music on the single music channel; any # Play a sound as looping background music on the single music channel; any
# previous music is stopped first. # previous music is stopped first.
function audio_play_music(id: int) -> void { function audio_play_music(rt_audio_st: mut RtAudioState, id: int) -> void {
if not is_windowed() { return } if not is_windowed() { return }
let p = audio_get(id) let p = audio_get(rt_audio_st, id)
if p == null { return } if p == null { return }
audio_stop_music() audio_stop_music(rt_audio_st)
snd_music = id rt_audio_st.snd_music = id
snd_play(p, -1, snd_rate, snd_master) snd_play(p, -1, rt_audio_st.snd_rate, rt_audio_st.snd_master)
} }
# Stop one sound. # Stop one sound.
function audio_stop(id: int) -> void { function audio_stop(rt_audio_st: mut RtAudioState, id: int) -> void {
if not is_windowed() { return } if not is_windowed() { return }
let p = audio_get(id) let p = audio_get(rt_audio_st, id)
if p == null { return } if p == null { return }
snd_stop(p) snd_stop(p)
if id == snd_music { snd_music = 0 } if id == rt_audio_st.snd_music { rt_audio_st.snd_music = 0 }
} }
# Stop the current music channel. # Stop the current music channel.
function audio_stop_music() -> void { function audio_stop_music(rt_audio_st: mut RtAudioState) -> void {
if not is_windowed() { return } if not is_windowed() { return }
if snd_music != 0 { if rt_audio_st.snd_music != 0 {
let p = audio_get(snd_music) let p = audio_get(rt_audio_st, rt_audio_st.snd_music)
if p != null { snd_stop(p) } if p != null { snd_stop(p) }
snd_music = 0 rt_audio_st.snd_music = 0
} }
} }
# Stop every loaded sound. # Stop every loaded sound.
function audio_stop_all() -> void { function audio_stop_all(rt_audio_st: mut RtAudioState) -> void {
if not is_windowed() { return } if not is_windowed() { return }
audio_init() audio_init(rt_audio_st)
var i = 0 var i = 0
while i < AUDIO_CAP { while i < AUDIO_CAP {
if snd_tab[i] != null { snd_stop(snd_tab[i]) } if rt_audio_st.snd_tab[i] != null { snd_stop(rt_audio_st.snd_tab[i]) }
i += 1 i += 1
} }
snd_music = 0 rt_audio_st.snd_music = 0
} }
# Master volume (0.0 .. 1.0) — applied to every currently-loaded sound now and # Master volume (0.0 .. 1.0) — applied to every currently-loaded sound now and
# to every future play. # to every future play.
function audio_volume(v: fixed) -> void { function audio_volume(rt_audio_st: mut RtAudioState, v: fixed) -> void {
snd_master = v rt_audio_st.snd_master = v
if not is_windowed() { return } if not is_windowed() { return }
audio_init() audio_init(rt_audio_st)
var i = 0 var i = 0
while i < AUDIO_CAP { while i < AUDIO_CAP {
if snd_tab[i] != null { snd_set_volume(snd_tab[i], v) } if rt_audio_st.snd_tab[i] != null { snd_set_volume(rt_audio_st.snd_tab[i], v) }
i += 1 i += 1
} }
} }
# Playback rate / pitch (1.0 = normal, 0.5 = an octave down, 2.0 = up). # Playback rate / pitch (1.0 = normal, 0.5 = an octave down, 2.0 = up).
function audio_pitch(v: fixed) -> void { function audio_pitch(rt_audio_st: mut RtAudioState, v: fixed) -> void {
snd_rate = v rt_audio_st.snd_rate = v
if not is_windowed() { return } if not is_windowed() { return }
audio_init() audio_init(rt_audio_st)
var i = 0 var i = 0
while i < AUDIO_CAP { while i < AUDIO_CAP {
if snd_tab[i] != null { snd_set_rate(snd_tab[i], v) } if rt_audio_st.snd_tab[i] != null { snd_set_rate(rt_audio_st.snd_tab[i], v) }
i += 1 i += 1
} }
} }
# Is this sound currently playing? # Is this sound currently playing?
function audio_is_playing(id: int) -> bool { function audio_is_playing(rt_audio_st: mut RtAudioState, id: int) -> bool {
if not is_windowed() { return false } if not is_windowed() { return false }
let p = audio_get(id) let p = audio_get(rt_audio_st, id)
if p == null { return false } if p == null { return false }
return snd_playing(p) != 0 return snd_playing(p) != 0
} }

View file

@ -23,11 +23,33 @@
# where a headless build leaves its last frame (relative to the working directory) # where a headless build leaves its last frame (relative to the working directory)
const HEADLESS_FRAME_PATH: string = "build/out.ppm" const HEADLESS_FRAME_PATH: string = "build/out.ppm"
var rt_fb: words = null # framebuffer, one i32 (0x00RRGGBB) per pixel export state RtCoreState {
var rt_fbw: int = 320 rt_fb: words = null # framebuffer, one i32 (0x00RRGGBB) per pixel
var rt_fbh: int = 240 rt_regs: words = null # the 64 general-purpose game registers
var rt_regs: words = null # the 64 general-purpose game registers rt_alive: int = 1 # platform still running?
var rt_alive: int = 1 # platform still running? rt_cam_x: int = 0 # camera base offset (world -> screen: subtracted)
rt_cam_y: int = 0
rt_shake_x: int = 0 # transient screen-shake offset, added to the base
rt_shake_y: int = 0
rt_clip_x0: int = 0 # clip rectangle in screen space (half-open)
rt_clip_y0: int = 0
rt_clip_x1: int = 320
rt_clip_y1: int = 240
rt_blend: int = 0 # 0 = replace, 1 = additive
rt_cam_zoom: fixed = 1.0 # 1.0 = no zoom; >1 zooms in, <1 zooms out
rt_cam_zoomed: bool = false # true once a non-1.0 zoom is set
rt_shake_amount: int = 0
rt_shake_left: int = 0
rt_map: pointer = null
rt_mapw: int = 0
rt_maph: int = 0
rt_map_solid1: int = 0
rt_map_solid2: int = 0
rt_map_tile_px: int = 16
rt_statusbuf: pointer = null
}
let rt_fbw: int = 320
let rt_fbh: int = 240
# ---- renderer state (camera / clip / blend) ------------------------------- # ---- renderer state (camera / clip / blend) -------------------------------
# A world-space camera offset, a clip rectangle, and a blend mode threaded # A world-space camera offset, a clip rectangle, and a blend mode threaded
@ -36,23 +58,12 @@ var rt_alive: int = 1 # platform still running?
# The defaults are neutral — camera (0,0), clip = full screen, blend = replace — # The defaults are neutral — camera (0,0), clip = full screen, blend = replace —
# so a game that never touches them renders exactly as before. The camera moves # so a game that never touches them renders exactly as before. The camera moves
# everything drawn; reset it to (0,0) to draw a fixed HUD over the world. # everything drawn; reset it to (0,0) to draw a fixed HUD over the world.
var rt_cam_x: int = 0 # camera base offset (world -> screen: subtracted)
var rt_cam_y: int = 0
var rt_shake_x: int = 0 # transient screen-shake offset, added to the base
var rt_shake_y: int = 0
var rt_clip_x0: int = 0 # clip rectangle in screen space (half-open)
var rt_clip_y0: int = 0
var rt_clip_x1: int = 320
var rt_clip_y1: int = 240
var rt_blend: int = 0 # 0 = replace, 1 = additive
# #78 — deterministic camera zoom. A Q16.16 scale applied about the screen centre # #78 — deterministic camera zoom. A Q16.16 scale applied about the screen centre
# in the same two chokepoints as the camera offset. Rejected floats for the # in the same two chokepoints as the camera offset. Rejected floats for the
# coordinate types (they would desync lockstep/replay/save); zoom is a *render-time* # coordinate types (they would desync lockstep/replay/save); zoom is a *render-time*
# transform, so it rides on fixed-point exactly like sprite-scale and the light math. # transform, so it rides on fixed-point exactly like sprite-scale and the light math.
# rt_cam_zoomed gates the fixed multiply out of the hot path so a game that never # rt_cam_zoomed gates the fixed multiply out of the hot path so a game that never
# zooms renders byte-for-byte identically (the else-branch is the original code). # zooms renders byte-for-byte identically (the else-branch is the original code).
var rt_cam_zoom: fixed = 1.0 # 1.0 = no zoom; >1 zooms in, <1 zooms out
var rt_cam_zoomed: bool = false # true once a non-1.0 zoom is set
# 5x7 glyphs for ASCII 32..90, 7 rows per glyph, each row a 5-bit mask stored # 5x7 glyphs for ASCII 32..90, 7 rows per glyph, each row a 5-bit mask stored
# biased by '0' so the whole font is one printable string literal. # biased by '0' so the whole font is one printable string literal.
@ -60,42 +71,42 @@ function rt_font() -> string {
return "00000004444404000000000000000000000IJ4:FC000000000000000000000000000000E>O>E0044O4400000448000O000000004012448@@>ACEIA>4<4444>>A168@ON11>11N26:BO22O@N11A>>@@NAA>O124888>AA>AA>>AA?11>04004000000000024842000O0O000842480>A164040000000>AAOAAANAANAAN>A@@@A>LBAAABLO@@L@@OO@@L@@@>A@GAA>AAAOAAA>44444>7222BB<ABDHDBA@@@@@@OAKEEAAAAIEECAA>AAAAA>NAAN@@@>AAAEB=NAANDBA>A@>1A>O444444AAAAAA>AAAAA:4AAAEEKAAA:4:AAAA:4444O1248@O" return "00000004444404000000000000000000000IJ4:FC000000000000000000000000000000E>O>E0044O4400000448000O000000004012448@@>ACEIA>4<4444>>A168@ON11>11N26:BO22O@N11A>>@@NAA>O124888>AA>AA>>AA?11>04004000000000024842000O0O000842480>A164040000000>AAOAAANAANAAN>A@@@A>LBAAABLO@@L@@OO@@L@@@>A@GAA>AAAOAAA>44444>7222BB<ABDHDBA@@@@@@OAKEEAAAAIEECAA>AAAAA>NAAN@@@>AAAEB=NAANDBA>A@>1A>O444444AAAAAA>AAAAA:4AAAEEKAAA:4:AAAA:4444O1248@O"
} }
function rt_init() -> void { function rt_init(rt_core_st: mut RtCoreState, rt_image_st: mut RtImageState, rt_rng_st: mut RtRngState, rt_truetype_st: mut RtTruetypeState, rt_ui_st: mut RtUiState) -> void {
rt_fb = words(320 * 240) rt_core_st.rt_fb = words(320 * 240)
rt_regs = words(64) rt_core_st.rt_regs = words(64)
fill(rt_regs, 0, 64 * 4) fill(rt_core_st.rt_regs, 0, 64 * 4)
rt_map = bytes(96 * 64) rt_core_st.rt_map = bytes(96 * 64)
fill(rt_map, ' ', 96 * 64) fill(rt_core_st.rt_map, ' ', 96 * 64)
rt_clip_x1 = rt_fbw rt_core_st.rt_clip_x1 = rt_fbw
rt_clip_y1 = rt_fbh rt_core_st.rt_clip_y1 = rt_fbh
rt_statusbuf = bytes(96) rt_core_st.rt_statusbuf = bytes(96)
rt_statusbuf[0] = 0 rt_core_st.rt_statusbuf[0] = 0
rt_image_init() rt_image_init(rt_image_st)
rt_tt_init() rt_tt_init(rt_truetype_st)
rt_ui_init() rt_ui_init(rt_ui_st)
rt_clear(0) rt_clear(rt_core_st, rt_rng_st, 0)
if is_windowed() { if is_windowed() {
win_open(rt_fbw, rt_fbh, 3, game_title()) win_open(rt_fbw, rt_fbh, 3, game_title())
} }
} }
function rt_shutdown() -> void { function rt_shutdown(rt_core_st: RtCoreState) -> void {
if is_windowed() { if is_windowed() {
win_close() win_close()
return return
} }
rt_dump_ppm(HEADLESS_FRAME_PATH) rt_dump_ppm(rt_core_st, HEADLESS_FRAME_PATH)
} }
# ---- framebuffer ---------------------------------------------------------- # ---- framebuffer ----------------------------------------------------------
function rt_screen_w() -> int { return rt_fbw } function rt_screen_w() -> int { return rt_fbw }
function rt_screen_h() -> int { return rt_fbh } function rt_screen_h() -> int { return rt_fbh }
function rt_clear(c: int) -> void { function rt_clear(rt_core_st: mut RtCoreState, rt_rng_st: mut RtRngState, c: int) -> void {
rt_camera_tick() # a timed Camera.shake_for advances once per frame rt_camera_tick(rt_core_st, rt_rng_st) # a timed Camera.shake_for advances once per frame
let n = rt_fbw * rt_fbh let n = rt_fbw * rt_fbh
for i in 0 .. n { for i in 0 .. n {
rt_fb[i] = c rt_core_st.rt_fb[i] = c
} }
} }
@ -109,74 +120,74 @@ function rt_blend_add(dst: int, src: int) -> int {
# the low-level plot: apply the camera (+ shake) offset, reject anything outside # the low-level plot: apply the camera (+ shake) offset, reject anything outside
# the clip rectangle or the framebuffer, then write or additively blend. # the clip rectangle or the framebuffer, then write or additively blend.
function rt_put_px(x: int, y: int, c: int) -> void { function rt_put_px(rt_core_st: mut RtCoreState, x: int, y: int, c: int) -> void {
var sx = x - rt_cam_x - rt_shake_x var sx = x - rt_core_st.rt_cam_x - rt_core_st.rt_shake_x
var sy = y - rt_cam_y - rt_shake_y var sy = y - rt_core_st.rt_cam_y - rt_core_st.rt_shake_y
if rt_cam_zoomed { # #78: scale about the screen centre if rt_core_st.rt_cam_zoomed { # #78: scale about the screen centre
let hw = rt_fbw / 2 let hw = rt_fbw / 2
let hh = rt_fbh / 2 let hh = rt_fbh / 2
sx = floor(fixed(sx - hw) * rt_cam_zoom) + hw sx = floor(fixed(sx - hw) * rt_core_st.rt_cam_zoom) + hw
sy = floor(fixed(sy - hh) * rt_cam_zoom) + hh sy = floor(fixed(sy - hh) * rt_core_st.rt_cam_zoom) + hh
} }
if sx < rt_clip_x0 { return } if sx < rt_core_st.rt_clip_x0 { return }
if sy < rt_clip_y0 { return } if sy < rt_core_st.rt_clip_y0 { return }
if sx >= rt_clip_x1 { return } if sx >= rt_core_st.rt_clip_x1 { return }
if sy >= rt_clip_y1 { return } if sy >= rt_core_st.rt_clip_y1 { return }
if sx < 0 { return } if sx < 0 { return }
if sy < 0 { return } if sy < 0 { return }
if sx >= rt_fbw { return } if sx >= rt_fbw { return }
if sy >= rt_fbh { return } if sy >= rt_fbh { return }
let idx = sy * rt_fbw + sx let idx = sy * rt_fbw + sx
if rt_blend == 1 { rt_fb[idx] = rt_blend_add(rt_fb[idx], c) } if rt_core_st.rt_blend == 1 { rt_core_st.rt_fb[idx] = rt_blend_add(rt_core_st.rt_fb[idx], c) }
else { rt_fb[idx] = c } else { rt_core_st.rt_fb[idx] = c }
} }
function rt_fill_rect(x: int, y: int, w: int, h: int, c: int) -> void { function rt_fill_rect(rt_core_st: mut RtCoreState, x: int, y: int, w: int, h: int, c: int) -> void {
var ox = x - rt_cam_x - rt_shake_x var ox = x - rt_core_st.rt_cam_x - rt_core_st.rt_shake_x
var oy = y - rt_cam_y - rt_shake_y var oy = y - rt_core_st.rt_cam_y - rt_core_st.rt_shake_y
var ow = w var ow = w
var oh = h var oh = h
if rt_cam_zoomed { # #78: scale position + size about the centre if rt_core_st.rt_cam_zoomed { # #78: scale position + size about the centre
let hw = rt_fbw / 2 let hw = rt_fbw / 2
let hh = rt_fbh / 2 let hh = rt_fbh / 2
ox = floor(fixed(ox - hw) * rt_cam_zoom) + hw ox = floor(fixed(ox - hw) * rt_core_st.rt_cam_zoom) + hw
oy = floor(fixed(oy - hh) * rt_cam_zoom) + hh oy = floor(fixed(oy - hh) * rt_core_st.rt_cam_zoom) + hh
ow = floor(fixed(w) * rt_cam_zoom); if ow < 1 { ow = 1 } ow = floor(fixed(w) * rt_core_st.rt_cam_zoom); if ow < 1 { ow = 1 }
oh = floor(fixed(h) * rt_cam_zoom); if oh < 1 { oh = 1 } oh = floor(fixed(h) * rt_core_st.rt_cam_zoom); if oh < 1 { oh = 1 }
} }
let x0 = max(max(0, rt_clip_x0), ox) let x0 = max(max(0, rt_core_st.rt_clip_x0), ox)
let y0 = max(max(0, rt_clip_y0), oy) let y0 = max(max(0, rt_core_st.rt_clip_y0), oy)
let x1 = min(min(rt_fbw, rt_clip_x1), ox + ow) let x1 = min(min(rt_fbw, rt_core_st.rt_clip_x1), ox + ow)
let y1 = min(min(rt_fbh, rt_clip_y1), oy + oh) let y1 = min(min(rt_fbh, rt_core_st.rt_clip_y1), oy + oh)
var j = y0 var j = y0
while j < y1 { while j < y1 {
let row = j * rt_fbw let row = j * rt_fbw
var i = x0 var i = x0
while i < x1 { while i < x1 {
if rt_blend == 1 { rt_fb[row + i] = rt_blend_add(rt_fb[row + i], c) } if rt_core_st.rt_blend == 1 { rt_core_st.rt_fb[row + i] = rt_blend_add(rt_core_st.rt_fb[row + i], c) }
else { rt_fb[row + i] = c } else { rt_core_st.rt_fb[row + i] = c }
i += 1 i += 1
} }
j += 1 j += 1
} }
} }
function rt_frame_rect(x: int, y: int, w: int, h: int, c: int) -> void { function rt_frame_rect(rt_core_st: mut RtCoreState, x: int, y: int, w: int, h: int, c: int) -> void {
rt_fill_rect(x, y, w, 1, c) rt_fill_rect(rt_core_st, x, y, w, 1, c)
rt_fill_rect(x, y + h - 1, w, 1, c) rt_fill_rect(rt_core_st, x, y + h - 1, w, 1, c)
rt_fill_rect(x, y, 1, h, c) rt_fill_rect(rt_core_st, x, y, 1, h, c)
rt_fill_rect(x + w - 1, y, 1, h, c) rt_fill_rect(rt_core_st, x + w - 1, y, 1, h, c)
} }
# A straight line by Bresenham's algorithm — integer only, any direction. # A straight line by Bresenham's algorithm — integer only, any direction.
function rt_line(x0: int, y0: int, x1: int, y1: int, c: int) -> void { function rt_line(rt_core_st: mut RtCoreState, x0: int, y0: int, x1: int, y1: int, c: int) -> void {
var x = x0; var y = y0 var x = x0; var y = y0
let dx = abs(x1 - x0); let dy = -abs(y1 - y0) let dx = abs(x1 - x0); let dy = -abs(y1 - y0)
var sx = -1; if x0 < x1 { sx = 1 } var sx = -1; if x0 < x1 { sx = 1 }
var sy = -1; if y0 < y1 { sy = 1 } var sy = -1; if y0 < y1 { sy = 1 }
var err = dx + dy var err = dx + dy
while true { while true {
rt_put_px(x, y, c) rt_put_px(rt_core_st, x, y, c)
if (x == x1) and (y == y1) { return } if (x == x1) and (y == y1) { return }
let e2 = 2 * err let e2 = 2 * err
if e2 >= dy { err += dy; x += sx } if e2 >= dy { err += dy; x += sx }
@ -185,14 +196,14 @@ function rt_line(x0: int, y0: int, x1: int, y1: int, c: int) -> void {
} }
# A circle outline by the midpoint algorithm (eight-way symmetry). # A circle outline by the midpoint algorithm (eight-way symmetry).
function rt_circle(cx: int, cy: int, r: int, c: int) -> void { function rt_circle(rt_core_st: mut RtCoreState, cx: int, cy: int, r: int, c: int) -> void {
if r < 0 { return } if r < 0 { return }
var x = r; var y = 0; var err = 1 - r var x = r; var y = 0; var err = 1 - r
while x >= y { while x >= y {
rt_put_px(cx + x, cy + y, c); rt_put_px(cx + y, cy + x, c) rt_put_px(rt_core_st, cx + x, cy + y, c); rt_put_px(rt_core_st, cx + y, cy + x, c)
rt_put_px(cx - y, cy + x, c); rt_put_px(cx - x, cy + y, c) rt_put_px(rt_core_st, cx - y, cy + x, c); rt_put_px(rt_core_st, cx - x, cy + y, c)
rt_put_px(cx - x, cy - y, c); rt_put_px(cx - y, cy - x, c) rt_put_px(rt_core_st, cx - x, cy - y, c); rt_put_px(rt_core_st, cx - y, cy - x, c)
rt_put_px(cx + y, cy - x, c); rt_put_px(cx + x, cy - y, c) rt_put_px(rt_core_st, cx + y, cy - x, c); rt_put_px(rt_core_st, cx + x, cy - y, c)
y += 1 y += 1
if err < 0 { err = err + 2 * y + 1 } if err < 0 { err = err + 2 * y + 1 }
else { x -= 1; err = err + 2 * (y - x) + 1 } else { x -= 1; err = err + 2 * (y - x) + 1 }
@ -200,27 +211,27 @@ function rt_circle(cx: int, cy: int, r: int, c: int) -> void {
} }
# A filled disc — one horizontal span per row, width from the circle equation. # A filled disc — one horizontal span per row, width from the circle equation.
function rt_fill_circle(cx: int, cy: int, r: int, c: int) -> void { function rt_fill_circle(rt_core_st: mut RtCoreState, cx: int, cy: int, r: int, c: int) -> void {
if r < 0 { return } if r < 0 { return }
let r2 = r * r let r2 = r * r
var dy = -r var dy = -r
while dy <= r { while dy <= r {
var dx = 0 var dx = 0
while (dx + 1) * (dx + 1) + dy * dy <= r2 { dx += 1 } while (dx + 1) * (dx + 1) + dy * dy <= r2 { dx += 1 }
rt_fill_rect(cx - dx, cy + dy, 2 * dx + 1, 1, c) rt_fill_rect(rt_core_st, cx - dx, cy + dy, 2 * dx + 1, 1, c)
dy += 1 dy += 1
} }
} }
# A triangle outline — three lines. # A triangle outline — three lines.
function rt_triangle(x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, c: int) -> void { function rt_triangle(rt_core_st: mut RtCoreState, x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, c: int) -> void {
rt_line(x0, y0, x1, y1, c) rt_line(rt_core_st, x0, y0, x1, y1, c)
rt_line(x1, y1, x2, y2, c) rt_line(rt_core_st, x1, y1, x2, y2, c)
rt_line(x2, y2, x0, y0, c) rt_line(rt_core_st, x2, y2, x0, y0, c)
} }
# A filled triangle — bounding-box scan with an edge-sign inside test. # A filled triangle — bounding-box scan with an edge-sign inside test.
function rt_fill_triangle(x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, c: int) -> void { function rt_fill_triangle(rt_core_st: mut RtCoreState, x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, c: int) -> void {
let minx = min(x0, min(x1, x2)); let maxx = max(x0, max(x1, x2)) let minx = min(x0, min(x1, x2)); let maxx = max(x0, max(x1, x2))
let miny = min(y0, min(y1, y2)); let maxy = max(y0, max(y1, y2)) let miny = min(y0, min(y1, y2)); let maxy = max(y0, max(y1, y2))
var py = miny var py = miny
@ -232,7 +243,7 @@ function rt_fill_triangle(x0: int, y0: int, x1: int, y1: int, x2: int, y2: int,
let d2 = (x0 - x2) * (py - y2) - (y0 - y2) * (px - x2) let d2 = (x0 - x2) * (py - y2) - (y0 - y2) * (px - x2)
let neg = (d0 < 0) or (d1 < 0) or (d2 < 0) let neg = (d0 < 0) or (d1 < 0) or (d2 < 0)
let pos = (d0 > 0) or (d1 > 0) or (d2 > 0) let pos = (d0 > 0) or (d1 > 0) or (d2 > 0)
if not (neg and pos) { rt_put_px(px, py, c) } if not (neg and pos) { rt_put_px(rt_core_st, px, py, c) }
px += 1 px += 1
} }
py += 1 py += 1
@ -240,16 +251,16 @@ function rt_fill_triangle(x0: int, y0: int, x1: int, y1: int, x2: int, y2: int,
} }
# the four-way symmetric points of an ellipse centred at (cx, cy). # the four-way symmetric points of an ellipse centred at (cx, cy).
function rt_oval_pts(cx: int, cy: int, x: int, y: int, c: int) -> void { function rt_oval_pts(rt_core_st: mut RtCoreState, cx: int, cy: int, x: int, y: int, c: int) -> void {
rt_put_px(cx + x, cy + y, c) rt_put_px(rt_core_st, cx + x, cy + y, c)
rt_put_px(cx - x, cy + y, c) rt_put_px(rt_core_st, cx - x, cy + y, c)
rt_put_px(cx + x, cy - y, c) rt_put_px(rt_core_st, cx + x, cy - y, c)
rt_put_px(cx - x, cy - y, c) rt_put_px(rt_core_st, cx - x, cy - y, c)
} }
# An axis-aligned ellipse outline by the midpoint algorithm — integer only, # An axis-aligned ellipse outline by the midpoint algorithm — integer only,
# radii rx (horizontal) and ry (vertical). rx == ry draws a circle. # radii rx (horizontal) and ry (vertical). rx == ry draws a circle.
function rt_oval(x: int, y: int, rx: int, ry: int, c: int) -> void { function rt_oval(rt_core_st: mut RtCoreState, x: int, y: int, rx: int, ry: int, c: int) -> void {
if rx <= 0 { return } if rx <= 0 { return }
if ry <= 0 { return } if ry <= 0 { return }
let rx2 = rx * rx let rx2 = rx * rx
@ -260,14 +271,14 @@ function rt_oval(x: int, y: int, rx: int, ry: int, c: int) -> void {
var ey = ry var ey = ry
var px = 0 var px = 0
var py = two_rx2 * ey var py = two_rx2 * ey
rt_oval_pts(x, y, ex, ey, c) rt_oval_pts(rt_core_st, x, y, ex, ey, c)
var p = ry2 - rx2 * ry + rx2 / 4 # region 1 var p = ry2 - rx2 * ry + rx2 / 4 # region 1
while px < py { while px < py {
ex += 1 ex += 1
px += two_ry2 px += two_ry2
if p < 0 { p = p + ry2 + px } if p < 0 { p = p + ry2 + px }
else { ey -= 1; py -= two_rx2; p = p + ry2 + px - py } else { ey -= 1; py -= two_rx2; p = p + ry2 + px - py }
rt_oval_pts(x, y, ex, ey, c) rt_oval_pts(rt_core_st, x, y, ex, ey, c)
} }
p = ry2 * (ex * 2 + 1) * (ex * 2 + 1) / 4 + rx2 * (ey - 1) * (ey - 1) - rx2 * ry2 # region 2 p = ry2 * (ex * 2 + 1) * (ex * 2 + 1) / 4 + rx2 * (ey - 1) * (ey - 1) - rx2 * ry2 # region 2
while ey > 0 { while ey > 0 {
@ -275,18 +286,18 @@ function rt_oval(x: int, y: int, rx: int, ry: int, c: int) -> void {
py -= two_rx2 py -= two_rx2
if p > 0 { p = p + rx2 - py } if p > 0 { p = p + rx2 - py }
else { ex += 1; px += two_ry2; p = p + rx2 - py + px } else { ex += 1; px += two_ry2; p = p + rx2 - py + px }
rt_oval_pts(x, y, ex, ey, c) rt_oval_pts(rt_core_st, x, y, ex, ey, c)
} }
} }
# Read a framebuffer pixel in screen space (0x00RRGGBB), or 0 if out of bounds. # Read a framebuffer pixel in screen space (0x00RRGGBB), or 0 if out of bounds.
# Unlike the plot path this ignores the camera — it reads the actual screen. # Unlike the plot path this ignores the camera — it reads the actual screen.
function rt_get_px(x: int, y: int) -> int { function rt_get_px(rt_core_st: RtCoreState, x: int, y: int) -> int {
if x < 0 { return 0 } if x < 0 { return 0 }
if y < 0 { return 0 } if y < 0 { return 0 }
if x >= rt_fbw { return 0 } if x >= rt_fbw { return 0 }
if y >= rt_fbh { return 0 } if y >= rt_fbh { return 0 }
return rt_fb[y * rt_fbw + x] return rt_core_st.rt_fb[y * rt_fbw + x]
} }
# Advance width (pixels) of `text` in the built-in 5x7 font at scale 1: 6 per # Advance width (pixels) of `text` in the built-in 5x7 font at scale 1: 6 per
@ -300,84 +311,82 @@ function rt_measure_text(text: string) -> int {
# ---- camera / clip / blend controls --------------------------------------- # ---- camera / clip / blend controls ---------------------------------------
# Set the world-space camera offset (a world point (wx,wy) draws at # Set the world-space camera offset (a world point (wx,wy) draws at
# (wx-x, wy-y)). Reset to (0,0) to draw a fixed HUD. # (wx-x, wy-y)). Reset to (0,0) to draw a fixed HUD.
function rt_camera(x: int, y: int) -> void { rt_cam_x = x; rt_cam_y = y } function rt_camera(rt_core_st: mut RtCoreState, x: int, y: int) -> void { rt_core_st.rt_cam_x = x; rt_core_st.rt_cam_y = y }
# #78 — set the render-time zoom (a Q16.16 scale applied about the screen centre): # #78 — set the render-time zoom (a Q16.16 scale applied about the screen centre):
# 1.0 = no zoom, 2.0 = 2x in, 0.5 = out. Deterministic (fixed-point), so it # 1.0 = no zoom, 2.0 = 2x in, 0.5 = out. Deterministic (fixed-point), so it
# preserves lockstep / replay / world_save. Setting exactly 1.0 turns the zoom # preserves lockstep / replay / world_save. Setting exactly 1.0 turns the zoom
# path back off, restoring the byte-identical no-zoom blit. # path back off, restoring the byte-identical no-zoom blit.
function rt_camera_zoom(scale: fixed) -> void { function rt_camera_zoom(rt_core_st: mut RtCoreState, scale: fixed) -> void {
rt_cam_zoom = scale rt_core_st.rt_cam_zoom = scale
rt_cam_zoomed = scale != 1.0 rt_core_st.rt_cam_zoomed = scale != 1.0
} }
# Ease the camera so (x,y) drifts toward the screen centre by `lerp` (a fixed in # Ease the camera so (x,y) drifts toward the screen centre by `lerp` (a fixed in
# 0..1): 0 keeps it still, 65536 (1.0) snaps it centred. Deterministic. # 0..1): 0 keeps it still, 65536 (1.0) snaps it centred. Deterministic.
function rt_camera_follow(x: int, y: int, lerp: fixed) -> void { function rt_camera_follow(rt_core_st: mut RtCoreState, x: int, y: int, lerp: fixed) -> void {
let tx = x - rt_fbw / 2 let tx = x - rt_fbw / 2
let ty = y - rt_fbh / 2 let ty = y - rt_fbh / 2
let sx = fixed(tx - rt_cam_x) * lerp # fixed * fixed, then floor to whole pixels let sx = fixed(tx - rt_core_st.rt_cam_x) * lerp # fixed * fixed, then floor to whole pixels
let sy = fixed(ty - rt_cam_y) * lerp let sy = fixed(ty - rt_core_st.rt_cam_y) * lerp
rt_cam_x += floor(sx) rt_core_st.rt_cam_x += floor(sx)
rt_cam_y += floor(sy) rt_core_st.rt_cam_y += floor(sy)
} }
# Add a random screen shake of up to +/- amount pixels, drawn from the seeded # Add a random screen shake of up to +/- amount pixels, drawn from the seeded
# RNG (so a replay shakes identically). Call each frame with a decaying amount; # RNG (so a replay shakes identically). Call each frame with a decaying amount;
# amount <= 0 clears it. # amount <= 0 clears it.
function rt_camera_shake(amount: int) -> void { function rt_camera_shake(rt_core_st: mut RtCoreState, rt_rng_st: mut RtRngState, amount: int) -> void {
if amount <= 0 { rt_shake_x = 0; rt_shake_y = 0; return } if amount <= 0 { rt_core_st.rt_shake_x = 0; rt_core_st.rt_shake_y = 0; return }
rt_shake_x = rt_rng_range(-amount, amount) rt_core_st.rt_shake_x = rt_rng_range(rt_rng_st, -amount, amount)
rt_shake_y = rt_rng_range(-amount, amount) rt_core_st.rt_shake_y = rt_rng_range(rt_rng_st, -amount, amount)
} }
# Camera.shake_for(amount, frames): shake by up to +/- amount pixels for `frames` # Camera.shake_for(amount, frames): shake by up to +/- amount pixels for `frames`
# frames, then stop — the engine re-rolls the offset at the start of every frame # frames, then stop — the engine re-rolls the offset at the start of every frame
# (rt_camera_tick, from the frame clear) so no handler has to count it down. A # (rt_camera_tick, from the frame clear) so no handler has to count it down. A
# later call restarts the shake; a bigger amount wins over a smaller one in flight. # later call restarts the shake; a bigger amount wins over a smaller one in flight.
var rt_shake_amount: int = 0 function rt_camera_shake_for(rt_core_st: mut RtCoreState, amount: int, frames: int) -> void {
var rt_shake_left: int = 0 if amount >= rt_core_st.rt_shake_amount { rt_core_st.rt_shake_amount = amount; rt_core_st.rt_shake_left = frames }
function rt_camera_shake_for(amount: int, frames: int) -> void {
if amount >= rt_shake_amount { rt_shake_amount = amount; rt_shake_left = frames }
} }
function rt_camera_tick() -> void { function rt_camera_tick(rt_core_st: mut RtCoreState, rt_rng_st: mut RtRngState) -> void {
if rt_shake_left > 0 { if rt_core_st.rt_shake_left > 0 {
rt_shake_left -= 1 rt_core_st.rt_shake_left -= 1
rt_camera_shake(rt_shake_amount) rt_camera_shake(rt_core_st, rt_rng_st, rt_core_st.rt_shake_amount)
return return
} }
if rt_shake_amount > 0 { rt_shake_amount = 0; rt_camera_shake(0) } if rt_core_st.rt_shake_amount > 0 { rt_core_st.rt_shake_amount = 0; rt_camera_shake(rt_core_st, rt_rng_st, 0) }
} }
# Restrict drawing to a screen-space rectangle (x, y, width, height). # Restrict drawing to a screen-space rectangle (x, y, width, height).
function rt_clip(x: int, y: int, width: int, height: int) -> void { function rt_clip(rt_core_st: mut RtCoreState, x: int, y: int, width: int, height: int) -> void {
rt_clip_x0 = x rt_core_st.rt_clip_x0 = x
rt_clip_y0 = y rt_core_st.rt_clip_y0 = y
rt_clip_x1 = x + width rt_core_st.rt_clip_x1 = x + width
rt_clip_y1 = y + height rt_core_st.rt_clip_y1 = y + height
} }
# Reset the clip rectangle to the whole framebuffer. # Reset the clip rectangle to the whole framebuffer.
function rt_clip_reset() -> void { function rt_clip_reset(rt_core_st: mut RtCoreState) -> void {
rt_clip_x0 = 0 rt_core_st.rt_clip_x0 = 0
rt_clip_y0 = 0 rt_core_st.rt_clip_y0 = 0
rt_clip_x1 = rt_fbw rt_core_st.rt_clip_x1 = rt_fbw
rt_clip_y1 = rt_fbh rt_core_st.rt_clip_y1 = rt_fbh
} }
# Select the pixel blend mode: 0 = replace (default), 1 = additive. # Select the pixel blend mode: 0 = replace (default), 1 = additive.
function rt_blend_mode(mode: int) -> void { rt_blend = mode } function rt_blend_mode(rt_core_st: mut RtCoreState, mode: int) -> void { rt_core_st.rt_blend = mode }
# Windowed: hand the framebuffer to the platform layer, which blits it into # Windowed: hand the framebuffer to the platform layer, which blits it into
# the view. Headless: nothing to do until shutdown writes the last frame out. # the view. Headless: nothing to do until shutdown writes the last frame out.
function rt_present() -> void { function rt_present(rt_core_st: RtCoreState) -> void {
if is_windowed() { if is_windowed() {
win_present(rt_fb, rt_fbw, rt_fbh) win_present(rt_core_st.rt_fb, rt_fbw, rt_fbh)
} }
} }
# ---- text ----------------------------------------------------------------- # ---- text -----------------------------------------------------------------
function rt_glyph(x: int, y: int, ch: int, colour: int, sc: int) -> void { function rt_glyph(rt_core_st: mut RtCoreState, x: int, y: int, ch: int, colour: int, sc: int) -> void {
var c = ch var c = ch
if c >= 'a' { if c >= 'a' {
if c <= 'z' { c -= 32 } if c <= 'z' { c -= 32 }
@ -392,34 +401,34 @@ function rt_glyph(x: int, y: int, ch: int, colour: int, sc: int) -> void {
for cc in 0 .. 5 { for cc in 0 .. 5 {
let on = b / 16 let on = b / 16
if on == 1 { if on == 1 {
rt_fill_rect(x + cc * sc, y + row * sc, sc, sc, colour) rt_fill_rect(rt_core_st, x + cc * sc, y + row * sc, sc, sc, colour)
} }
b = (b - on * 16) * 2 b = (b - on * 16) * 2
} }
} }
} }
function rt_text(x: int, y: int, s: string, colour: int, sc: int) -> void { function rt_text(rt_core_st: mut RtCoreState, x: int, y: int, s: string, colour: int, sc: int) -> void {
var i = 0 var i = 0
var cx = x var cx = x
var ch = s[0] var ch = s[0]
while ch != 0 { while ch != 0 {
rt_glyph(cx, y, ch, colour, sc) rt_glyph(rt_core_st, cx, y, ch, colour, sc)
cx = cx + 6 * sc cx = cx + 6 * sc
i += 1 i += 1
ch = s[i] ch = s[i]
} }
} }
function rt_text_int(x: int, y: int, n: int, colour: int, sc: int) -> void { function rt_text_int(rt_core_st: mut RtCoreState, x: int, y: int, n: int, colour: int, sc: int) -> void {
if n == 0 { if n == 0 {
rt_glyph(x, y, '0', colour, sc) rt_glyph(rt_core_st, x, y, '0', colour, sc)
return return
} }
var v = n var v = n
var cx = x var cx = x
if v < 0 { if v < 0 {
rt_glyph(cx, y, '-', colour, sc) rt_glyph(rt_core_st, cx, y, '-', colour, sc)
cx = cx + 6 * sc cx = cx + 6 * sc
v = -v v = -v
} }
@ -435,46 +444,46 @@ function rt_text_int(x: int, y: int, n: int, colour: int, sc: int) -> void {
for k in 1 .. p { for k in 1 .. p {
div *= 10 div *= 10
} }
rt_glyph(cx, y, 48 + (v / div) % 10, colour, sc) rt_glyph(rt_core_st, cx, y, 48 + (v / div) % 10, colour, sc)
cx = cx + 6 * sc cx = cx + 6 * sc
p -= 1 p -= 1
} }
} }
# ---- registers ------------------------------------------------------------ # ---- registers ------------------------------------------------------------
function rt_reg(i: int) -> int { function rt_reg(rt_core_st: RtCoreState, i: int) -> int {
if i < 0 { return 0 } if i < 0 { return 0 }
if i >= 64 { return 0 } if i >= 64 { return 0 }
return rt_regs[i] return rt_core_st.rt_regs[i]
} }
function rt_set_reg(i: int, v: int) -> void { function rt_set_reg(rt_core_st: mut RtCoreState, i: int, v: int) -> void {
if i < 0 { return } if i < 0 { return }
if i >= 64 { return } if i >= 64 { return }
rt_regs[i] = v rt_core_st.rt_regs[i] = v
} }
# ---- platform: input ------------------------------------------------------ # ---- platform: input ------------------------------------------------------
function rt_poll() -> int { function rt_poll(rt_core_st: mut RtCoreState) -> int {
if is_windowed() { if is_windowed() {
return win_poll() return win_poll()
} }
let c = read_char() let c = read_char()
if c < 0 { if c < 0 {
rt_alive = 0 rt_core_st.rt_alive = 0
return 0 return 0
} }
if c == 'q' { # 'q' quits, as in the headless C platform if c == 'q' { # 'q' quits, as in the headless C platform
rt_alive = 0 rt_core_st.rt_alive = 0
} }
return c return c
} }
function rt_running() -> bool { function rt_running(rt_core_st: RtCoreState) -> bool {
if is_windowed() { if is_windowed() {
return win_running() return win_running()
} }
return rt_alive != 0 return rt_core_st.rt_alive != 0
} }
# ---- writing the frame out ------------------------------------------------ # ---- writing the frame out ------------------------------------------------
@ -516,7 +525,7 @@ function rt_put_int(buf: pointer, at: int, v: int) -> int {
return n return n
} }
function rt_dump_ppm(path: string) -> void { function rt_dump_ppm(rt_core_st: RtCoreState, path: string) -> void {
let f = file_open(path, "wb") let f = file_open(path, "wb")
if (f == null) { return } if (f == null) { return }
@ -531,7 +540,7 @@ function rt_dump_ppm(path: string) -> void {
let px = rt_fbw * rt_fbh let px = rt_fbw * rt_fbh
let buf = bytes(px * 3) let buf = bytes(px * 3)
for i in 0 .. px { for i in 0 .. px {
let c = rt_fb[i] let c = rt_core_st.rt_fb[i]
buf[i * 3] = (c / 65536) % 256 buf[i * 3] = (c / 65536) % 256
buf[i * 3 + 1] = (c / 256) % 256 buf[i * 3 + 1] = (c / 256) % 256
buf[i * 3 + 2] = c % 256 buf[i * 3 + 2] = c % 256
@ -553,91 +562,85 @@ import "grid.ludic"
# A character grid the game paints with map_row() and reads with tile(). Stored # A character grid the game paints with map_row() and reads with tile(). Stored
# as one byte per cell in a flat buffer; out-of-bounds reads answer '#' so a # as one byte per cell in a flat buffer; out-of-bounds reads answer '#' so a
# caller can treat the edge of the world as wall without special-casing it. # caller can treat the edge of the world as wall without special-casing it.
var rt_map: pointer = null
var rt_mapw: int = 0
var rt_maph: int = 0
function rt_map_size(w: int, h: int) -> void { function rt_map_size(rt_core_st: mut RtCoreState, w: int, h: int) -> void {
rt_mapw = clamp(w, 0, 96) rt_core_st.rt_mapw = clamp(w, 0, 96)
rt_maph = clamp(h, 0, 64) rt_core_st.rt_maph = clamp(h, 0, 64)
fill(rt_map, ' ', 96 * 64) fill(rt_core_st.rt_map, ' ', 96 * 64)
} }
function rt_map_row(y: int, s: string) -> void { function rt_map_row(rt_core_st: mut RtCoreState, y: int, s: string) -> void {
if y < 0 { return } if y < 0 { return }
if y >= 64 { return } if y >= 64 { return }
var x = 0 var x = 0
var ch = s[0] var ch = s[0]
while ch != 0 { while ch != 0 {
if x >= 96 { return } if x >= 96 { return }
rt_map[y * 96 + x] = ch rt_core_st.rt_map[y * 96 + x] = ch
x += 1 x += 1
ch = s[x] ch = s[x]
} }
} }
# ---- the cell API: a game edits the grid in place instead of keeping its own copy # ---- the cell API: a game edits the grid in place instead of keeping its own copy
function rt_map_set(x: int, y: int, glyph: int) -> void { function rt_map_set(rt_core_st: mut RtCoreState, x: int, y: int, glyph: int) -> void {
if (x < 0) or (y < 0) or (x >= rt_mapw) or (y >= rt_maph) { return } if (x < 0) or (y < 0) or (x >= rt_core_st.rt_mapw) or (y >= rt_core_st.rt_maph) { return }
rt_map[y * 96 + x] = glyph rt_core_st.rt_map[y * 96 + x] = glyph
} }
function rt_map_fill(glyph: int) -> void { function rt_map_fill(rt_core_st: mut RtCoreState, glyph: int) -> void {
var y = 0 var y = 0
while y < rt_maph { var x = 0; while x < rt_mapw { rt_map[y * 96 + x] = glyph; x += 1 }; y += 1 } while y < rt_core_st.rt_maph { var x = 0; while x < rt_core_st.rt_mapw { rt_core_st.rt_map[y * 96 + x] = glyph; x += 1 }; y += 1 }
} }
# every cell of the rectangle (x, y, w, h) # every cell of the rectangle (x, y, w, h)
function rt_map_rect(x: int, y: int, w: int, h: int, glyph: int) -> void { function rt_map_rect(rt_core_st: mut RtCoreState, x: int, y: int, w: int, h: int, glyph: int) -> void {
var yy = y var yy = y
while yy < y + h { var xx = x; while xx < x + w { rt_map_set(xx, yy, glyph); xx += 1 }; yy += 1 } while yy < y + h { var xx = x; while xx < x + w { rt_map_set(rt_core_st, xx, yy, glyph); xx += 1 }; yy += 1 }
} }
# the outermost ring of cells # the outermost ring of cells
function rt_map_border(glyph: int) -> void { function rt_map_border(rt_core_st: mut RtCoreState, glyph: int) -> void {
rt_map_rect(0, 0, rt_mapw, 1, glyph) rt_map_rect(rt_core_st, 0, 0, rt_core_st.rt_mapw, 1, glyph)
rt_map_rect(0, rt_maph - 1, rt_mapw, 1, glyph) rt_map_rect(rt_core_st, 0, rt_core_st.rt_maph - 1, rt_core_st.rt_mapw, 1, glyph)
rt_map_rect(0, 0, 1, rt_maph, glyph) rt_map_rect(rt_core_st, 0, 0, 1, rt_core_st.rt_maph, glyph)
rt_map_rect(rt_mapw - 1, 0, 1, rt_maph, glyph) rt_map_rect(rt_core_st, rt_core_st.rt_mapw - 1, 0, 1, rt_core_st.rt_maph, glyph)
} }
# a random cell holding `glyph` (seeded RNG): random tries, then a sweep; (-1, -1) if none # a random cell holding `glyph` (seeded RNG): random tries, then a sweep; (-1, -1) if none
function rt_map_random_cell(glyph: int) -> IVec2 { function rt_map_random_cell(rt_core_st: RtCoreState, rt_rng_st: mut RtRngState, glyph: int) -> IVec2 {
var tries = 0 var tries = 0
while tries < 64 { while tries < 64 {
let x = rt_rng_range(0, rt_mapw - 1) let x = rt_rng_range(rt_rng_st, 0, rt_core_st.rt_mapw - 1)
let y = rt_rng_range(0, rt_maph - 1) let y = rt_rng_range(rt_rng_st, 0, rt_core_st.rt_maph - 1)
if rt_tile(x, y) == glyph { return IVec2.make(x, y) } if rt_tile(rt_core_st, x, y) == glyph { return IVec2.make(x, y) }
tries += 1 tries += 1
} }
var y2 = 0 var y2 = 0
while y2 < rt_maph { var x2 = 0; while x2 < rt_mapw { if rt_tile(x2, y2) == glyph { return IVec2.make(x2, y2) }; x2 += 1 }; y2 += 1 } while y2 < rt_core_st.rt_maph { var x2 = 0; while x2 < rt_core_st.rt_mapw { if rt_tile(rt_core_st, x2, y2) == glyph { return IVec2.make(x2, y2) }; x2 += 1 }; y2 += 1 }
return IVec2.make(-1, -1) return IVec2.make(-1, -1)
} }
# a random cell holding `glyph` at least `min_tiles` from `from` (tiles); falls back to any such cell # a random cell holding `glyph` at least `min_tiles` from `from` (tiles); falls back to any such cell
function rt_map_random_cell_far(glyph: int, from: IVec2, min_tiles: int) -> IVec2 { function rt_map_random_cell_far(rt_core_st: RtCoreState, rt_rng_st: mut RtRngState, glyph: int, from: IVec2, min_tiles: int) -> IVec2 {
var tile = rt_map_random_cell(glyph) var tile = rt_map_random_cell(rt_core_st, rt_rng_st, glyph)
var tries = 0 var tries = 0
while tries < 40 { while tries < 40 {
if not IVec2.within(tile, from, min_tiles - 1) { return tile } if not IVec2.within(tile, from, min_tiles - 1) { return tile }
tile = rt_map_random_cell(glyph) tile = rt_map_random_cell(rt_core_st, rt_rng_st, glyph)
tries += 1 tries += 1
} }
return tile return tile
} }
# the solid glyphs, as the move system read them from the Solids config (0 = none) # the solid glyphs, as the move system read them from the Solids config (0 = none)
var rt_map_solid1: int = 0 function rt_map_tile_size(rt_core_st: RtCoreState) -> int { return rt_core_st.rt_map_tile_px }
var rt_map_solid2: int = 0 function rt_map_is_solid(rt_core_st: RtCoreState, x: int, y: int) -> bool {
var rt_map_tile_px: int = 16 if rt_core_st.rt_map_solid1 == 0 { return false } # no Solids config: nothing is solid
function rt_map_tile_size() -> int { return rt_map_tile_px } let g = rt_tile(rt_core_st, x, y)
function rt_map_is_solid(x: int, y: int) -> bool { if (x < 0) or (y < 0) or (x >= rt_core_st.rt_mapw) or (y >= rt_core_st.rt_maph) { return true }
if rt_map_solid1 == 0 { return false } # no Solids config: nothing is solid if (rt_core_st.rt_map_solid1 != 0) and (g == rt_core_st.rt_map_solid1) { return true }
let g = rt_tile(x, y) if (rt_core_st.rt_map_solid2 != 0) and (g == rt_core_st.rt_map_solid2) { return true }
if (x < 0) or (y < 0) or (x >= rt_mapw) or (y >= rt_maph) { return true }
if (rt_map_solid1 != 0) and (g == rt_map_solid1) { return true }
if (rt_map_solid2 != 0) and (g == rt_map_solid2) { return true }
return false return false
} }
function rt_map_is_solid_at(px: int, py: int) -> bool { return rt_map_is_solid(px / rt_map_tile_px, py / rt_map_tile_px) } function rt_map_is_solid_at(rt_core_st: RtCoreState, px: int, py: int) -> bool { return rt_map_is_solid(rt_core_st, px / rt_core_st.rt_map_tile_px, py / rt_core_st.rt_map_tile_px) }
function rt_map_to_tile(pixel: IVec2) -> IVec2 { return IVec2.make(pixel.x / rt_map_tile_px, pixel.y / rt_map_tile_px) } function rt_map_to_tile(rt_core_st: RtCoreState, pixel: IVec2) -> IVec2 { return IVec2.make(pixel.x / rt_core_st.rt_map_tile_px, pixel.y / rt_core_st.rt_map_tile_px) }
function rt_map_width() -> int { return rt_mapw } function rt_map_width(rt_core_st: RtCoreState) -> int { return rt_core_st.rt_mapw }
function rt_map_height() -> int { return rt_maph } function rt_map_height(rt_core_st: RtCoreState) -> int { return rt_core_st.rt_maph }
# IVec2.heading / along / step and Angle.diff_degrees — integer-degree geometry # IVec2.heading / along / step and Angle.diff_degrees — integer-degree geometry
function rt_ivec_heading(a: IVec2, b: IVec2) -> int { function rt_ivec_heading(a: IVec2, b: IVec2) -> int {
@ -659,22 +662,22 @@ function rt_angle_diff_degrees(a: int, b: int) -> int {
} }
# Screen.bar: a filled meter — `value` of `max` in `color` over a `back` track # Screen.bar: a filled meter — `value` of `max` in `color` over a `back` track
function rt_bar(x: int, y: int, w: int, h: int, value: int, max: int, color: int, back: int) -> void { function rt_bar(rt_core_st: mut RtCoreState, x: int, y: int, w: int, h: int, value: int, max: int, color: int, back: int) -> void {
rt_fill_rect(x, y, w, h, back) rt_fill_rect(rt_core_st, x, y, w, h, back)
var filled = 0 var filled = 0
if max > 0 { filled = clamp(value, 0, max) * w / max } if max > 0 { filled = clamp(value, 0, max) * w / max }
if filled > 0 { rt_fill_rect(x, y, filled, h, color) } if filled > 0 { rt_fill_rect(rt_core_st, x, y, filled, h, color) }
} }
# List.sample(pool, count): `count` picks from an int slice, distinct while the # List.sample(pool, count): `count` picks from an int slice, distinct while the
# pool has enough, repeating a valid pick when it does not; empty in -> zeros # pool has enough, repeating a valid pick when it does not; empty in -> zeros
function rt_list_sample(pool: []int, count: int) -> []int { function rt_list_sample(rt_rng_st: mut RtRngState, pool: []int, count: int) -> []int {
let out = new []int let out = new []int
let n = len(pool) let n = len(pool)
var i = 0 var i = 0
while i < count { while i < count {
if n == 0 { push(out, 0); i += 1; continue } if n == 0 { push(out, 0); i += 1; continue }
var pick = rt_rng_range(0, n - 1) var pick = rt_rng_range(rt_rng_st, 0, n - 1)
var distinct = n > i var distinct = n > i
var tries = 0 var tries = 0
while distinct and (tries < 64) { while distinct and (tries < 64) {
@ -682,7 +685,7 @@ function rt_list_sample(pool: []int, count: int) -> []int {
var j = 0 var j = 0
while j < len(out) { if out[j] == pool[pick] { seen = true }; j += 1 } while j < len(out) { if out[j] == pool[pick] { seen = true }; j += 1 }
if not seen { break } if not seen { break }
pick = rt_rng_range(0, n - 1) pick = rt_rng_range(rt_rng_st, 0, n - 1)
tries += 1 tries += 1
} }
push(out, pool[pick]) push(out, pool[pick])
@ -691,62 +694,61 @@ function rt_list_sample(pool: []int, count: int) -> []int {
return out return out
} }
function rt_tile(x: int, y: int) -> int { function rt_tile(rt_core_st: RtCoreState, x: int, y: int) -> int {
if x < 0 { return 35 } if x < 0 { return 35 }
if y < 0 { return 35 } if y < 0 { return 35 }
if x >= rt_mapw { return 35 } if x >= rt_core_st.rt_mapw { return 35 }
if y >= rt_maph { return 35 } if y >= rt_core_st.rt_maph { return 35 }
return rt_map[y * 96 + x] return rt_core_st.rt_map[y * 96 + x]
} }
# ---- status line ---------------------------------------------------------- # ---- status line ----------------------------------------------------------
# One persistent string of feedback/dialogue, copied into runtime-owned memory # One persistent string of feedback/dialogue, copied into runtime-owned memory
# so it survives whatever the caller does with the original. # so it survives whatever the caller does with the original.
var rt_statusbuf: pointer = null
function rt_status(s: string) -> void { function rt_status(rt_core_st: mut RtCoreState, s: string) -> void {
var i = 0 var i = 0
var ch = s[0] var ch = s[0]
while ch != 0 { while ch != 0 {
if i >= 95 { ch = 0 } if i >= 95 { ch = 0 }
if ch != 0 { if ch != 0 {
rt_statusbuf[i] = ch rt_core_st.rt_statusbuf[i] = ch
i += 1 i += 1
ch = s[i] ch = s[i]
} }
} }
rt_statusbuf[i] = 0 rt_core_st.rt_statusbuf[i] = 0
} }
function rt_status_text() -> pointer { function rt_status_text(rt_core_st: RtCoreState) -> pointer {
return rt_statusbuf return rt_core_st.rt_statusbuf
} }
# ---- snapshot: the runtime serialises its own half ------------------------ # ---- snapshot: the runtime serialises its own half ------------------------
# The compiler writes the ECS (entities, components, archetype kinds) because # The compiler writes the ECS (entities, components, archetype kinds) because
# only it knows their shape. Everything below belongs to the runtime, so the # only it knows their shape. Everything below belongs to the runtime, so the
# runtime writes it — same order both ways. # runtime writes it — same order both ways.
function rt_save_state(f: pointer) -> void { function rt_save_state(rt_core_st: RtCoreState, rt_rng_st: RtRngState, f: pointer) -> void {
let w: words = words(4) let w: words = words(4)
w[0] = rt_rng w[0] = rt_rng_st.rt_rng
w[1] = rt_mapw w[1] = rt_core_st.rt_mapw
w[2] = rt_maph w[2] = rt_core_st.rt_maph
w[3] = rt_alive w[3] = rt_core_st.rt_alive
file_write(f, w, 16) file_write(f, w, 16)
file_write(f, rt_regs, 64 * 4) file_write(f, rt_core_st.rt_regs, 64 * 4)
file_write(f, rt_map, 96 * 64) file_write(f, rt_core_st.rt_map, 96 * 64)
file_write(f, rt_statusbuf, 96) file_write(f, rt_core_st.rt_statusbuf, 96)
free(w) free(w)
} }
function rt_load_state(f: pointer) -> void { function rt_load_state(rt_core_st: mut RtCoreState, rt_rng_st: mut RtRngState, f: pointer) -> void {
let w: words = words(4) let w: words = words(4)
file_read(f, w, 16) file_read(f, w, 16)
rt_rng = w[0] rt_rng_st.rt_rng = w[0]
rt_mapw = w[1] rt_core_st.rt_mapw = w[1]
rt_maph = w[2] rt_core_st.rt_maph = w[2]
file_read(f, rt_regs, 64 * 4) file_read(f, rt_core_st.rt_regs, 64 * 4)
file_read(f, rt_map, 96 * 64) file_read(f, rt_core_st.rt_map, 96 * 64)
file_read(f, rt_statusbuf, 96) file_read(f, rt_core_st.rt_statusbuf, 96)
free(w) free(w)
} }

View file

@ -19,106 +19,108 @@ const FX_SPARK_LIFE_JITTER: int = 8
const FX_SPARK_SPEED: int = 3 # px per frame, each axis, either way const FX_SPARK_SPEED: int = 3 # px per frame, each axis, either way
const FX_NUMBER_LIFE: int = 30 # frames; a number rises one pixel every other frame const FX_NUMBER_LIFE: int = 30 # frames; a number rises one pixel every other frame
var fx_ready: bool = false export state RtFxState {
var fx_sx: words = null fx_ready: bool = false
var fx_sy: words = null fx_sx: words = null
var fx_svx: words = null fx_sy: words = null
var fx_svy: words = null fx_svx: words = null
var fx_slife: words = null fx_svy: words = null
var fx_scolor: words = null fx_slife: words = null
var fx_ssize: words = null fx_scolor: words = null
var fx_sn: int = 0 fx_ssize: words = null
var fx_nx: words = null fx_sn: int = 0
var fx_ny: words = null fx_nx: words = null
var fx_nvalue: words = null fx_ny: words = null
var fx_nlife: words = null fx_nvalue: words = null
var fx_ncolor: words = null fx_nlife: words = null
var fx_nn: int = 0 fx_ncolor: words = null
fx_nn: int = 0
function fx_init() -> void {
if fx_ready { return }
fx_sx = words(FX_MAX_SPARKS); fx_sy = words(FX_MAX_SPARKS)
fx_svx = words(FX_MAX_SPARKS); fx_svy = words(FX_MAX_SPARKS)
fx_slife = words(FX_MAX_SPARKS); fx_scolor = words(FX_MAX_SPARKS); fx_ssize = words(FX_MAX_SPARKS)
fx_nx = words(FX_MAX_NUMBERS); fx_ny = words(FX_MAX_NUMBERS)
fx_nvalue = words(FX_MAX_NUMBERS); fx_nlife = words(FX_MAX_NUMBERS); fx_ncolor = words(FX_MAX_NUMBERS)
fx_ready = true
} }
function fx_sparks(x: int, y: int, color: int, count: int) -> void { function fx_init(rt_fx_st: mut RtFxState) -> void {
fx_init() if rt_fx_st.fx_ready { return }
rt_fx_st.fx_sx = words(FX_MAX_SPARKS); rt_fx_st.fx_sy = words(FX_MAX_SPARKS)
rt_fx_st.fx_svx = words(FX_MAX_SPARKS); rt_fx_st.fx_svy = words(FX_MAX_SPARKS)
rt_fx_st.fx_slife = words(FX_MAX_SPARKS); rt_fx_st.fx_scolor = words(FX_MAX_SPARKS); rt_fx_st.fx_ssize = words(FX_MAX_SPARKS)
rt_fx_st.fx_nx = words(FX_MAX_NUMBERS); rt_fx_st.fx_ny = words(FX_MAX_NUMBERS)
rt_fx_st.fx_nvalue = words(FX_MAX_NUMBERS); rt_fx_st.fx_nlife = words(FX_MAX_NUMBERS); rt_fx_st.fx_ncolor = words(FX_MAX_NUMBERS)
rt_fx_st.fx_ready = true
}
function fx_sparks(rt_fx_st: mut RtFxState, rt_rng_st: mut RtRngState, x: int, y: int, color: int, count: int) -> void {
fx_init(rt_fx_st)
var i = 0 var i = 0
while i < count { while i < count {
if fx_sn >= FX_MAX_SPARKS { return } if rt_fx_st.fx_sn >= FX_MAX_SPARKS { return }
fx_sx[fx_sn] = x rt_fx_st.fx_sx[rt_fx_st.fx_sn] = x
fx_sy[fx_sn] = y rt_fx_st.fx_sy[rt_fx_st.fx_sn] = y
fx_svx[fx_sn] = rt_rng_range(-FX_SPARK_SPEED, FX_SPARK_SPEED) rt_fx_st.fx_svx[rt_fx_st.fx_sn] = rt_rng_range(rt_rng_st, -FX_SPARK_SPEED, FX_SPARK_SPEED)
fx_svy[fx_sn] = rt_rng_range(-FX_SPARK_SPEED, FX_SPARK_SPEED) rt_fx_st.fx_svy[rt_fx_st.fx_sn] = rt_rng_range(rt_rng_st, -FX_SPARK_SPEED, FX_SPARK_SPEED)
fx_slife[fx_sn] = FX_SPARK_LIFE + rt_rng_range(0, FX_SPARK_LIFE_JITTER) rt_fx_st.fx_slife[rt_fx_st.fx_sn] = FX_SPARK_LIFE + rt_rng_range(rt_rng_st, 0, FX_SPARK_LIFE_JITTER)
fx_scolor[fx_sn] = color rt_fx_st.fx_scolor[rt_fx_st.fx_sn] = color
fx_ssize[fx_sn] = 1 + rt_rng_range(0, 2) rt_fx_st.fx_ssize[rt_fx_st.fx_sn] = 1 + rt_rng_range(rt_rng_st, 0, 2)
fx_sn += 1 rt_fx_st.fx_sn += 1
i += 1 i += 1
} }
} }
function fx_number(x: int, y: int, value: int, color: int) -> void { function fx_number(rt_fx_st: mut RtFxState, x: int, y: int, value: int, color: int) -> void {
fx_init() fx_init(rt_fx_st)
if fx_nn >= FX_MAX_NUMBERS { return } if rt_fx_st.fx_nn >= FX_MAX_NUMBERS { return }
fx_nx[fx_nn] = x rt_fx_st.fx_nx[rt_fx_st.fx_nn] = x
fx_ny[fx_nn] = y rt_fx_st.fx_ny[rt_fx_st.fx_nn] = y
fx_nvalue[fx_nn] = value rt_fx_st.fx_nvalue[rt_fx_st.fx_nn] = value
fx_nlife[fx_nn] = FX_NUMBER_LIFE rt_fx_st.fx_nlife[rt_fx_st.fx_nn] = FX_NUMBER_LIFE
fx_ncolor[fx_nn] = color rt_fx_st.fx_ncolor[rt_fx_st.fx_nn] = color
fx_nn += 1 rt_fx_st.fx_nn += 1
} }
function fx_clear() -> void { fx_sn = 0; fx_nn = 0 } function fx_clear(rt_fx_st: mut RtFxState) -> void { rt_fx_st.fx_sn = 0; rt_fx_st.fx_nn = 0 }
# drop spark i by moving the last one into its slot # drop spark i by moving the last one into its slot
function fx_drop_spark(i: int) -> void { function fx_drop_spark(rt_fx_st: mut RtFxState, i: int) -> void {
let last = fx_sn - 1 let last = rt_fx_st.fx_sn - 1
fx_sx[i] = fx_sx[last]; fx_sy[i] = fx_sy[last] rt_fx_st.fx_sx[i] = rt_fx_st.fx_sx[last]; rt_fx_st.fx_sy[i] = rt_fx_st.fx_sy[last]
fx_svx[i] = fx_svx[last]; fx_svy[i] = fx_svy[last] rt_fx_st.fx_svx[i] = rt_fx_st.fx_svx[last]; rt_fx_st.fx_svy[i] = rt_fx_st.fx_svy[last]
fx_slife[i] = fx_slife[last]; fx_scolor[i] = fx_scolor[last]; fx_ssize[i] = fx_ssize[last] rt_fx_st.fx_slife[i] = rt_fx_st.fx_slife[last]; rt_fx_st.fx_scolor[i] = rt_fx_st.fx_scolor[last]; rt_fx_st.fx_ssize[i] = rt_fx_st.fx_ssize[last]
fx_sn = last rt_fx_st.fx_sn = last
} }
function fx_drop_number(i: int) -> void { function fx_drop_number(rt_fx_st: mut RtFxState, i: int) -> void {
let last = fx_nn - 1 let last = rt_fx_st.fx_nn - 1
fx_nx[i] = fx_nx[last]; fx_ny[i] = fx_ny[last] rt_fx_st.fx_nx[i] = rt_fx_st.fx_nx[last]; rt_fx_st.fx_ny[i] = rt_fx_st.fx_ny[last]
fx_nvalue[i] = fx_nvalue[last]; fx_nlife[i] = fx_nlife[last]; fx_ncolor[i] = fx_ncolor[last] rt_fx_st.fx_nvalue[i] = rt_fx_st.fx_nvalue[last]; rt_fx_st.fx_nlife[i] = rt_fx_st.fx_nlife[last]; rt_fx_st.fx_ncolor[i] = rt_fx_st.fx_ncolor[last]
fx_nn = last rt_fx_st.fx_nn = last
} }
# once per Update: move and age everything, dropping what expired # once per Update: move and age everything, dropping what expired
function fx_tick() -> void { function fx_tick(rt_fx_st: mut RtFxState) -> void {
if not fx_ready { return } if not rt_fx_st.fx_ready { return }
var i = 0 var i = 0
while i < fx_sn { while i < rt_fx_st.fx_sn {
fx_sx[i] += fx_svx[i] rt_fx_st.fx_sx[i] += rt_fx_st.fx_svx[i]
fx_sy[i] += fx_svy[i] rt_fx_st.fx_sy[i] += rt_fx_st.fx_svy[i]
fx_slife[i] -= 1 rt_fx_st.fx_slife[i] -= 1
if fx_slife[i] <= 0 { fx_drop_spark(i) } else { i += 1 } if rt_fx_st.fx_slife[i] <= 0 { fx_drop_spark(rt_fx_st, i) } else { i += 1 }
} }
i = 0 i = 0
while i < fx_nn { while i < rt_fx_st.fx_nn {
fx_nlife[i] -= 1 rt_fx_st.fx_nlife[i] -= 1
if fx_nlife[i] % 2 == 0 { fx_ny[i] -= 1 } if rt_fx_st.fx_nlife[i] % 2 == 0 { rt_fx_st.fx_ny[i] -= 1 }
if fx_nlife[i] <= 0 { fx_drop_number(i) } else { i += 1 } if rt_fx_st.fx_nlife[i] <= 0 { fx_drop_number(rt_fx_st, i) } else { i += 1 }
} }
} }
# once per Render, after the sprites: everything goes through the camera chokepoints # once per Render, after the sprites: everything goes through the camera chokepoints
function fx_draw() -> void { function fx_draw(rt_core_st: mut RtCoreState, rt_fx_st: RtFxState) -> void {
if not fx_ready { return } if not rt_fx_st.fx_ready { return }
var i = 0 var i = 0
while i < fx_sn { while i < rt_fx_st.fx_sn {
rt_fill_rect(fx_sx[i], fx_sy[i], fx_ssize[i], fx_ssize[i], fx_scolor[i]) rt_fill_rect(rt_core_st, rt_fx_st.fx_sx[i], rt_fx_st.fx_sy[i], rt_fx_st.fx_ssize[i], rt_fx_st.fx_ssize[i], rt_fx_st.fx_scolor[i])
i += 1 i += 1
} }
i = 0 i = 0
while i < fx_nn { while i < rt_fx_st.fx_nn {
rt_text_int(fx_nx[i] - 4, fx_ny[i] - 10, fx_nvalue[i], fx_ncolor[i], 1) rt_text_int(rt_core_st, rt_fx_st.fx_nx[i] - 4, rt_fx_st.fx_ny[i] - 10, rt_fx_st.fx_nvalue[i], rt_fx_st.fx_ncolor[i], 1)
i += 1 i += 1
} }
} }

View file

@ -73,22 +73,26 @@ extern function f_from_int(a: int) -> int = "f_from_int"
extern function f_to_int(a: int) -> int = "f_to_int" extern function f_to_int(a: int) -> int = "f_to_int"
# ---- state -------------------------------------------------------------------- # ---- state --------------------------------------------------------------------
var gl_is_open: bool = false export state RtGlState {
var gl_w: int = 0 # drawable width, in pixels gl_is_open: bool = false
var gl_h: int = 0 gl_w: int = 0 # drawable width, in pixels
var gl_scale: int = 1 # backing pixels per window point gl_h: int = 0
var gl_screen: int = 0 # the framebuffer that is "the screen" (an FBO headless) gl_scale: int = 1 # backing pixels per window point
var gl_ids: words = null # one-word scratch for glGen*/glGet* gl_screen: int = 0 # the framebuffer that is "the screen" (an FBO headless)
gl_ids: words = null # one-word scratch for glGen*/glGet*
gl_size_buf: words = null
gl_log_buf: string = null
}
function gl_scratch() -> words { function gl_scratch(rt_gl_st: mut RtGlState) -> words {
if gl_ids == null { gl_ids = words(4) } if rt_gl_st.gl_ids == null { rt_gl_st.gl_ids = words(4) }
return gl_ids return rt_gl_st.gl_ids
} }
# Open a GL 4.1 core context on a w x h (points) window titled `title`; headless, # Open a GL 4.1 core context on a w x h (points) window titled `title`; headless,
# an offscreen context with a w x h framebuffer standing in for the screen. # an offscreen context with a w x h framebuffer standing in for the screen.
function gl_open(width: int, height: int, title: pointer) -> bool { function gl_open(rt_gl_st: mut RtGlState, width: int, height: int, title: pointer) -> bool {
if gl_is_open { return true } if rt_gl_st.gl_is_open { return true }
if is_windowed() { if is_windowed() {
if win_gl_attach() == 0 { if win_gl_attach() == 0 {
win_open(width, height, 1, title) # a plain program: no window yet win_open(width, height, 1, title) # a plain program: no window yet
@ -99,25 +103,25 @@ function gl_open(width: int, height: int, title: pointer) -> bool {
win_set_title(title) win_set_title(title)
} }
win_gl_resize(width, height) win_gl_resize(width, height)
gl_scale = win_gl_scale() rt_gl_st.gl_scale = win_gl_scale()
gl_w = width * gl_scale rt_gl_st.gl_w = width * rt_gl_st.gl_scale
gl_h = height * gl_scale rt_gl_st.gl_h = height * rt_gl_st.gl_scale
gl_screen = 0 rt_gl_st.gl_screen = 0
} else { } else {
if cgl_offscreen() == 0 { return false } if cgl_offscreen() == 0 { return false }
gl_scale = 1 rt_gl_st.gl_scale = 1
gl_w = width rt_gl_st.gl_w = width
gl_h = height rt_gl_st.gl_h = height
gl_screen = gl_make_screen_fbo(width, height) rt_gl_st.gl_screen = gl_make_screen_fbo(rt_gl_st, width, height)
} }
gl_bind_framebuffer(GL_FRAMEBUFFER, gl_screen) gl_bind_framebuffer(GL_FRAMEBUFFER, rt_gl_st.gl_screen)
gl_viewport(0, 0, gl_w, gl_h) gl_viewport(0, 0, rt_gl_st.gl_w, rt_gl_st.gl_h)
gl_is_open = true rt_gl_st.gl_is_open = true
return true return true
} }
function gl_make_screen_fbo(w: int, h: int) -> int { function gl_make_screen_fbo(rt_gl_st: mut RtGlState, w: int, h: int) -> int {
let ids = gl_scratch() let ids = gl_scratch(rt_gl_st)
gl_gen_framebuffers(1, ids) gl_gen_framebuffers(1, ids)
let fbo = ids[0] let fbo = ids[0]
gl_bind_framebuffer(GL_FRAMEBUFFER, fbo) gl_bind_framebuffer(GL_FRAMEBUFFER, fbo)
@ -138,18 +142,17 @@ function gl_make_screen_fbo(w: int, h: int) -> int {
# Did the drawable change size (a window drag, full screen, a Retina switch)? Then # Did the drawable change size (a window drag, full screen, a Retina switch)? Then
# gl_w / gl_h follow it and the caller rebuilds its screen-sized targets. # gl_w / gl_h follow it and the caller rebuilds its screen-sized targets.
var gl_size_buf: words = null function gl_resize_check(rt_gl_st: mut RtGlState) -> bool {
function gl_resize_check() -> bool { if not is_windowed() or not rt_gl_st.gl_is_open { return false }
if not is_windowed() or not gl_is_open { return false } if rt_gl_st.gl_size_buf == null { rt_gl_st.gl_size_buf = words(4) }
if gl_size_buf == null { gl_size_buf = words(4) } win_gl_drawable(rt_gl_st.gl_size_buf)
win_gl_drawable(gl_size_buf) let w = rt_gl_st.gl_size_buf[0]; let h = rt_gl_st.gl_size_buf[1]
let w = gl_size_buf[0]; let h = gl_size_buf[1]
if w <= 0 or h <= 0 { return false } if w <= 0 or h <= 0 { return false }
if w == gl_w and h == gl_h { return false } if w == rt_gl_st.gl_w and h == rt_gl_st.gl_h { return false }
win_gl_update() win_gl_update()
gl_w = w; gl_h = h rt_gl_st.gl_w = w; rt_gl_st.gl_h = h
gl_scale = win_gl_scale() rt_gl_st.gl_scale = win_gl_scale()
gl_viewport(0, 0, gl_w, gl_h) gl_viewport(0, 0, rt_gl_st.gl_w, rt_gl_st.gl_h)
return true return true
} }
function gl_set_window(w: int, h: int) -> void { if is_windowed() { win_gl_resize(w, h) } } function gl_set_window(w: int, h: int) -> void { if is_windowed() { win_gl_resize(w, h) } }
@ -157,16 +160,16 @@ function gl_toggle_fullscreen() -> void { if is_windowed() { win_toggle_fullscre
function gl_set_retina(on: bool) -> void { if is_windowed() { var v = 0; if on { v = 1 }; win_gl_retina(v) } } function gl_set_retina(on: bool) -> void { if is_windowed() { var v = 0; if on { v = 1 }; win_gl_retina(v) } }
# vsync on (1, the default) or off (0); headless has nothing to sync to # vsync on (1, the default) or off (0); headless has nothing to sync to
function gl_vsync(n: int) -> void { if is_windowed() { win_gl_swap_interval(n) } } function gl_vsync(n: int) -> void { if is_windowed() { win_gl_swap_interval(n) } }
function gl_width() -> int { return gl_w } function gl_width(rt_gl_st: RtGlState) -> int { return rt_gl_st.gl_w }
function gl_height() -> int { return gl_h } function gl_height(rt_gl_st: RtGlState) -> int { return rt_gl_st.gl_h }
function gl_screen_fbo() -> int { return gl_screen } function gl_screen_fbo(rt_gl_st: RtGlState) -> int { return rt_gl_st.gl_screen }
function gl_pixel_scale() -> int { return gl_scale } function gl_pixel_scale(rt_gl_st: RtGlState) -> int { return rt_gl_st.gl_scale }
# a renderer that owns its swapchain (render3d's Vulkan path) says what the drawable is # a renderer that owns its swapchain (render3d's Vulkan path) says what the drawable is
function gl_set_drawable(w: int, h: int) -> void { function gl_set_drawable(rt_gl_st: mut RtGlState, w: int, h: int) -> void {
gl_w = w rt_gl_st.gl_w = w
gl_h = h rt_gl_st.gl_h = h
} }
function gl_set_pixel_scale(s: int) -> void { gl_scale = s } function gl_set_pixel_scale(rt_gl_st: mut RtGlState, s: int) -> void { rt_gl_st.gl_scale = s }
# Present the frame (vsync'd flushBuffer); headless, just finish the GPU work. # Present the frame (vsync'd flushBuffer); headless, just finish the GPU work.
function gl_swap() -> void { function gl_swap() -> void {
@ -175,13 +178,13 @@ function gl_swap() -> void {
} }
# Write what is on the screen framebuffer to a binary PPM (call before Gl.swap). # Write what is on the screen framebuffer to a binary PPM (call before Gl.swap).
function gl_screenshot(path: pointer) -> bool { function gl_screenshot(rt_gl_st: RtGlState, path: pointer) -> bool {
let w = gl_w let w = rt_gl_st.gl_w
let h = gl_h let h = rt_gl_st.gl_h
let f = file_open(path, "wb") let f = file_open(path, "wb")
if f == null { return false } if f == null { return false }
let buf = bytes(w * h * 3) let buf = bytes(w * h * 3)
gl_bind_framebuffer(GL_READ_FRAMEBUFFER, gl_screen) gl_bind_framebuffer(GL_READ_FRAMEBUFFER, rt_gl_st.gl_screen)
gl_pixel_storei(GL_PACK_ALIGNMENT, 1) gl_pixel_storei(GL_PACK_ALIGNMENT, 1)
gl_read_pixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, buf) gl_read_pixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, buf)
let hdr = `P6\n{w} {h}\n255\n` let hdr = `P6\n{w} {h}\n255\n`
@ -204,22 +207,21 @@ function gl_check(tag: pointer) -> int {
} }
# ---- shaders -------------------------------------------------------------------- # ---- shaders --------------------------------------------------------------------
var gl_log_buf: string = null
# Compile one shader stage from source; 0 (and the info log on stdout) on failure. # Compile one shader stage from source; 0 (and the info log on stdout) on failure.
function gl_shader(kind: int, src: pointer) -> int { function gl_shader(rt_gl_st: mut RtGlState, kind: int, src: pointer) -> int {
let id = gl_create_shader(kind) let id = gl_create_shader(kind)
var srcs: pointers = pointers(1) var srcs: pointers = pointers(1)
srcs[0] = src srcs[0] = src
gl_shader_source(id, 1, srcs, null) gl_shader_source(id, 1, srcs, null)
gl_compile_shader(id) gl_compile_shader(id)
let ids = gl_scratch() let ids = gl_scratch(rt_gl_st)
gl_get_shaderiv(id, GL_COMPILE_STATUS, ids) gl_get_shaderiv(id, GL_COMPILE_STATUS, ids)
if ids[0] == 0 { if ids[0] == 0 {
if gl_log_buf == null { gl_log_buf = bytes(8192) } if rt_gl_st.gl_log_buf == null { rt_gl_st.gl_log_buf = bytes(8192) }
gl_get_shader_info_log(id, 8191, null, gl_log_buf) gl_get_shader_info_log(id, 8191, null, rt_gl_st.gl_log_buf)
print("shader compile failed:") print("shader compile failed:")
print(gl_log_buf) print(rt_gl_st.gl_log_buf)
gl_delete_shader(id) gl_delete_shader(id)
return 0 return 0
} }
@ -227,28 +229,28 @@ function gl_shader(kind: int, src: pointer) -> int {
} }
# Link a program from a vertex + fragment source pair; 0 on failure. # Link a program from a vertex + fragment source pair; 0 on failure.
function gl_program(vs: pointer, fs: pointer) -> int { function gl_program(rt_gl_st: mut RtGlState, vs: pointer, fs: pointer) -> int {
return gl_program5(vs, null, null, null, fs) return gl_program5(rt_gl_st, vs, null, null, null, fs)
} }
# Link a program from up to five stages (null = stage absent). # Link a program from up to five stages (null = stage absent).
function gl_program5(vs: pointer, tcs: pointer, tes: pointer, gs: pointer, fs: pointer) -> int { function gl_program5(rt_gl_st: mut RtGlState, vs: pointer, tcs: pointer, tes: pointer, gs: pointer, fs: pointer) -> int {
let prog = gl_create_program() let prog = gl_create_program()
var ok = true var ok = true
if vs != null { let s = gl_shader(GL_VERTEX_SHADER, vs); if s == 0 { ok = false } else { gl_attach_shader(prog, s) } } if vs != null { let s = gl_shader(rt_gl_st, GL_VERTEX_SHADER, vs); if s == 0 { ok = false } else { gl_attach_shader(prog, s) } }
if tcs != null { let s = gl_shader(GL_TESS_CONTROL_SHADER, tcs); if s == 0 { ok = false } else { gl_attach_shader(prog, s) } } if tcs != null { let s = gl_shader(rt_gl_st, GL_TESS_CONTROL_SHADER, tcs); if s == 0 { ok = false } else { gl_attach_shader(prog, s) } }
if tes != null { let s = gl_shader(GL_TESS_EVALUATION_SHADER, tes); if s == 0 { ok = false } else { gl_attach_shader(prog, s) } } if tes != null { let s = gl_shader(rt_gl_st, GL_TESS_EVALUATION_SHADER, tes); if s == 0 { ok = false } else { gl_attach_shader(prog, s) } }
if gs != null { let s = gl_shader(GL_GEOMETRY_SHADER, gs); if s == 0 { ok = false } else { gl_attach_shader(prog, s) } } if gs != null { let s = gl_shader(rt_gl_st, GL_GEOMETRY_SHADER, gs); if s == 0 { ok = false } else { gl_attach_shader(prog, s) } }
if fs != null { let s = gl_shader(GL_FRAGMENT_SHADER, fs); if s == 0 { ok = false } else { gl_attach_shader(prog, s) } } if fs != null { let s = gl_shader(rt_gl_st, GL_FRAGMENT_SHADER, fs); if s == 0 { ok = false } else { gl_attach_shader(prog, s) } }
if not ok { gl_delete_program(prog); return 0 } if not ok { gl_delete_program(prog); return 0 }
gl_link_program(prog) gl_link_program(prog)
let ids = gl_scratch() let ids = gl_scratch(rt_gl_st)
gl_get_programiv(prog, GL_LINK_STATUS, ids) gl_get_programiv(prog, GL_LINK_STATUS, ids)
if ids[0] == 0 { if ids[0] == 0 {
if gl_log_buf == null { gl_log_buf = bytes(8192) } if rt_gl_st.gl_log_buf == null { rt_gl_st.gl_log_buf = bytes(8192) }
gl_get_program_info_log(prog, 8191, null, gl_log_buf) gl_get_program_info_log(prog, 8191, null, rt_gl_st.gl_log_buf)
print("program link failed:") print("program link failed:")
print(gl_log_buf) print(rt_gl_st.gl_log_buf)
gl_delete_program(prog) gl_delete_program(prog)
return 0 return 0
} }
@ -271,24 +273,24 @@ function gl_f32(v: fixed) -> int { return fx_to_f32(v) }
function gl_fixed(bits: int) -> fixed { return f32_to_fx(bits) } function gl_fixed(bits: int) -> fixed { return f32_to_fx(bits) }
# One VAO, one VBO helper: create a vertex array object and return it, bound. # One VAO, one VBO helper: create a vertex array object and return it, bound.
function gl_vao() -> int { function gl_vao(rt_gl_st: mut RtGlState) -> int {
let ids = gl_scratch() let ids = gl_scratch(rt_gl_st)
gl_gen_vertex_arrays(1, ids) gl_gen_vertex_arrays(1, ids)
gl_bind_vertex_array(ids[0]) gl_bind_vertex_array(ids[0])
return ids[0] return ids[0]
} }
function gl_buffer() -> int { function gl_buffer(rt_gl_st: mut RtGlState) -> int {
let ids = gl_scratch() let ids = gl_scratch(rt_gl_st)
gl_gen_buffers(1, ids) gl_gen_buffers(1, ids)
return ids[0] return ids[0]
} }
function gl_texture() -> int { function gl_texture(rt_gl_st: mut RtGlState) -> int {
let ids = gl_scratch() let ids = gl_scratch(rt_gl_st)
gl_gen_textures(1, ids) gl_gen_textures(1, ids)
return ids[0] return ids[0]
} }
function gl_framebuffer() -> int { function gl_framebuffer(rt_gl_st: mut RtGlState) -> int {
let ids = gl_scratch() let ids = gl_scratch(rt_gl_st)
gl_gen_framebuffers(1, ids) gl_gen_framebuffers(1, ids)
return ids[0] return ids[0]
} }

View file

@ -16,11 +16,11 @@
property Cell { x: int = 0, y: int = 0 } property Cell { x: int = 0, y: int = 0 }
function grid_abs(v: int) -> int { if v < 0 { return -v }; return v } function grid_abs(v: int) -> int { if v < 0 { return -v }; return v }
function grid_in_bounds(x: int, y: int) -> bool { return x >= 0 and y >= 0 and x < rt_mapw and y < rt_maph } function grid_in_bounds(rt_core_st: RtCoreState, x: int, y: int) -> bool { return x >= 0 and y >= 0 and x < rt_core_st.rt_mapw and y < rt_core_st.rt_maph }
# a cell blocks movement if it is out of bounds or holds the `wall` tile. # a cell blocks movement if it is out of bounds or holds the `wall` tile.
function grid_blocked(x: int, y: int, wall: int) -> bool { function grid_blocked(rt_core_st: RtCoreState, x: int, y: int, wall: int) -> bool {
if not grid_in_bounds(x, y) { return true } if not grid_in_bounds(rt_core_st, x, y) { return true }
return rt_tile(x, y) == wall return rt_tile(rt_core_st, x, y) == wall
} }
# Bresenham line from (x0,y0) to (x1,y1), inclusive — every cell it crosses. # Bresenham line from (x0,y0) to (x1,y1), inclusive — every cell it crosses.
@ -48,22 +48,22 @@ function grid_line(x0: int, y0: int, x1: int, y1: int) -> []Cell {
} }
# line of sight: true if the straight line hits no `wall` cell (endpoints incl). # line of sight: true if the straight line hits no `wall` cell (endpoints incl).
function grid_line_of_sight(x0: int, y0: int, x1: int, y1: int, wall: int) -> bool { function grid_line_of_sight(rt_core_st: RtCoreState, x0: int, y0: int, x1: int, y1: int, wall: int) -> bool {
let cells = grid_line(x0, y0, x1, y1) let cells = grid_line(x0, y0, x1, y1)
var i = 0 var i = 0
while i < len(cells) { while i < len(cells) {
if grid_blocked(cells[i].x, cells[i].y, wall) { return false } if grid_blocked(rt_core_st, cells[i].x, cells[i].y, wall) { return false }
i += 1 i += 1
} }
return true return true
} }
# 4-connected flood fill: every passable cell reachable from (sx,sy), BFS order. # 4-connected flood fill: every passable cell reachable from (sx,sy), BFS order.
function grid_flood(sx: int, sy: int, wall: int) -> []Cell { function grid_flood(rt_core_st: RtCoreState, sx: int, sy: int, wall: int) -> []Cell {
let out = new []Cell let out = new []Cell
if grid_blocked(sx, sy, wall) { return out } if grid_blocked(rt_core_st, sx, sy, wall) { return out }
let w = rt_mapw let w = rt_core_st.rt_mapw
let n = w * rt_maph let n = w * rt_core_st.rt_maph
let seen = bytes(n) let seen = bytes(n)
var i = 0 var i = 0
while i < n { seen[i] = 0; i += 1 } while i < n { seen[i] = 0; i += 1 }
@ -88,7 +88,7 @@ function grid_flood(sx: int, sy: int, wall: int) -> []Cell {
if dir == 1 { nx = cx - 1 } if dir == 1 { nx = cx - 1 }
if dir == 2 { ny = cy + 1 } if dir == 2 { ny = cy + 1 }
if dir == 3 { ny = cy - 1 } if dir == 3 { ny = cy - 1 }
if grid_in_bounds(nx, ny) and seen[ny * w + nx] == 0 and not grid_blocked(nx, ny, wall) { if grid_in_bounds(rt_core_st, nx, ny) and seen[ny * w + nx] == 0 and not grid_blocked(rt_core_st, nx, ny, wall) {
seen[ny * w + nx] = 1 seen[ny * w + nx] = 1
qx[tail] = nx; qy[tail] = ny; tail += 1 qx[tail] = nx; qy[tail] = ny; tail += 1
} }
@ -102,11 +102,11 @@ function grid_flood(sx: int, sy: int, wall: int) -> []Cell {
# heuristic. Returns the path start..goal inclusive, or an empty list if the # heuristic. Returns the path start..goal inclusive, or an empty list if the
# goal is unreachable (or start/goal is a wall). The open set is a linear scan — # goal is unreachable (or start/goal is a wall). The open set is a linear scan —
# ample for a tilemap (<= 96x64), and the heuristic keeps it near-optimal work. # ample for a tilemap (<= 96x64), and the heuristic keeps it near-optimal work.
function path_a_star(x0: int, y0: int, x1: int, y1: int, wall: int) -> []Cell { function path_a_star(rt_core_st: RtCoreState, x0: int, y0: int, x1: int, y1: int, wall: int) -> []Cell {
let out = new []Cell let out = new []Cell
if grid_blocked(x0, y0, wall) or grid_blocked(x1, y1, wall) { return out } if grid_blocked(rt_core_st, x0, y0, wall) or grid_blocked(rt_core_st, x1, y1, wall) { return out }
let w = rt_mapw let w = rt_core_st.rt_mapw
let n = w * rt_maph let n = w * rt_core_st.rt_maph
let INF = 1000000000 let INF = 1000000000
let g = words(n) # cost from start (INF = unvisited) let g = words(n) # cost from start (INF = unvisited)
let came = words(n) # parent cell index (-1 = none) let came = words(n) # parent cell index (-1 = none)
@ -146,7 +146,7 @@ function path_a_star(x0: int, y0: int, x1: int, y1: int, wall: int) -> []Cell {
if dir == 1 { nx = cx - 1 } if dir == 1 { nx = cx - 1 }
if dir == 2 { ny = cy + 1 } if dir == 2 { ny = cy + 1 }
if dir == 3 { ny = cy - 1 } if dir == 3 { ny = cy - 1 }
if grid_in_bounds(nx, ny) and not grid_blocked(nx, ny, wall) { if grid_in_bounds(rt_core_st, nx, ny) and not grid_blocked(rt_core_st, nx, ny, wall) {
let ni = ny * w + nx let ni = ny * w + nx
if closed[ni] == 0 { if closed[ni] == 0 {
let ng = g[best] + 1 let ng = g[best] + 1

View file

@ -28,50 +28,52 @@ extern function hs_cancel(slot: int) -> void = "hs_cancel"
const HTTP_SLOTS: int = 8 const HTTP_SLOTS: int = 8
var h_ready: bool = false export state RtHttpState {
var h_used: words = null # slot in use h_ready: bool = false
var h_native: words = null # 1 = backed by the platform fetch, 0 = parsed offline h_used: words = null # slot in use
var h_sent: words = null # 1 = dispatched (native) h_native: words = null # 1 = backed by the platform fetch, 0 = parsed offline
var h_res: words = null # 1 = a native fetch has been resolved into the fields below h_sent: words = null # 1 = dispatched (native)
var h_status: words = null # cached HTTP status (0 = network error / unset) h_res: words = null # 1 = a native fetch has been resolved into the fields below
var h_blen: words = null # body length h_status: words = null # cached HTTP status (0 = network error / unset)
var h_req: pointers = null # the pending NSURLRequest (native, before send) h_blen: words = null # body length
var h_body: pointers = null # body bytes (NUL-terminated); owned by the slot h_req: pointers = null # the pending NSURLRequest (native, before send)
var h_hdr: pointers = null # raw header block (parsed handles only); owned by the slot h_body: pointers = null # body bytes (NUL-terminated); owned by the slot
var h_save: words = null # 1 = the body streams to a file (Http.save_to) h_hdr: pointers = null # raw header block (parsed handles only); owned by the slot
h_save: words = null # 1 = the body streams to a file (Http.save_to)
function http_init() -> void {
if h_ready { return }
h_used = words(HTTP_SLOTS)
h_native = words(HTTP_SLOTS)
h_sent = words(HTTP_SLOTS)
h_res = words(HTTP_SLOTS)
h_status = words(HTTP_SLOTS)
h_blen = words(HTTP_SLOTS)
h_req = pointers(HTTP_SLOTS)
h_body = pointers(HTTP_SLOTS)
h_hdr = pointers(HTTP_SLOTS)
h_save = words(HTTP_SLOTS)
var i = 0
while i < HTTP_SLOTS {
h_used[i] = 0; h_native[i] = 0; h_sent[i] = 0; h_res[i] = 0
h_status[i] = 0; h_blen[i] = 0
h_req[i] = null; h_body[i] = null; h_hdr[i] = null
h_save[i] = 0
i += 1
}
h_ready = true
} }
function http_slot_alloc() -> int { function http_init(rt_http_st: mut RtHttpState) -> void {
http_init() if rt_http_st.h_ready { return }
rt_http_st.h_used = words(HTTP_SLOTS)
rt_http_st.h_native = words(HTTP_SLOTS)
rt_http_st.h_sent = words(HTTP_SLOTS)
rt_http_st.h_res = words(HTTP_SLOTS)
rt_http_st.h_status = words(HTTP_SLOTS)
rt_http_st.h_blen = words(HTTP_SLOTS)
rt_http_st.h_req = pointers(HTTP_SLOTS)
rt_http_st.h_body = pointers(HTTP_SLOTS)
rt_http_st.h_hdr = pointers(HTTP_SLOTS)
rt_http_st.h_save = words(HTTP_SLOTS)
var i = 0 var i = 0
while i < HTTP_SLOTS { while i < HTTP_SLOTS {
if h_used[i] == 0 { rt_http_st.h_used[i] = 0; rt_http_st.h_native[i] = 0; rt_http_st.h_sent[i] = 0; rt_http_st.h_res[i] = 0
h_used[i] = 1; h_native[i] = 0; h_sent[i] = 0; h_res[i] = 0 rt_http_st.h_status[i] = 0; rt_http_st.h_blen[i] = 0
h_status[i] = 0; h_blen[i] = 0 rt_http_st.h_req[i] = null; rt_http_st.h_body[i] = null; rt_http_st.h_hdr[i] = null
h_req[i] = null; h_body[i] = null; h_hdr[i] = null rt_http_st.h_save[i] = 0
h_save[i] = 0 i += 1
}
rt_http_st.h_ready = true
}
function http_slot_alloc(rt_http_st: mut RtHttpState) -> int {
http_init(rt_http_st)
var i = 0
while i < HTTP_SLOTS {
if rt_http_st.h_used[i] == 0 {
rt_http_st.h_used[i] = 1; rt_http_st.h_native[i] = 0; rt_http_st.h_sent[i] = 0; rt_http_st.h_res[i] = 0
rt_http_st.h_status[i] = 0; rt_http_st.h_blen[i] = 0
rt_http_st.h_req[i] = null; rt_http_st.h_body[i] = null; rt_http_st.h_hdr[i] = null
rt_http_st.h_save[i] = 0
return i return i
} }
i += 1 i += 1
@ -79,22 +81,22 @@ function http_slot_alloc() -> int {
return -1 return -1
} }
function http_valid(h: int) -> bool { function http_valid(rt_http_st: mut RtHttpState, h: int) -> bool {
http_init() http_init(rt_http_st)
if (h < 1) or (h > HTTP_SLOTS) { return false } if (h < 1) or (h > HTTP_SLOTS) { return false }
return h_used[h - 1] == 1 return rt_http_st.h_used[h - 1] == 1
} }
# A native request freed while its worker was still running is parked (h_used 2) rather # A native request freed while its worker was still running is parked (h_used 2) rather
# than released, so the worker never writes into a slot a new request has taken. Once the # than released, so the worker never writes into a slot a new request has taken. Once the
# worker has finished, the slot's native resources are freed and it is free again. # worker has finished, the slot's native resources are freed and it is free again.
function http_reap() -> void { function http_reap(rt_http_st: mut RtHttpState) -> void {
http_init() http_init(rt_http_st)
var i = 0 var i = 0
while i < HTTP_SLOTS { while i < HTTP_SLOTS {
if (h_used[i] == 2) and (hs_done(i) != 0) { if (rt_http_st.h_used[i] == 2) and (hs_done(i) != 0) {
hs_free(i) hs_free(i)
h_used[i] = 0 rt_http_st.h_used[i] = 0
} }
i += 1 i += 1
} }
@ -110,32 +112,32 @@ function http_cstr_len(p: pointer) -> int {
# ---- request construction (native) ----------------------------------------- # ---- request construction (native) -----------------------------------------
# Open a request without sending it, so headers / a body can be added first. # Open a request without sending it, so headers / a body can be added first.
function http_open(method: pointer, url: pointer) -> int { function http_open(rt_http_st: mut RtHttpState, method: pointer, url: pointer) -> int {
http_reap() http_reap(rt_http_st)
let req = hs_req_new(method, url) let req = hs_req_new(method, url)
if req == null { return 0 } if req == null { return 0 }
let s = http_slot_alloc() let s = http_slot_alloc(rt_http_st)
if s < 0 { return 0 } if s < 0 { return 0 }
h_req[s] = req rt_http_st.h_req[s] = req
h_native[s] = 1 rt_http_st.h_native[s] = 1
return s + 1 return s + 1
} }
function http_set_header(h: int, name: pointer, value: pointer) -> void { function http_set_header(rt_http_st: mut RtHttpState, h: int, name: pointer, value: pointer) -> void {
if not http_valid(h) { return } if not http_valid(rt_http_st, h) { return }
let s = h - 1 let s = h - 1
if (h_sent[s] == 0) and (h_req[s] != null) { hs_req_header(h_req[s], name, value) } if (rt_http_st.h_sent[s] == 0) and (rt_http_st.h_req[s] != null) { hs_req_header(rt_http_st.h_req[s], name, value) }
} }
# Attach a body of `len` bytes (use http_body for a NUL-terminated string body). # Attach a body of `len` bytes (use http_body for a NUL-terminated string body).
function http_body_n(h: int, bytes_ptr: pointer, len: int) -> void { function http_body_n(rt_http_st: mut RtHttpState, h: int, bytes_ptr: pointer, len: int) -> void {
if not http_valid(h) { return } if not http_valid(rt_http_st, h) { return }
let s = h - 1 let s = h - 1
if (h_sent[s] == 0) and (h_req[s] != null) { hs_req_body(h_req[s], bytes_ptr, len) } if (rt_http_st.h_sent[s] == 0) and (rt_http_st.h_req[s] != null) { hs_req_body(rt_http_st.h_req[s], bytes_ptr, len) }
} }
function http_body(h: int, body: pointer) -> void { function http_body(rt_http_st: mut RtHttpState, h: int, body: pointer) -> void {
http_body_n(h, body, http_cstr_len(body)) http_body_n(rt_http_st, h, body, http_cstr_len(body))
} }
# Stream the response body to the file at `path` instead of keeping it in memory. Must be # Stream the response body to the file at `path` instead of keeping it in memory. Must be
@ -143,134 +145,134 @@ function http_body(h: int, body: pointer) -> void {
# and written directly - no temporary name - so a failed or cancelled download leaves # and written directly - no temporary name - so a failed or cancelled download leaves
# whatever arrived there; the caller picks the name and renames it once the status is # whatever arrived there; the caller picks the name and renames it once the status is
# good. Http.text of such a request is empty (null) and Http.body_len is the bytes written. # good. Http.text of such a request is empty (null) and Http.body_len is the bytes written.
function http_save_to(h: int, path: pointer) -> void { function http_save_to(rt_http_st: mut RtHttpState, h: int, path: pointer) -> void {
if not http_valid(h) { return } if not http_valid(rt_http_st, h) { return }
let s = h - 1 let s = h - 1
if (h_native[s] == 1) and (h_sent[s] == 0) and (path != null) { if (rt_http_st.h_native[s] == 1) and (rt_http_st.h_sent[s] == 0) and (path != null) {
hs_save_to(s, path) hs_save_to(s, path)
h_save[s] = 1 rt_http_st.h_save[s] = 1
} }
} }
# Body bytes received so far. Live while a Http.save_to request is pending; for an # Body bytes received so far. Live while a Http.save_to request is pending; for an
# in-memory request it is 0 until the reply lands, then the body length. # in-memory request it is 0 until the reply lands, then the body length.
function http_received(h: int) -> int { function http_received(rt_http_st: mut RtHttpState, h: int) -> int {
if not http_valid(h) { return 0 } if not http_valid(rt_http_st, h) { return 0 }
let s = h - 1 let s = h - 1
if h_native[s] == 0 { return h_blen[s] } if rt_http_st.h_native[s] == 0 { return rt_http_st.h_blen[s] }
if h_sent[s] == 0 { return 0 } if rt_http_st.h_sent[s] == 0 { return 0 }
if h_save[s] == 1 { return hs_received(s) } if rt_http_st.h_save[s] == 1 { return hs_received(s) }
if h_res[s] == 1 { return h_blen[s] } if rt_http_st.h_res[s] == 1 { return rt_http_st.h_blen[s] }
return 0 return 0
} }
# The body's announced length (Content-Length), or -1 while it is not known. For a # The body's announced length (Content-Length), or -1 while it is not known. For a
# Http.save_to request it is known as soon as the headers arrive; for an in-memory request # Http.save_to request it is known as soon as the headers arrive; for an in-memory request
# it is -1 until the reply lands, then the body length. # it is -1 until the reply lands, then the body length.
function http_expected(h: int) -> int { function http_expected(rt_http_st: mut RtHttpState, h: int) -> int {
if not http_valid(h) { return -1 } if not http_valid(rt_http_st, h) { return -1 }
let s = h - 1 let s = h - 1
if h_native[s] == 0 { return h_blen[s] } if rt_http_st.h_native[s] == 0 { return rt_http_st.h_blen[s] }
if h_sent[s] == 0 { return -1 } if rt_http_st.h_sent[s] == 0 { return -1 }
if h_save[s] == 1 { return hs_expected(s) } if rt_http_st.h_save[s] == 1 { return hs_expected(s) }
if h_res[s] == 1 { return h_blen[s] } if rt_http_st.h_res[s] == 1 { return rt_http_st.h_blen[s] }
return -1 return -1
} }
# Dispatch the request onto the background worker. # Dispatch the request onto the background worker.
function http_send_req(h: int) -> void { function http_send_req(rt_http_st: mut RtHttpState, h: int) -> void {
if not http_valid(h) { return } if not http_valid(rt_http_st, h) { return }
let s = h - 1 let s = h - 1
if (h_sent[s] == 0) and (h_req[s] != null) { if (rt_http_st.h_sent[s] == 0) and (rt_http_st.h_req[s] != null) {
hs_send(s, h_req[s]) hs_send(s, rt_http_st.h_req[s])
h_sent[s] = 1 rt_http_st.h_sent[s] = 1
} }
} }
# ---- convenience one-shots ------------------------------------------------- # ---- convenience one-shots -------------------------------------------------
function http_get(url: pointer) -> int { function http_get(rt_http_st: mut RtHttpState, url: pointer) -> int {
let h = http_open("GET", url) let h = http_open(rt_http_st, "GET", url)
if h != 0 { http_send_req(h) } if h != 0 { http_send_req(rt_http_st, h) }
return h return h
} }
function http_post(url: pointer, body: pointer) -> int { function http_post(rt_http_st: mut RtHttpState, url: pointer, body: pointer) -> int {
let h = http_open("POST", url) let h = http_open(rt_http_st, "POST", url)
if h != 0 { http_body(h, body); http_send_req(h) } if h != 0 { http_body(rt_http_st, h, body); http_send_req(rt_http_st, h) }
return h return h
} }
function http_request(method: pointer, url: pointer, body: pointer) -> int { function http_request(rt_http_st: mut RtHttpState, method: pointer, url: pointer, body: pointer) -> int {
let h = http_open(method, url) let h = http_open(rt_http_st, method, url)
if h == 0 { return 0 } if h == 0 { return 0 }
if http_cstr_len(body) > 0 { http_body(h, body) } if http_cstr_len(body) > 0 { http_body(rt_http_st, h, body) }
http_send_req(h) http_send_req(rt_http_st, h)
return h return h
} }
# ---- polling + response ---------------------------------------------------- # ---- polling + response ----------------------------------------------------
# -1 while pending, 0 on a transport error, else the HTTP status code. # -1 while pending, 0 on a transport error, else the HTTP status code.
function http_poll(h: int) -> int { function http_poll(rt_http_st: mut RtHttpState, h: int) -> int {
if not http_valid(h) { return 0 } if not http_valid(rt_http_st, h) { return 0 }
let s = h - 1 let s = h - 1
if h_native[s] == 0 { return h_status[s] } # a parsed handle is ready immediately if rt_http_st.h_native[s] == 0 { return rt_http_st.h_status[s] } # a parsed handle is ready immediately
if h_sent[s] == 0 { return -1 } if rt_http_st.h_sent[s] == 0 { return -1 }
if h_res[s] == 1 { return h_status[s] } if rt_http_st.h_res[s] == 1 { return rt_http_st.h_status[s] }
if hs_done(s) == 0 { return -1 } if hs_done(s) == 0 { return -1 }
# first time we see it finished: cache the fields off the worker. # first time we see it finished: cache the fields off the worker.
h_status[s] = hs_status(s) rt_http_st.h_status[s] = hs_status(s)
h_body[s] = hs_body(s) rt_http_st.h_body[s] = hs_body(s)
h_blen[s] = hs_blen(s) rt_http_st.h_blen[s] = hs_blen(s)
h_res[s] = 1 rt_http_st.h_res[s] = 1
return h_status[s] return rt_http_st.h_status[s]
} }
function http_status_of(h: int) -> int { function http_status_of(rt_http_st: mut RtHttpState, h: int) -> int {
if not http_valid(h) { return 0 } if not http_valid(rt_http_st, h) { return 0 }
return h_status[h - 1] return rt_http_st.h_status[h - 1]
} }
function http_ok(h: int) -> bool { function http_ok(rt_http_st: mut RtHttpState, h: int) -> bool {
let st = http_status_of(h) let st = http_status_of(rt_http_st, h)
return (st >= 200) and (st < 300) return (st >= 200) and (st < 300)
} }
function http_text(h: int) -> string { function http_text(rt_http_st: mut RtHttpState, h: int) -> string {
if not http_valid(h) { return null } if not http_valid(rt_http_st, h) { return null }
return h_body[h - 1] return rt_http_st.h_body[h - 1]
} }
function http_body_len(h: int) -> int { function http_body_len(rt_http_st: mut RtHttpState, h: int) -> int {
if not http_valid(h) { return 0 } if not http_valid(rt_http_st, h) { return 0 }
return h_blen[h - 1] return rt_http_st.h_blen[h - 1]
} }
function http_header_of(h: int, name: pointer) -> string { function http_header_of(rt_http_st: mut RtHttpState, h: int, name: pointer) -> string {
if not http_valid(h) { return null } if not http_valid(rt_http_st, h) { return null }
let s = h - 1 let s = h - 1
if h_native[s] == 1 { return hs_header(s, name) } if rt_http_st.h_native[s] == 1 { return hs_header(s, name) }
return http_find_header(h_hdr[s], name) return http_find_header(rt_http_st.h_hdr[s], name)
} }
function http_close(h: int) -> void { function http_close(rt_http_st: mut RtHttpState, h: int) -> void {
if not http_valid(h) { return } if not http_valid(rt_http_st, h) { return }
let s = h - 1 let s = h - 1
if h_native[s] == 1 { if rt_http_st.h_native[s] == 1 {
if (h_sent[s] == 1) and (h_res[s] == 0) and (hs_done(s) == 0) { if (rt_http_st.h_sent[s] == 1) and (rt_http_st.h_res[s] == 0) and (hs_done(s) == 0) {
# still running: stop it, and park the slot until the worker has let go # still running: stop it, and park the slot until the worker has let go
hs_cancel(s) hs_cancel(s)
h_used[s] = 2 rt_http_st.h_used[s] = 2
h_req[s] = null; h_body[s] = null; h_hdr[s] = null rt_http_st.h_req[s] = null; rt_http_st.h_body[s] = null; rt_http_st.h_hdr[s] = null
return return
} }
hs_free(s) hs_free(s)
} }
else { else {
if h_body[s] != null { free(h_body[s]) } if rt_http_st.h_body[s] != null { free(rt_http_st.h_body[s]) }
if h_hdr[s] != null { free(h_hdr[s]) } if rt_http_st.h_hdr[s] != null { free(rt_http_st.h_hdr[s]) }
} }
h_used[s] = 0 rt_http_st.h_used[s] = 0
h_req[s] = null; h_body[s] = null; h_hdr[s] = null rt_http_st.h_req[s] = null; rt_http_st.h_body[s] = null; rt_http_st.h_hdr[s] = null
} }
# ---- the pure-Ludic response parser (transport-independent) ----------------- # ---- the pure-Ludic response parser (transport-independent) -----------------
@ -290,10 +292,10 @@ function http_lower(c: int) -> int { if (c >= 'A') and (c <= 'Z') { return c + 3
# Parse a raw HTTP/1.1 response (status line + headers + blank line + body) into # Parse a raw HTTP/1.1 response (status line + headers + blank line + body) into
# a ready handle. Useful for cached/custom transports and offline tests. # a ready handle. Useful for cached/custom transports and offline tests.
function http_parse(resp: pointer, len: int) -> int { function http_parse(rt_http_st: mut RtHttpState, resp: pointer, len: int) -> int {
let s = http_slot_alloc() let s = http_slot_alloc(rt_http_st)
if s < 0 { return 0 } if s < 0 { return 0 }
h_native[s] = 0 rt_http_st.h_native[s] = 0
# status: the token after the first space on the status line. # status: the token after the first space on the status line.
var i = 0 var i = 0
@ -301,7 +303,7 @@ function http_parse(resp: pointer, len: int) -> int {
while (i < len) and (resp[i] == ' ') { i += 1 } # skip the space(s) while (i < len) and (resp[i] == ' ') { i += 1 } # skip the space(s)
var st = 0 var st = 0
while (i < len) and http_is_digit(resp[i]) { st = (st * 10) + (resp[i] - 48); i += 1 } while (i < len) and http_is_digit(resp[i]) { st = (st * 10) + (resp[i] - 48); i += 1 }
h_status[s] = st rt_http_st.h_status[s] = st
# header/body split at the first CRLFCRLF. # header/body split at the first CRLFCRLF.
var split = -1 var split = -1
@ -323,10 +325,10 @@ function http_parse(resp: pointer, len: int) -> int {
hs += 1 hs += 1
} }
if hs > hdr_end { hs = hdr_end } if hs > hdr_end { hs = hdr_end }
h_hdr[s] = http_slice_dup(resp, hs, hdr_end) rt_http_st.h_hdr[s] = http_slice_dup(resp, hs, hdr_end)
h_body[s] = http_slice_dup(resp, body_start, len) rt_http_st.h_body[s] = http_slice_dup(resp, body_start, len)
h_blen[s] = len - body_start rt_http_st.h_blen[s] = len - body_start
if h_blen[s] < 0 { h_blen[s] = 0 } if rt_http_st.h_blen[s] < 0 { rt_http_st.h_blen[s] = 0 }
return s + 1 return s + 1
} }

View file

@ -13,25 +13,31 @@ const IMG_MAX: int = 32
const SPR_MAX: int = 160 const SPR_MAX: int = 160
const SPR_SZ: int = 16 const SPR_SZ: int = 16
var img_px: [][]int = null # IMG_MAX RGBA buffers export state RtImageState {
var img_w: words = null img_px: [][]int = null # IMG_MAX RGBA buffers
var img_h: words = null img_w: words = null
var img_n: int = 0 img_h: words = null
img_n: int = 0
spr_px: words = null # SPR_MAX * 16 * 16 RGBA pixels, one block
spr_n: int = 0
rt_file_len: int = 0
png_w: int = 0
png_h: int = 0
png_px: words = null
}
var spr_px: words = null # SPR_MAX * 16 * 16 RGBA pixels, one block
var spr_n: int = 0
function rt_image_init() -> void { function rt_image_init(rt_image_st: mut RtImageState) -> void {
img_px = new [][]int rt_image_st.img_px = new [][]int
var pi0 = 0 var pi0 = 0
while pi0 < IMG_MAX { while pi0 < IMG_MAX {
push(img_px, null) push(rt_image_st.img_px, null)
pi0 += 1 pi0 += 1
} }
img_w = words(IMG_MAX) rt_image_st.img_w = words(IMG_MAX)
img_h = words(IMG_MAX) rt_image_st.img_h = words(IMG_MAX)
spr_px = words(SPR_MAX * SPR_SZ * SPR_SZ) rt_image_st.spr_px = words(SPR_MAX * SPR_SZ * SPR_SZ)
fill(spr_px, 0, SPR_MAX * SPR_SZ * SPR_SZ * 4) fill(rt_image_st.spr_px, 0, SPR_MAX * SPR_SZ * SPR_SZ * 4)
} }
# ---- decoding ------------------------------------------------------------- # ---- decoding -------------------------------------------------------------
@ -47,7 +53,7 @@ function png_tag(b: pointer, at: int, a: int, c: int, d: int, e: int) -> bool {
return true return true
} }
function rt_read_file(path: string) -> pointer { function rt_read_file(rt_image_st: mut RtImageState, path: string) -> pointer {
let f = file_open(path, "rb") let f = file_open(path, "rb")
if (f == null) { return null } if (f == null) { return null }
file_seek(f, 0, 2) file_seek(f, 0, 2)
@ -57,23 +63,19 @@ function rt_read_file(path: string) -> pointer {
let buf = bytes(n + 8) let buf = bytes(n + 8)
file_read(f, buf, n) file_read(f, buf, n)
file_close(f) file_close(f)
rt_file_len = n rt_image_st.rt_file_len = n
return buf return buf
} }
var rt_file_len: int = 0
# decoded image is left in these; -1 width means failure # decoded image is left in these; -1 width means failure
var png_w: int = 0
var png_h: int = 0
var png_px: words = null
function rt_decode_png(path: string) -> bool { function rt_decode_png(rt_image_st: mut RtImageState, rt_inflate_st: mut RtInflateState, path: string) -> bool {
png_w = 0 rt_image_st.png_w = 0
png_h = 0 rt_image_st.png_h = 0
png_px = null rt_image_st.png_px = null
let d = rt_read_file(path) let d = rt_read_file(rt_image_st, path)
if (d == null) { return false } if (d == null) { return false }
let size = rt_file_len let size = rt_image_st.rt_file_len
if size < 8 { free(d); return false } if size < 8 { free(d); return false }
if d[0] != 137 { free(d); return false } if d[0] != 137 { free(d); return false }
if d[1] != 'P' { free(d); return false } if d[1] != 'P' { free(d); return false }
@ -136,7 +138,7 @@ function rt_decode_png(path: string) -> bool {
let stride = (w * bppbits + 7) / 8 let stride = (w * bppbits + 7) / 8
let rawlen = h * (stride + 1) let rawlen = h * (stride + 1)
let raw = bytes(rawlen + 8) let raw = bytes(rawlen + 8)
if z_uncompress(idat, idlen, raw, rawlen) < 0 { if z_uncompress(rt_inflate_st, idat, idlen, raw, rawlen) < 0 {
free(d); free(raw); free(idat) free(d); free(raw); free(idat)
return false return false
} }
@ -225,25 +227,25 @@ function rt_decode_png(path: string) -> bool {
free(raw) free(raw)
free(plte) free(plte)
free(trns) free(trns)
png_w = w rt_image_st.png_w = w
png_h = h rt_image_st.png_h = h
png_px = out rt_image_st.png_px = out
return true return true
} }
# ---- images --------------------------------------------------------------- # ---- images ---------------------------------------------------------------
function rt_image_load(path: string) -> int { function rt_image_load(rt_image_st: mut RtImageState, rt_inflate_st: mut RtInflateState, path: string) -> int {
if img_n >= IMG_MAX { return -1 } if rt_image_st.img_n >= IMG_MAX { return -1 }
if rt_decode_png(path) == false { return -1 } if rt_decode_png(rt_image_st, rt_inflate_st, path) == false { return -1 }
let id = img_n let id = rt_image_st.img_n
img_n += 1 rt_image_st.img_n += 1
img_px[id] = png_px rt_image_st.img_px[id] = rt_image_st.png_px
img_w[id] = png_w rt_image_st.img_w[id] = rt_image_st.png_w
img_h[id] = png_h rt_image_st.img_h[id] = rt_image_st.png_h
return id return id
} }
function rt_blend_px(x: int, y: int, argb: int) -> void { function rt_blend_px(rt_core_st: mut RtCoreState, x: int, y: int, argb: int) -> void {
let a = ((argb >> 24) & 255) let a = ((argb >> 24) & 255)
if a == 0 { return } if a == 0 { return }
if x < 0 { return } if x < 0 { return }
@ -254,7 +256,7 @@ function rt_blend_px(x: int, y: int, argb: int) -> void {
var g = ((argb >> 8) & 255) var g = ((argb >> 8) & 255)
var b = (argb & 255) var b = (argb & 255)
if a != 255 { if a != 255 {
let dst = rt_fb[y * rt_fbw + x] let dst = rt_core_st.rt_fb[y * rt_fbw + x]
let dr = ((dst >> 16) & 255) let dr = ((dst >> 16) & 255)
let dg = ((dst >> 8) & 255) let dg = ((dst >> 8) & 255)
let db = (dst & 255) let db = (dst & 255)
@ -262,33 +264,33 @@ function rt_blend_px(x: int, y: int, argb: int) -> void {
g = (g * a + dg * (255 - a)) / 255 g = (g * a + dg * (255 - a)) / 255
b = (b * a + db * (255 - a)) / 255 b = (b * a + db * (255 - a)) / 255
} }
rt_fb[y * rt_fbw + x] = (((r << 16) | (g << 8)) | b) rt_core_st.rt_fb[y * rt_fbw + x] = (((r << 16) | (g << 8)) | b)
} }
function rt_draw_image(id: int, dx: int, dy: int) -> void { function rt_draw_image(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, id: int, dx: int, dy: int) -> void {
if id < 0 { return } if id < 0 { return }
if id >= img_n { return } if id >= rt_image_st.img_n { return }
let w = img_w[id] let w = rt_image_st.img_w[id]
let h = img_h[id] let h = rt_image_st.img_h[id]
let s: words = img_px[id] let s: words = rt_image_st.img_px[id]
for y in 0 .. h { for y in 0 .. h {
for x in 0 .. w { for x in 0 .. w {
rt_blend_px(dx + x, dy + y, s[y * w + x]) rt_blend_px(rt_core_st, dx + x, dy + y, s[y * w + x])
} }
} }
} }
function rt_draw_image_scaled(id: int, dx: int, dy: int, dw: int, dh: int) -> void { function rt_draw_image_scaled(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, id: int, dx: int, dy: int, dw: int, dh: int) -> void {
if id < 0 { return } if id < 0 { return }
if id >= img_n { return } if id >= rt_image_st.img_n { return }
if dw <= 0 { return } if dw <= 0 { return }
if dh <= 0 { return } if dh <= 0 { return }
let w = img_w[id] let w = rt_image_st.img_w[id]
let h = img_h[id] let h = rt_image_st.img_h[id]
let s: words = img_px[id] let s: words = rt_image_st.img_px[id]
for y in 0 .. dh { for y in 0 .. dh {
for x in 0 .. dw { for x in 0 .. dw {
rt_blend_px(dx + x, dy + y, s[(y * h / dh) * w + x * w / dw]) rt_blend_px(rt_core_st, dx + x, dy + y, s[(y * h / dh) * w + x * w / dw])
} }
} }
} }
@ -303,70 +305,70 @@ function rt_9map(d: int, dsz: int, ssz: int, inset: int) -> int {
return inset + (d - inset) * (ssz - 2 * inset) / (dsz - 2 * inset) return inset + (d - inset) * (ssz - 2 * inset) / (dsz - 2 * inset)
} }
function rt_draw_9slice(id: int, dx: int, dy: int, dw: int, dh: int, inset: int) -> void { function rt_draw_9slice(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, id: int, dx: int, dy: int, dw: int, dh: int, inset: int) -> void {
if id < 0 { return } if id < 0 { return }
if id >= img_n { return } if id >= rt_image_st.img_n { return }
if dw <= 0 { return } if dw <= 0 { return }
if dh <= 0 { return } if dh <= 0 { return }
let w = img_w[id] let w = rt_image_st.img_w[id]
let h = img_h[id] let h = rt_image_st.img_h[id]
let s: words = img_px[id] let s: words = rt_image_st.img_px[id]
for y in 0 .. dh { for y in 0 .. dh {
let sy = rt_9map(y, dh, h, inset) let sy = rt_9map(y, dh, h, inset)
for x in 0 .. dw { for x in 0 .. dw {
rt_blend_px(dx + x, dy + y, s[sy * w + rt_9map(x, dw, w, inset)]) rt_blend_px(rt_core_st, dx + x, dy + y, s[sy * w + rt_9map(x, dw, w, inset)])
} }
} }
} }
# ---- sprites (16x16 art) -------------------------------------------------- # ---- sprites (16x16 art) --------------------------------------------------
function rt_png_load(path: string) -> int { function rt_png_load(rt_image_st: mut RtImageState, rt_inflate_st: mut RtInflateState, path: string) -> int {
if spr_n >= SPR_MAX { return -1 } if rt_image_st.spr_n >= SPR_MAX { return -1 }
if rt_decode_png(path) == false { return -1 } if rt_decode_png(rt_image_st, rt_inflate_st, path) == false { return -1 }
let id = spr_n let id = rt_image_st.spr_n
spr_n += 1 rt_image_st.spr_n += 1
let base = id * SPR_SZ * SPR_SZ let base = id * SPR_SZ * SPR_SZ
for y in 0 .. SPR_SZ { for y in 0 .. SPR_SZ {
for x in 0 .. SPR_SZ { for x in 0 .. SPR_SZ {
var px = 0 var px = 0
if x < png_w { if x < rt_image_st.png_w {
if y < png_h { if y < rt_image_st.png_h {
let c = png_px[y * png_w + x] let c = rt_image_st.png_px[y * rt_image_st.png_w + x]
if ((c >> 24) & 255) >= 128 { px = ((255 << 24) | (c & 16777215)) } if ((c >> 24) & 255) >= 128 { px = ((255 << 24) | (c & 16777215)) }
} }
} }
spr_px[base + y * SPR_SZ + x] = px rt_image_st.spr_px[base + y * SPR_SZ + x] = px
} }
} }
free(png_px) free(rt_image_st.png_px)
png_px = null rt_image_st.png_px = null
return id return id
} }
function rt_draw_sprite(id: int, px: int, py: int) -> void { function rt_draw_sprite(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, id: int, px: int, py: int) -> void {
if id < 0 { return } if id < 0 { return }
if id >= spr_n { return } if id >= rt_image_st.spr_n { return }
let base = id * SPR_SZ * SPR_SZ let base = id * SPR_SZ * SPR_SZ
for y in 0 .. SPR_SZ { for y in 0 .. SPR_SZ {
for x in 0 .. SPR_SZ { for x in 0 .. SPR_SZ {
let p = spr_px[base + y * SPR_SZ + x] let p = rt_image_st.spr_px[base + y * SPR_SZ + x]
if ((p >> 24) & 255) != 0 { if ((p >> 24) & 255) != 0 {
rt_put_px(px + x, py + y, (p & 16777215)) rt_put_px(rt_core_st, px + x, py + y, (p & 16777215))
} }
} }
} }
} }
function rt_draw_sprite_scaled(id: int, px: int, py: int, sc: int) -> void { function rt_draw_sprite_scaled(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, id: int, px: int, py: int, sc: int) -> void {
if id < 0 { return } if id < 0 { return }
if id >= spr_n { return } if id >= rt_image_st.spr_n { return }
if sc < 1 { return } if sc < 1 { return }
let base = id * SPR_SZ * SPR_SZ let base = id * SPR_SZ * SPR_SZ
for y in 0 .. SPR_SZ { for y in 0 .. SPR_SZ {
for x in 0 .. SPR_SZ { for x in 0 .. SPR_SZ {
let p = spr_px[base + y * SPR_SZ + x] let p = rt_image_st.spr_px[base + y * SPR_SZ + x]
if ((p >> 24) & 255) != 0 { if ((p >> 24) & 255) != 0 {
rt_fill_rect(px + x * sc, py + y * sc, sc, sc, (p & 16777215)) rt_fill_rect(rt_core_st, px + x * sc, py + y * sc, sc, sc, (p & 16777215))
} }
} }
} }
@ -377,9 +379,9 @@ function rt_draw_sprite_scaled(id: int, px: int, py: int, sc: int) -> void {
# sprite's own colours; non-zero = draw every opaque pixel in the tint colour, for # sprite's own colours; non-zero = draw every opaque pixel in the tint colour, for
# a hit-flash / team-colour silhouette). It funnels through the same put_px / # a hit-flash / team-colour silhouette). It funnels through the same put_px /
# fill_rect chokepoints, so camera / zoom / clip / blend all apply. # fill_rect chokepoints, so camera / zoom / clip / blend all apply.
function rt_draw_sprite_ex(id: int, px: int, py: int, sc: int, flip: int, tint: int) -> void { function rt_draw_sprite_ex(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, id: int, px: int, py: int, sc: int, flip: int, tint: int) -> void {
if id < 0 { return } if id < 0 { return }
if id >= spr_n { return } if id >= rt_image_st.spr_n { return }
var s = sc var s = sc
if s < 1 { s = 1 } if s < 1 { s = 1 }
let base = id * SPR_SZ * SPR_SZ let base = id * SPR_SZ * SPR_SZ
@ -387,12 +389,12 @@ function rt_draw_sprite_ex(id: int, px: int, py: int, sc: int, flip: int, tint:
for x in 0 .. SPR_SZ { for x in 0 .. SPR_SZ {
var rx = x var rx = x
if flip != 0 { rx = SPR_SZ - 1 - x } # sample mirrored, draw upright if flip != 0 { rx = SPR_SZ - 1 - x } # sample mirrored, draw upright
let p = spr_px[base + y * SPR_SZ + rx] let p = rt_image_st.spr_px[base + y * SPR_SZ + rx]
if ((p >> 24) & 255) != 0 { if ((p >> 24) & 255) != 0 {
var col = (p & 16777215) var col = (p & 16777215)
if tint != 0 { col = (tint & 16777215) } if tint != 0 { col = (tint & 16777215) }
if s == 1 { rt_put_px(px + x, py + y, col) } if s == 1 { rt_put_px(rt_core_st, px + x, py + y, col) }
else { rt_fill_rect(px + x * s, py + y * s, s, s, col) } else { rt_fill_rect(rt_core_st, px + x * s, py + y * s, s, s, col) }
} }
} }
} }
@ -406,10 +408,10 @@ function rt_hexval(c: int) -> int {
return -1 return -1
} }
function rt_sprites_load(path: string) -> void { function rt_sprites_load(rt_image_st: mut RtImageState, path: string) -> void {
let d = rt_read_file(path) let d = rt_read_file(rt_image_st, path)
if (d == null) { spr_n = 0; return } if (d == null) { rt_image_st.spr_n = 0; return }
let size = rt_file_len let size = rt_image_st.rt_file_len
let pal: words = words(128) let pal: words = words(128)
fill(pal, 0, 128 * 4) fill(pal, 0, 128 * 4)
var cur = -1 var cur = -1
@ -435,10 +437,10 @@ function rt_sprites_load(path: string) -> void {
} }
if c0 == 's' { # 's' — "spr" starts a new sprite if c0 == 's' { # 's' — "spr" starts a new sprite
cur = -1 cur = -1
if spr_n < SPR_MAX { if rt_image_st.spr_n < SPR_MAX {
cur = spr_n cur = rt_image_st.spr_n
spr_n += 1 rt_image_st.spr_n += 1
fill(offset(spr_px, cur * SPR_SZ * SPR_SZ * 4), 0, SPR_SZ * SPR_SZ * 4) fill(offset(rt_image_st.spr_px, cur * SPR_SZ * SPR_SZ * 4), 0, SPR_SZ * SPR_SZ * 4)
} }
row = 0 row = 0
} }
@ -449,7 +451,7 @@ function rt_sprites_load(path: string) -> void {
if row < SPR_SZ { if row < SPR_SZ {
for x in 0 .. SPR_SZ { for x in 0 .. SPR_SZ {
if x < len { if x < len {
spr_px[cur * SPR_SZ * SPR_SZ + row * SPR_SZ + x] = pal[(d[start + x] & 127)] rt_image_st.spr_px[cur * SPR_SZ * SPR_SZ + row * SPR_SZ + x] = pal[(d[start + x] & 127)]
} }
} }
row += 1 row += 1

View file

@ -16,47 +16,53 @@
# ============================================================================ # ============================================================================
# ---- bit reader (DEFLATE packs bits least-significant-first) --------------- # ---- bit reader (DEFLATE packs bits least-significant-first) ---------------
var z_src: pointer = null export state RtInflateState {
var z_len: int = 0 z_src: pointer = null
var z_pos: int = 0 z_len: int = 0
var z_bitbuf: int = 0 z_pos: int = 0
var z_bitcnt: int = 0 z_bitbuf: int = 0
var z_err: int = 0 z_bitcnt: int = 0
z_err: int = 0
z_lbase: words = null
z_lext: words = null
z_dbase: words = null
z_dext: words = null
}
function z_start(src: pointer, len: int) -> void { function z_start(rt_inflate_st: mut RtInflateState, src: pointer, len: int) -> void {
z_tables_once() z_tables_once(rt_inflate_st)
z_src = src rt_inflate_st.z_src = src
z_len = len rt_inflate_st.z_len = len
z_pos = 0 rt_inflate_st.z_pos = 0
z_bitbuf = 0 rt_inflate_st.z_bitbuf = 0
z_bitcnt = 0 rt_inflate_st.z_bitcnt = 0
z_err = 0 rt_inflate_st.z_err = 0
} }
# Fill the bit buffer to at least `n` bits without consuming any (n <= 16, so the # Fill the bit buffer to at least `n` bits without consuming any (n <= 16, so the
# buffer never shifts a byte past bit 15 and cannot reach the sign bit). # buffer never shifts a byte past bit 15 and cannot reach the sign bit).
function z_need(n: int) -> void { function z_need(rt_inflate_st: mut RtInflateState, n: int) -> void {
while z_bitcnt < n { while rt_inflate_st.z_bitcnt < n {
if z_pos >= z_len { return } if rt_inflate_st.z_pos >= rt_inflate_st.z_len { return }
z_bitbuf = (z_bitbuf | (z_src[z_pos] << z_bitcnt)) rt_inflate_st.z_bitbuf = (rt_inflate_st.z_bitbuf | (rt_inflate_st.z_src[rt_inflate_st.z_pos] << rt_inflate_st.z_bitcnt))
z_pos += 1 rt_inflate_st.z_pos += 1
z_bitcnt += 8 rt_inflate_st.z_bitcnt += 8
} }
} }
function z_bits(need: int) -> int { function z_bits(rt_inflate_st: mut RtInflateState, need: int) -> int {
var val = z_bitbuf var val = rt_inflate_st.z_bitbuf
while z_bitcnt < need { while rt_inflate_st.z_bitcnt < need {
if z_pos >= z_len { if rt_inflate_st.z_pos >= rt_inflate_st.z_len {
z_err = 1 rt_inflate_st.z_err = 1
return 0 return 0
} }
val = (val | (z_src[z_pos] << z_bitcnt)) val = (val | (rt_inflate_st.z_src[rt_inflate_st.z_pos] << rt_inflate_st.z_bitcnt))
z_pos += 1 rt_inflate_st.z_pos += 1
z_bitcnt += 8 rt_inflate_st.z_bitcnt += 8
} }
z_bitbuf = (val >> need) rt_inflate_st.z_bitbuf = (val >> need)
z_bitcnt -= need rt_inflate_st.z_bitcnt -= need
return (val & ((1 << need) - 1)) return (val & ((1 << need) - 1))
} }
@ -140,14 +146,14 @@ function z_table_build(table: words, lengths: words, n: int) -> void {
free(firstc) free(firstc)
} }
function z_decode(table: words) -> int { function z_decode(rt_inflate_st: mut RtInflateState, table: words) -> int {
z_need(Z_FAST) z_need(rt_inflate_st, Z_FAST)
if z_bitcnt >= Z_FAST { if rt_inflate_st.z_bitcnt >= Z_FAST {
let e = table[16 + (z_bitbuf & (Z_FASTSZ - 1))] let e = table[16 + (rt_inflate_st.z_bitbuf & (Z_FASTSZ - 1))]
if e != 0 { if e != 0 {
let l = (e >> 16) let l = (e >> 16)
z_bitbuf = (z_bitbuf >> l) rt_inflate_st.z_bitbuf = (rt_inflate_st.z_bitbuf >> l)
z_bitcnt -= l rt_inflate_st.z_bitcnt -= l
return (e & 65535) return (e & 65535)
} }
} }
@ -156,7 +162,7 @@ function z_decode(table: words) -> int {
var first = 0 var first = 0
var index = 0 var index = 0
for len in 1 .. 16 { for len in 1 .. 16 {
code = (code | z_bits(1)) code = (code | z_bits(rt_inflate_st, 1))
let count = table[len] let count = table[len]
if code - first < count { if code - first < count {
return table[Z_SYMS + index + (code - first)] return table[Z_SYMS + index + (code - first)]
@ -165,7 +171,7 @@ function z_decode(table: words) -> int {
first = ((first + count) << 1) first = ((first + count) << 1)
code = (code << 1) code = (code << 1)
} }
z_err = 1 rt_inflate_st.z_err = 1
return -1 return -1
} }
@ -198,51 +204,47 @@ function z_dist_extra(sym: int) -> int {
# The RFC tables above are pure functions of the symbol; compute them once rather # The RFC tables above are pure functions of the symbol; compute them once rather
# than dividing per match. # than dividing per match.
var z_lbase: words = null
var z_lext: words = null
var z_dbase: words = null
var z_dext: words = null
function z_tables_once() -> void { function z_tables_once(rt_inflate_st: mut RtInflateState) -> void {
if z_lbase != null { return } if rt_inflate_st.z_lbase != null { return }
z_lbase = words(29) rt_inflate_st.z_lbase = words(29)
z_lext = words(29) rt_inflate_st.z_lext = words(29)
for s in 0 .. 29 { for s in 0 .. 29 {
z_lbase[s] = z_len_base(s) rt_inflate_st.z_lbase[s] = z_len_base(s)
z_lext[s] = z_len_extra(s) rt_inflate_st.z_lext[s] = z_len_extra(s)
} }
z_dbase = words(30) rt_inflate_st.z_dbase = words(30)
z_dext = words(30) rt_inflate_st.z_dext = words(30)
for s in 0 .. 30 { for s in 0 .. 30 {
z_dbase[s] = z_dist_base(s) rt_inflate_st.z_dbase[s] = z_dist_base(s)
z_dext[s] = z_dist_extra(s) rt_inflate_st.z_dext[s] = z_dist_extra(s)
} }
} }
# ---- block decoders ------------------------------------------------------- # ---- block decoders -------------------------------------------------------
# `out` is the destination window; returns the new write position, or -1. # `out` is the destination window; returns the new write position, or -1.
function z_stored(out: pointer, at: int, cap: int) -> int { function z_stored(rt_inflate_st: mut RtInflateState, out: pointer, at: int, cap: int) -> int {
z_bitbuf = 0 rt_inflate_st.z_bitbuf = 0
z_bitcnt = 0 # stored blocks are byte-aligned rt_inflate_st.z_bitcnt = 0 # stored blocks are byte-aligned
if z_pos + 4 > z_len { return -1 } if rt_inflate_st.z_pos + 4 > rt_inflate_st.z_len { return -1 }
let n = z_src[z_pos] + (z_src[z_pos + 1] << 8) let n = rt_inflate_st.z_src[rt_inflate_st.z_pos] + (rt_inflate_st.z_src[rt_inflate_st.z_pos + 1] << 8)
z_pos += 4 # LEN then its one's complement rt_inflate_st.z_pos += 4 # LEN then its one's complement
var w = at var w = at
for i in 0 .. n { for i in 0 .. n {
if z_pos >= z_len { return -1 } if rt_inflate_st.z_pos >= rt_inflate_st.z_len { return -1 }
if w >= cap { return -1 } if w >= cap { return -1 }
out[w] = z_src[z_pos] out[w] = rt_inflate_st.z_src[rt_inflate_st.z_pos]
w += 1 w += 1
z_pos += 1 rt_inflate_st.z_pos += 1
} }
return w return w
} }
function z_codes(out: pointer, at: int, cap: int, lit: words, dist: words) -> int { function z_codes(rt_inflate_st: mut RtInflateState, out: pointer, at: int, cap: int, lit: words, dist: words) -> int {
var w = at var w = at
var sym = z_decode(lit) var sym = z_decode(rt_inflate_st, lit)
while sym != 256 { while sym != 256 {
if z_err != 0 { return -1 } if rt_inflate_st.z_err != 0 { return -1 }
if sym < 0 { return -1 } if sym < 0 { return -1 }
if sym < 256 { if sym < 256 {
if w >= cap { return -1 } if w >= cap { return -1 }
@ -252,11 +254,11 @@ function z_codes(out: pointer, at: int, cap: int, lit: words, dist: words) -> in
if sym > 256 { if sym > 256 {
let s = sym - 257 let s = sym - 257
if s >= 29 { return -1 } if s >= 29 { return -1 }
let length = z_lbase[s] + z_bits(z_lext[s]) let length = rt_inflate_st.z_lbase[s] + z_bits(rt_inflate_st, rt_inflate_st.z_lext[s])
let d = z_decode(dist) let d = z_decode(rt_inflate_st, dist)
if d < 0 { return -1 } if d < 0 { return -1 }
if d >= 30 { return -1 } if d >= 30 { return -1 }
let distance = z_dbase[d] + z_bits(z_dext[d]) let distance = rt_inflate_st.z_dbase[d] + z_bits(rt_inflate_st, rt_inflate_st.z_dext[d])
if distance > w { return -1 } if distance > w { return -1 }
if w + length > cap { return -1 } # bounds once, not per byte if w + length > cap { return -1 } # bounds once, not per byte
var sp = w - distance var sp = w - distance
@ -268,7 +270,7 @@ function z_codes(out: pointer, at: int, cap: int, lit: words, dist: words) -> in
k += 1 k += 1
} }
} }
sym = z_decode(lit) sym = z_decode(rt_inflate_st, lit)
} }
return w return w
} }
@ -285,10 +287,10 @@ function z_fixed_tables(lit: words, dist: words) -> void {
free(lengths) free(lengths)
} }
function z_dynamic_tables(lit: words, dist: words) -> int { function z_dynamic_tables(rt_inflate_st: mut RtInflateState, lit: words, dist: words) -> int {
let nlen = z_bits(5) + 257 let nlen = z_bits(rt_inflate_st, 5) + 257
let ndist = z_bits(5) + 1 let ndist = z_bits(rt_inflate_st, 5) + 1
let ncode = z_bits(4) + 4 let ncode = z_bits(rt_inflate_st, 4) + 4
if nlen > 286 { return 0 } if nlen > 286 { return 0 }
if ndist > 30 { return 0 } if ndist > 30 { return 0 }
@ -298,14 +300,14 @@ function z_dynamic_tables(lit: words, dist: words) -> int {
# 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 — biased by '0' so it is one literal # 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 — biased by '0' so it is one literal
let order = "@AB08796:5;4<3=2>1?" let order = "@AB08796:5;4<3=2>1?"
for i in 0 .. ncode { for i in 0 .. ncode {
lengths[order[i] - 48] = z_bits(3) lengths[order[i] - 48] = z_bits(rt_inflate_st, 3)
} }
let clen = z_table_new(19) let clen = z_table_new(19)
z_table_build(clen, lengths, 19) z_table_build(clen, lengths, 19)
var n = 0 var n = 0
while n < nlen + ndist { while n < nlen + ndist {
let sym = z_decode(clen) let sym = z_decode(rt_inflate_st, clen)
if sym < 0 { return 0 } if sym < 0 { return 0 }
if sym < 16 { if sym < 16 {
lengths[n] = sym lengths[n] = sym
@ -317,10 +319,10 @@ function z_dynamic_tables(lit: words, dist: words) -> int {
if sym == 16 { if sym == 16 {
if n == 0 { return 0 } if n == 0 { return 0 }
prev = lengths[n - 1] prev = lengths[n - 1]
rep = 3 + z_bits(2) rep = 3 + z_bits(rt_inflate_st, 2)
} }
if sym == 17 { rep = 3 + z_bits(3) } if sym == 17 { rep = 3 + z_bits(rt_inflate_st, 3) }
if sym == 18 { rep = 11 + z_bits(7) } if sym == 18 { rep = 11 + z_bits(rt_inflate_st, 7) }
for k in 0 .. rep { for k in 0 .. rep {
if n < 320 { if n < 320 {
lengths[n] = prev lengths[n] = prev
@ -341,24 +343,24 @@ function z_dynamic_tables(lit: words, dist: words) -> int {
} }
# Inflate a raw DEFLATE stream. Returns bytes written, or -1. # Inflate a raw DEFLATE stream. Returns bytes written, or -1.
function z_inflate(src: pointer, len: int, out: pointer, cap: int) -> int { function z_inflate(rt_inflate_st: mut RtInflateState, src: pointer, len: int, out: pointer, cap: int) -> int {
z_start(src, len) z_start(rt_inflate_st, src, len)
let lit = z_table_new(288) let lit = z_table_new(288)
let dist = z_table_new(30) let dist = z_table_new(30)
var w = 0 var w = 0
var final = 0 var final = 0
while final == 0 { while final == 0 {
final = z_bits(1) final = z_bits(rt_inflate_st, 1)
let btype = z_bits(2) let btype = z_bits(rt_inflate_st, 2)
if z_err != 0 { return -1 } if rt_inflate_st.z_err != 0 { return -1 }
if btype == 0 { w = z_stored(out, w, cap) } if btype == 0 { w = z_stored(rt_inflate_st, out, w, cap) }
if btype == 1 { if btype == 1 {
z_fixed_tables(lit, dist) z_fixed_tables(lit, dist)
w = z_codes(out, w, cap, lit, dist) w = z_codes(rt_inflate_st, out, w, cap, lit, dist)
} }
if btype == 2 { if btype == 2 {
if z_dynamic_tables(lit, dist) == 0 { return -1 } if z_dynamic_tables(rt_inflate_st, lit, dist) == 0 { return -1 }
w = z_codes(out, w, cap, lit, dist) w = z_codes(rt_inflate_st, out, w, cap, lit, dist)
} }
if btype == 3 { return -1 } if btype == 3 { return -1 }
if w < 0 { return -1 } if w < 0 { return -1 }
@ -369,11 +371,11 @@ function z_inflate(src: pointer, len: int, out: pointer, cap: int) -> int {
} }
# zlib wrapper (RFC 1950): two header bytes, then DEFLATE, then Adler-32. # zlib wrapper (RFC 1950): two header bytes, then DEFLATE, then Adler-32.
function z_uncompress(src: pointer, len: int, out: pointer, cap: int) -> int { function z_uncompress(rt_inflate_st: mut RtInflateState, src: pointer, len: int, out: pointer, cap: int) -> int {
if len < 2 { return -1 } if len < 2 { return -1 }
let cmf = src[0] let cmf = src[0]
if (cmf & 15) != 8 { return -1 } if (cmf & 15) != 8 { return -1 }
return z_inflate(offset(src, 2), len - 2, out, cap) return z_inflate(rt_inflate_st, offset(src, 2), len - 2, out, cap)
} }
# gzip framing (RFC 1952): a 10-byte header (magic 1f 8b, CM=8, FLG, 4-byte MTIME, # gzip framing (RFC 1952): a 10-byte header (magic 1f 8b, CM=8, FLG, 4-byte MTIME,
@ -382,7 +384,7 @@ function z_uncompress(src: pointer, len: int, out: pointer, cap: int) -> int {
# the header + optional fields, inflate the body, and ignore the trailer — the # the header + optional fields, inflate the body, and ignore the trailer — the
# CRC is a redundancy check, not needed to decode (PNG likewise ignores ancillary # CRC is a redundancy check, not needed to decode (PNG likewise ignores ancillary
# CRCs). Returns bytes written, or -1. # CRCs). Returns bytes written, or -1.
function z_gunzip(src: pointer, len: int, out: pointer, cap: int) -> int { function z_gunzip(rt_inflate_st: mut RtInflateState, src: pointer, len: int, out: pointer, cap: int) -> int {
if len < 18 { return -1 } # 10 header + 8 trailer minimum if len < 18 { return -1 } # 10 header + 8 trailer minimum
if src[0] != 31 { return -1 } # 0x1f if src[0] != 31 { return -1 } # 0x1f
if src[1] != 139 { return -1 } # 0x8b if src[1] != 139 { return -1 } # 0x8b
@ -404,5 +406,5 @@ function z_gunzip(src: pointer, len: int, out: pointer, cap: int) -> int {
} }
if (flg & 2) != 0 { pos += 2 } # FHCRC: 2-byte header CRC if (flg & 2) != 0 { pos += 2 } # FHCRC: 2-byte header CRC
if pos + 8 > len { return -1 } if pos + 8 > len { return -1 }
return z_inflate(offset(src, pos), len - pos - 8, out, cap) return z_inflate(rt_inflate_st, offset(src, pos), len - pos - 8, out, cap)
} }

View file

@ -24,62 +24,91 @@ const INPUT_MAX_ACT: int = 32 # named actions
const INPUT_MAX_KEYS: int = 4 # physical keys bound per action const INPUT_MAX_KEYS: int = 4 # physical keys bound per action
const INPUT_REC_CAP: int = 8192 # recordable frames const INPUT_REC_CAP: int = 8192 # recordable frames
var input_names: pointers = null # action name per slot (0..input_nact) export state RtInputState {
var input_keys: words = null # INPUT_MAX_ACT * INPUT_MAX_KEYS key codes (0 = empty) input_names: pointers = null # action name per slot (0..input_nact)
var input_pads: words = null # INPUT_MAX_ACT * INPUT_MAX_KEYS pad buttons, stored +1 (0 = empty) — #83 input_keys: words = null # INPUT_MAX_ACT * INPUT_MAX_KEYS key codes (0 = empty)
var input_nact: int = 0 input_pads: words = null # INPUT_MAX_ACT * INPUT_MAX_KEYS pad buttons, stored +1 (0 = empty) — #83
input_nact: int = 0
input_frame: int = 0 # the key polled this frame
input_last: int = 0 # the key polled last frame (for edges)
input_mode: int = 0 # 0 = live, 1 = record, 2 = replay
input_rec: words = null # recorded key per frame
input_recn: int = 0 # frames recorded
input_pos: int = 0 # replay / record cursor
in_have_frame_driver: bool = false
in_ready: bool = false
in_sim: words = null # simulated held set (Input.press / release) — persists
in_dev: words = null # platform / polled held set — refreshed each poll
in_held: words = null # committed effective set this frame (what reads see)
in_prev: words = null # committed set last frame (for edges)
in_mx: int = 0 # current x/y
in_my: int = 0
in_mx0: int = 0 # x/y at the previous frame (for the delta)
in_my0: int = 0
in_mdx: int = 0 # delta this frame
in_mdy: int = 0
in_rdx: int = 0 # the raw motion the platform reports while captured
in_rdy: int = 0
in_cursor_mode: int = 0
in_mouse_rebase: bool = true
in_mbtn: int = 0 # button bitmask (bit 0 left, 1 right, 2 middle)
in_wheel: int = 0 # wheel delta this frame
in_pad_conn: words = null # IN_PADS
in_pad_btn: words = null # IN_PADS
in_pad_btn0: words = null # IN_PADS — pad button mask last frame (edges) — #83
in_pad_axis: fixeds = null # IN_PADS * IN_AXES
in_touch_on: words = null # IN_TOUCH
in_touch_x: words = null # IN_TOUCH
in_touch_y: words = null # IN_TOUCH
in_tape: words = null # INPUT_REC_CAP * IN_STRIDE
in_text_buf: words = null
}
var input_frame: int = 0 # the key polled this frame
var input_last: int = 0 # the key polled last frame (for edges)
var input_mode: int = 0 # 0 = live, 1 = record, 2 = replay
var input_rec: words = null # recorded key per frame
var input_recn: int = 0 # frames recorded
var input_pos: int = 0 # replay / record cursor
function input_init() -> void { function input_init(rt_input_st: mut RtInputState) -> void {
if input_names == null { if rt_input_st.input_names == null {
input_names = pointers(INPUT_MAX_ACT) # a pointer (8 bytes) per action slot rt_input_st.input_names = pointers(INPUT_MAX_ACT) # a pointer (8 bytes) per action slot
input_keys = words(INPUT_MAX_ACT * INPUT_MAX_KEYS) rt_input_st.input_keys = words(INPUT_MAX_ACT * INPUT_MAX_KEYS)
input_pads = words(INPUT_MAX_ACT * INPUT_MAX_KEYS) # #83 pad buttons per action (+1 encoded) rt_input_st.input_pads = words(INPUT_MAX_ACT * INPUT_MAX_KEYS) # #83 pad buttons per action (+1 encoded)
} }
} }
# slot of the action `name`, or -1. Names compare by byte-string equality. # slot of the action `name`, or -1. Names compare by byte-string equality.
function input_find(name: pointer) -> int { function input_find(rt_input_st: mut RtInputState, name: pointer) -> int {
input_init() input_init(rt_input_st)
var i = 0 var i = 0
while i < input_nact { while i < rt_input_st.input_nact {
if input_names[i] == name { return i } if rt_input_st.input_names[i] == name { return i }
i += 1 i += 1
} }
return -1 return -1
} }
# get-or-create the slot for `name`. # get-or-create the slot for `name`.
function input_slot(name: pointer) -> int { function input_slot(rt_input_st: mut RtInputState, name: pointer) -> int {
let f = input_find(name) let f = input_find(rt_input_st, name)
if f >= 0 { return f } if f >= 0 { return f }
if input_nact >= INPUT_MAX_ACT { return INPUT_MAX_ACT - 1 } # silently reuse the last slot when full if rt_input_st.input_nact >= INPUT_MAX_ACT { return INPUT_MAX_ACT - 1 } # silently reuse the last slot when full
let s = input_nact let s = rt_input_st.input_nact
input_names[s] = name rt_input_st.input_names[s] = name
input_nact += 1 rt_input_st.input_nact += 1
return s return s
} }
# bind physical `key` to the named action, creating the action if new. A key # bind physical `key` to the named action, creating the action if new. A key
# already bound to the action is left as-is (idempotent). # already bound to the action is left as-is (idempotent).
function input_bind(name: pointer, key: int) -> void { function input_bind(rt_input_st: mut RtInputState, name: pointer, key: int) -> void {
let s = input_slot(name) let s = input_slot(rt_input_st, name)
let base = s * INPUT_MAX_KEYS let base = s * INPUT_MAX_KEYS
var i = 0 var i = 0
while i < INPUT_MAX_KEYS { while i < INPUT_MAX_KEYS {
if input_keys[base + i] == key { return } # already bound if rt_input_st.input_keys[base + i] == key { return } # already bound
i += 1 i += 1
} }
i = 0 i = 0
while i < INPUT_MAX_KEYS { while i < INPUT_MAX_KEYS {
if input_keys[base + i] == 0 { input_keys[base + i] = key; return } if rt_input_st.input_keys[base + i] == 0 { rt_input_st.input_keys[base + i] = key; return }
i += 1 i += 1
} }
} }
@ -87,56 +116,56 @@ function input_bind(name: pointer, key: int) -> void {
# #83 — a *default* binding: bind `key` only if the action has no key bound yet. # #83 — a *default* binding: bind `key` only if the action has no key bound yet.
# A game ships its defaults with Input.action in Boot; a player's later Input.rebind # A game ships its defaults with Input.action in Boot; a player's later Input.rebind
# (or a loaded key-map) is not clobbered, and re-running the defaults is idempotent. # (or a loaded key-map) is not clobbered, and re-running the defaults is idempotent.
function input_default(name: pointer, key: int) -> void { function input_default(rt_input_st: mut RtInputState, name: pointer, key: int) -> void {
let s = input_slot(name) let s = input_slot(rt_input_st, name)
let base = s * INPUT_MAX_KEYS let base = s * INPUT_MAX_KEYS
var i = 0 var i = 0
while i < INPUT_MAX_KEYS { while i < INPUT_MAX_KEYS {
if input_keys[base + i] != 0 { return } # already has a binding — keep it if rt_input_st.input_keys[base + i] != 0 { return } # already has a binding — keep it
i += 1 i += 1
} }
input_keys[base] = key rt_input_st.input_keys[base] = key
} }
# #83 — device-agnostic actions: also fire the named action from a gamepad button. # #83 — device-agnostic actions: also fire the named action from a gamepad button.
# Buttons are stored +1 so 0 stays the empty marker. The same action can carry both # Buttons are stored +1 so 0 stays the empty marker. The same action can carry both
# keyboard keys (input_bind / input_default) and pad buttons; a read fires on either. # keyboard keys (input_bind / input_default) and pad buttons; a read fires on either.
function input_bind_pad(name: pointer, button: int) -> void { function input_bind_pad(rt_input_st: mut RtInputState, name: pointer, button: int) -> void {
let s = input_slot(name) let s = input_slot(rt_input_st, name)
let base = s * INPUT_MAX_KEYS let base = s * INPUT_MAX_KEYS
var i = 0 var i = 0
while i < INPUT_MAX_KEYS { while i < INPUT_MAX_KEYS {
if input_pads[base + i] == (button + 1) { return } # already bound if rt_input_st.input_pads[base + i] == (button + 1) { return } # already bound
i += 1 i += 1
} }
i = 0 i = 0
while i < INPUT_MAX_KEYS { while i < INPUT_MAX_KEYS {
if input_pads[base + i] == 0 { input_pads[base + i] = button + 1; return } if rt_input_st.input_pads[base + i] == 0 { rt_input_st.input_pads[base + i] = button + 1; return }
i += 1 i += 1
} }
} }
# runtime rebinding: replace `oldkey` with `newkey` on the named action. A no-op # runtime rebinding: replace `oldkey` with `newkey` on the named action. A no-op
# if the action or the old key is not found. # if the action or the old key is not found.
function input_rebind(name: pointer, oldkey: int, newkey: int) -> void { function input_rebind(rt_input_st: mut RtInputState, name: pointer, oldkey: int, newkey: int) -> void {
let s = input_find(name) let s = input_find(rt_input_st, name)
if s < 0 { return } if s < 0 { return }
let base = s * INPUT_MAX_KEYS let base = s * INPUT_MAX_KEYS
var i = 0 var i = 0
while i < INPUT_MAX_KEYS { while i < INPUT_MAX_KEYS {
if input_keys[base + i] == oldkey { input_keys[base + i] = newkey; return } if rt_input_st.input_keys[base + i] == oldkey { rt_input_st.input_keys[base + i] = newkey; return }
i += 1 i += 1
} }
} }
# does key `k` (0 = none) fire the action in slot `s`? # does key `k` (0 = none) fire the action in slot `s`?
function input_slot_has(s: int, k: int) -> bool { function input_slot_has(rt_input_st: RtInputState, s: int, k: int) -> bool {
if s < 0 { return false } if s < 0 { return false }
if k == 0 { return false } if k == 0 { return false }
let base = s * INPUT_MAX_KEYS let base = s * INPUT_MAX_KEYS
var i = 0 var i = 0
while i < INPUT_MAX_KEYS { while i < INPUT_MAX_KEYS {
if input_keys[base + i] == k { return true } if rt_input_st.input_keys[base + i] == k { return true }
i += 1 i += 1
} }
return false return false
@ -149,44 +178,43 @@ function input_slot_has(s: int, k: int) -> bool {
# commit copied in_held into in_prev twice, which destroyed the key_pressed / # commit copied in_held into in_prev twice, which destroyed the key_pressed /
# key_released edges (in_prev ended up equal to in_held). An entry-driven harness # key_released edges (in_prev ended up equal to in_held). An entry-driven harness
# has no loop, so the flag stays false and each Input.poll commits a frame as before. # has no loop, so the flag stays false and each Input.poll commits a frame as before.
var in_have_frame_driver: bool = false
# The actual per-frame input read: read the live key (or a recorded one), advance # The actual per-frame input read: read the live key (or a recorded one), advance
# the record/replay tape, and rebuild the multi-key device layer (held keys, mouse, # the record/replay tape, and rebuild the multi-key device layer (held keys, mouse,
# gamepad — #50). Returns the frame's key. # gamepad — #50). Returns the frame's key.
function input_commit() -> int { function input_commit(rt_core_st: mut RtCoreState, rt_input_st: mut RtInputState) -> int {
input_last = input_frame rt_input_st.input_last = rt_input_st.input_frame
if input_mode == 2 { # replay if rt_input_st.input_mode == 2 { # replay
var k = 0 var k = 0
if input_pos < input_recn { k = input_rec[input_pos]; input_pos += 1 } if rt_input_st.input_pos < rt_input_st.input_recn { k = rt_input_st.input_rec[rt_input_st.input_pos]; rt_input_st.input_pos += 1 }
input_frame = k rt_input_st.input_frame = k
input_device_commit(k, 1) # rebuild the device state from the tape input_device_commit(rt_input_st, k, 1) # rebuild the device state from the tape
return k return k
} }
let k = rt_poll() let k = rt_poll(rt_core_st)
if input_mode == 1 { # record if rt_input_st.input_mode == 1 { # record
if input_rec == null { input_rec = words(INPUT_REC_CAP) } if rt_input_st.input_rec == null { rt_input_st.input_rec = words(INPUT_REC_CAP) }
if input_recn < INPUT_REC_CAP { input_rec[input_recn] = k; input_recn += 1 } if rt_input_st.input_recn < INPUT_REC_CAP { rt_input_st.input_rec[rt_input_st.input_recn] = k; rt_input_st.input_recn += 1 }
} }
input_frame = k rt_input_st.input_frame = k
input_device_commit(k, 0) input_device_commit(rt_input_st, k, 0)
return k return k
} }
# Called by the generated frame loop once per frame (#83). Marks that a loop is # Called by the generated frame loop once per frame (#83). Marks that a loop is
# driving input so a later manual Input.poll this frame does not double-commit. # driving input so a later manual Input.poll this frame does not double-commit.
function input_drive() -> int { function input_drive(rt_core_st: mut RtCoreState, rt_input_st: mut RtInputState) -> int {
in_have_frame_driver = true rt_input_st.in_have_frame_driver = true
return input_commit() return input_commit(rt_core_st, rt_input_st)
} }
# Input.poll — the single per-frame input read a game can call by hand. In a # Input.poll — the single per-frame input read a game can call by hand. In a
# frame-loop game the loop already drove input this frame (input_drive), so this is # frame-loop game the loop already drove input this frame (input_drive), so this is
# a no-op that returns the frame's key; in an entry-driven harness (no loop) it # a no-op that returns the frame's key; in an entry-driven harness (no loop) it
# commits a frame of input each call, exactly as before. # commits a frame of input each call, exactly as before.
function input_poll() -> int { function input_poll(rt_core_st: mut RtCoreState, rt_input_st: mut RtInputState) -> int {
if in_have_frame_driver { return input_frame } if rt_input_st.in_have_frame_driver { return rt_input_st.input_frame }
return input_commit() return input_commit(rt_core_st, rt_input_st)
} }
# ============================================================================ # ============================================================================
@ -211,53 +239,28 @@ const IN_AXES: int = 4 # axes per pad (2 sticks: lx, ly, rx, ry)
const IN_TOUCH: int = 8 # simultaneous touch points const IN_TOUCH: int = 8 # simultaneous touch points
const IN_STRIDE: int = 12 # tape words / frame: 8 held + mx, my, buttons, wheel const IN_STRIDE: int = 12 # tape words / frame: 8 held + mx, my, buttons, wheel
var in_ready: bool = false
var in_sim: words = null # simulated held set (Input.press / release) — persists
var in_dev: words = null # platform / polled held set — refreshed each poll
var in_held: words = null # committed effective set this frame (what reads see)
var in_prev: words = null # committed set last frame (for edges)
# mouse # mouse
var in_mx: int = 0 # current x/y
var in_my: int = 0
var in_mx0: int = 0 # x/y at the previous frame (for the delta)
var in_my0: int = 0
var in_mdx: int = 0 # delta this frame
var in_mdy: int = 0
var in_rdx: int = 0 # the raw motion the platform reports while captured
var in_rdy: int = 0
var in_cursor_mode: int = 0
# the next commit reports no mouse delta: true before the first position is read (the previous one # the next commit reports no mouse delta: true before the first position is read (the previous one
# is not a position, it is 0,0) and after a cursor-mode change (the position source switches between # is not a position, it is 0,0) and after a cursor-mode change (the position source switches between
# the virtual reticle and the real cursor, which are unrelated points) # the virtual reticle and the real cursor, which are unrelated points)
var in_mouse_rebase: bool = true
var in_mbtn: int = 0 # button bitmask (bit 0 left, 1 right, 2 middle)
var in_wheel: int = 0 # wheel delta this frame
# gamepads: connected flag, button bitmask, and IN_AXES fixed axes each # gamepads: connected flag, button bitmask, and IN_AXES fixed axes each
var in_pad_conn: words = null # IN_PADS
var in_pad_btn: words = null # IN_PADS
var in_pad_btn0: words = null # IN_PADS — pad button mask last frame (edges) — #83
var in_pad_axis: fixeds = null # IN_PADS * IN_AXES
# touch points: active flag, x, y each # touch points: active flag, x, y each
var in_touch_on: words = null # IN_TOUCH
var in_touch_x: words = null # IN_TOUCH
var in_touch_y: words = null # IN_TOUCH
# full-state tape (held + mouse), recorded / replayed alongside the key tape # full-state tape (held + mouse), recorded / replayed alongside the key tape
var in_tape: words = null # INPUT_REC_CAP * IN_STRIDE
function in_init() -> void { function in_init(rt_input_st: mut RtInputState) -> void {
if in_ready { return } if rt_input_st.in_ready { return }
in_sim = words(IN_WORDS) rt_input_st.in_sim = words(IN_WORDS)
in_dev = words(IN_WORDS) rt_input_st.in_dev = words(IN_WORDS)
in_held = words(IN_WORDS) rt_input_st.in_held = words(IN_WORDS)
in_prev = words(IN_WORDS) rt_input_st.in_prev = words(IN_WORDS)
in_pad_conn = words(IN_PADS) rt_input_st.in_pad_conn = words(IN_PADS)
in_pad_btn = words(IN_PADS) rt_input_st.in_pad_btn = words(IN_PADS)
in_pad_btn0 = words(IN_PADS) rt_input_st.in_pad_btn0 = words(IN_PADS)
in_pad_axis = fixeds(IN_PADS * IN_AXES) rt_input_st.in_pad_axis = fixeds(IN_PADS * IN_AXES)
in_touch_on = words(IN_TOUCH) rt_input_st.in_touch_on = words(IN_TOUCH)
in_touch_x = words(IN_TOUCH) rt_input_st.in_touch_x = words(IN_TOUCH)
in_touch_y = words(IN_TOUCH) rt_input_st.in_touch_y = words(IN_TOUCH)
in_ready = true rt_input_st.in_ready = true
} }
# ---- key-set bit helpers --------------------------------------------------- # ---- key-set bit helpers ---------------------------------------------------
@ -278,38 +281,38 @@ function in_set_or(dst: words, a: words, b: words) -> void { var i = 0; while i
# ---- the per-frame device commit (called by input_poll) -------------------- # ---- the per-frame device commit (called by input_poll) --------------------
# Snapshot the committed set into prev (for edges), refresh the platform set (or # Snapshot the committed set into prev (for edges), refresh the platform set (or
# rebuild it from the tape on replay), then recombine into the committed set. # rebuild it from the tape on replay), then recombine into the committed set.
function input_device_commit(k: int, replaying: int) -> void { function input_device_commit(rt_input_st: mut RtInputState, k: int, replaying: int) -> void {
in_init() in_init(rt_input_st)
in_set_copy(in_prev, in_held) # last frame's committed set in_set_copy(rt_input_st.in_prev, rt_input_st.in_held) # last frame's committed set
# #83: snapshot last frame's pad-button masks for the just_pressed/released edges. # #83: snapshot last frame's pad-button masks for the just_pressed/released edges.
# Taken before the platform refresh (win_pad, below, runs after this), so it holds # Taken before the platform refresh (win_pad, below, runs after this), so it holds
# the previous frame's committed value against which this frame's edge is measured. # the previous frame's committed value against which this frame's edge is measured.
var pj = 0 var pj = 0
while pj < IN_PADS { in_pad_btn0[pj] = in_pad_btn[pj]; pj += 1 } while pj < IN_PADS { rt_input_st.in_pad_btn0[pj] = rt_input_st.in_pad_btn[pj]; pj += 1 }
if replaying == 1 { if replaying == 1 {
# rebuild the platform set + mouse from the tape; sim/injection is ignored so # rebuild the platform set + mouse from the tape; sim/injection is ignored so
# a replay is authoritative (as #7's key replay ignores the live device). # a replay is authoritative (as #7's key replay ignores the live device).
let base = (input_pos - 1) * IN_STRIDE let base = (rt_input_st.input_pos - 1) * IN_STRIDE
if (in_tape != null) and (base >= 0) { if (rt_input_st.in_tape != null) and (base >= 0) {
var i = 0 var i = 0
while i < IN_WORDS { in_dev[i] = in_tape[base + i]; i += 1 } while i < IN_WORDS { rt_input_st.in_dev[i] = rt_input_st.in_tape[base + i]; i += 1 }
in_mx = in_tape[base + 8] rt_input_st.in_mx = rt_input_st.in_tape[base + 8]
in_my = in_tape[base + 9] rt_input_st.in_my = rt_input_st.in_tape[base + 9]
in_mbtn = in_tape[base + 10] rt_input_st.in_mbtn = rt_input_st.in_tape[base + 10]
in_wheel = in_tape[base + 11] rt_input_st.in_wheel = rt_input_st.in_tape[base + 11]
} }
in_set_copy(in_held, in_dev) in_set_copy(rt_input_st.in_held, rt_input_st.in_dev)
} else { } else {
# live: fill the platform set from the window (real simultaneous keys) or, # live: fill the platform set from the window (real simultaneous keys) or,
# headless, from the single polled key. Injection (in_sim) is OR-ed on top. # headless, from the single polled key. Injection (in_sim) is OR-ed on top.
if is_windowed() { if is_windowed() {
win_held(in_dev) win_held(rt_input_st.in_dev)
let mbuf = words(6) # [x, y, button-mask, wheel, raw dx, raw dy] let mbuf = words(6) # [x, y, button-mask, wheel, raw dx, raw dy]
mbuf[4] = 0; mbuf[5] = 0 mbuf[4] = 0; mbuf[5] = 0
win_mouse(mbuf) win_mouse(mbuf)
in_mx = mbuf[0]; in_my = mbuf[1]; in_mbtn = mbuf[2]; in_wheel = mbuf[3] rt_input_st.in_mx = mbuf[0]; rt_input_st.in_my = mbuf[1]; rt_input_st.in_mbtn = mbuf[2]; rt_input_st.in_wheel = mbuf[3]
in_rdx = mbuf[4]; in_rdy = mbuf[5] rt_input_st.in_rdx = mbuf[4]; rt_input_st.in_rdy = mbuf[5]
# #51 — feed the platform gamepad + touch state into the same buffers the # #51 — feed the platform gamepad + touch state into the same buffers the
# read APIs use. Each is windowed-only glue (win_pad / win_touch are DCE'd # read APIs use. Each is windowed-only glue (win_pad / win_touch are DCE'd
# in a headless build); on hardware they overwrite the injected state. # in a headless build); on hardware they overwrite the injected state.
@ -318,7 +321,7 @@ function input_device_commit(k: int, replaying: int) -> void {
var pi = 0 var pi = 0
while pi < IN_PADS { while pi < IN_PADS {
let pb = pi * 6 let pb = pi * 6
input_set_pad(pi, pbuf[pb] != 0, pbuf[pb + 1], input_set_pad(rt_input_st, pi, pbuf[pb] != 0, pbuf[pb + 1],
as_fixed(pbuf[pb + 2]), as_fixed(pbuf[pb + 3]), as_fixed(pbuf[pb + 2]), as_fixed(pbuf[pb + 3]),
as_fixed(pbuf[pb + 4]), as_fixed(pbuf[pb + 5])) as_fixed(pbuf[pb + 4]), as_fixed(pbuf[pb + 5]))
pi += 1 pi += 1
@ -328,49 +331,49 @@ function input_device_commit(k: int, replaying: int) -> void {
var ti = 0 var ti = 0
while ti < IN_TOUCH { while ti < IN_TOUCH {
let tb = ti * 3 let tb = ti * 3
input_set_touch(ti, tbuf[tb + 1], tbuf[tb + 2], tbuf[tb] != 0) input_set_touch(rt_input_st, ti, tbuf[tb + 1], tbuf[tb + 2], tbuf[tb] != 0)
ti += 1 ti += 1
} }
} else { } else {
in_set_clear(in_dev) in_set_clear(rt_input_st.in_dev)
if k > 0 { in_bit_set(in_dev, k, true) } if k > 0 { in_bit_set(rt_input_st.in_dev, k, true) }
} }
in_set_or(in_held, in_dev, in_sim) in_set_or(rt_input_st.in_held, rt_input_st.in_dev, rt_input_st.in_sim)
if input_mode == 1 { input_device_record() } # snapshot the frame into the tape if rt_input_st.input_mode == 1 { input_device_record(rt_input_st) } # snapshot the frame into the tape
} }
# mouse delta vs the previous frame's committed position (in_mx set by the # mouse delta vs the previous frame's committed position (in_mx set by the
# platform above when windowed, by Input.set_mouse before this poll otherwise). # platform above when windowed, by Input.set_mouse before this poll otherwise).
in_mdx = in_mx - in_mx0 rt_input_st.in_mdx = rt_input_st.in_mx - rt_input_st.in_mx0
in_mdy = in_my - in_my0 rt_input_st.in_mdy = rt_input_st.in_my - rt_input_st.in_my0
# captured (mode 2): the cursor is a clamped reticle, the motion is the raw delta # captured (mode 2): the cursor is a clamped reticle, the motion is the raw delta
if is_windowed() and in_cursor_mode == 2 { in_mdx = in_rdx; in_mdy = in_rdy } if is_windowed() and rt_input_st.in_cursor_mode == 2 { rt_input_st.in_mdx = rt_input_st.in_rdx; rt_input_st.in_mdy = rt_input_st.in_rdy }
# No motion on the first frame or across a cursor-mode change. The previous position there is # No motion on the first frame or across a cursor-mode change. The previous position there is
# 0,0 or a point in another coordinate source, so the difference is the cursor's whole distance # 0,0 or a point in another coordinate source, so the difference is the cursor's whole distance
# from it: a camera that adds mouse_dy to its pitch came up pointing at the ground. # from it: a camera that adds mouse_dy to its pitch came up pointing at the ground.
if in_mouse_rebase { in_mdx = 0; in_mdy = 0; in_mouse_rebase = false } if rt_input_st.in_mouse_rebase { rt_input_st.in_mdx = 0; rt_input_st.in_mdy = 0; rt_input_st.in_mouse_rebase = false }
in_mx0 = in_mx rt_input_st.in_mx0 = rt_input_st.in_mx
in_my0 = in_my rt_input_st.in_my0 = rt_input_st.in_my
} }
# write this frame's committed set + mouse into the tape at the record cursor. # write this frame's committed set + mouse into the tape at the record cursor.
function input_device_record() -> void { function input_device_record(rt_input_st: mut RtInputState) -> void {
if in_tape == null { in_tape = words(INPUT_REC_CAP * IN_STRIDE) } if rt_input_st.in_tape == null { rt_input_st.in_tape = words(INPUT_REC_CAP * IN_STRIDE) }
let f = input_recn - 1 let f = rt_input_st.input_recn - 1
if (f < 0) or (f >= INPUT_REC_CAP) { return } if (f < 0) or (f >= INPUT_REC_CAP) { return }
let base = f * IN_STRIDE let base = f * IN_STRIDE
var i = 0 var i = 0
while i < IN_WORDS { in_tape[base + i] = in_held[i]; i += 1 } while i < IN_WORDS { rt_input_st.in_tape[base + i] = rt_input_st.in_held[i]; i += 1 }
in_tape[base + 8] = in_mx rt_input_st.in_tape[base + 8] = rt_input_st.in_mx
in_tape[base + 9] = in_my rt_input_st.in_tape[base + 9] = rt_input_st.in_my
in_tape[base + 10] = in_mbtn rt_input_st.in_tape[base + 10] = rt_input_st.in_mbtn
in_tape[base + 11] = in_wheel rt_input_st.in_tape[base + 11] = rt_input_st.in_wheel
} }
# ---- held keys ------------------------------------------------------------- # ---- held keys -------------------------------------------------------------
function input_key_down(k: int) -> bool { in_init(); return in_bit_get(in_held, k) } function input_key_down(rt_input_st: mut RtInputState, k: int) -> bool { in_init(rt_input_st); return in_bit_get(rt_input_st.in_held, k) }
function input_key_pressed(k: int) -> bool { in_init(); return in_bit_get(in_held, k) and (not in_bit_get(in_prev, k)) } function input_key_pressed(rt_input_st: mut RtInputState, k: int) -> bool { in_init(rt_input_st); return in_bit_get(rt_input_st.in_held, k) and (not in_bit_get(rt_input_st.in_prev, k)) }
function input_key_released(k: int) -> bool { in_init(); return (not in_bit_get(in_held, k)) and in_bit_get(in_prev, k) } function input_key_released(rt_input_st: mut RtInputState, k: int) -> bool { in_init(rt_input_st); return (not in_bit_get(rt_input_st.in_held, k)) and in_bit_get(rt_input_st.in_prev, k) }
# The name to show a player for key code `k`. A letter, digit or punctuation code is a # The name to show a player for key code `k`. A letter, digit or punctuation code is a
# PHYSICAL key - the one that types it on a US layout - so the name is what the player's # PHYSICAL key - the one that types it on a US layout - so the name is what the player's
@ -421,19 +424,18 @@ function input_key_label(k: int) -> string {
# actually produce. Building text out of key codes instead meant a Turkish player could not type # actually produce. Building text out of key codes instead meant a Turkish player could not type
# c-cedilla, g-breve, dotless i, o-umlaut, s-cedilla or u-umlaut anywhere - their own name included. # c-cedilla, g-breve, dotless i, o-umlaut, s-cedilla or u-umlaut anywhere - their own name included.
# Empty headless, and where the platform has no text channel. # Empty headless, and where the platform has no text channel.
var in_text_buf: words = null function input_text(rt_input_st: mut RtInputState) -> string {
function input_text() -> string {
if not is_windowed() { return "" } if not is_windowed() { return "" }
if in_text_buf == null { in_text_buf = words(64) } if rt_input_st.in_text_buf == null { rt_input_st.in_text_buf = words(64) }
let n = win_text(in_text_buf, 64) let n = win_text(rt_input_st.in_text_buf, 64)
if n <= 0 { return "" } if n <= 0 { return "" }
var out = "" var out = ""
var i = 0 var i = 0
while i < n { while i < n {
var c = in_text_buf[i] var c = rt_input_st.in_text_buf[i]
# a code point outside the BMP arrives as a surrogate PAIR - two units, one character # a code point outside the BMP arrives as a surrogate PAIR - two units, one character
if c >= 55296 and c < 56320 and i + 1 < n { if c >= 55296 and c < 56320 and i + 1 < n {
let lo = in_text_buf[i + 1] let lo = rt_input_st.in_text_buf[i + 1]
if lo >= 56320 and lo < 57344 { c = 65536 + ((c - 55296) * 1024) + (lo - 56320); i = i + 1 } if lo >= 56320 and lo < 57344 { c = 65536 + ((c - 55296) * 1024) + (lo - 56320); i = i + 1 }
} }
out = out + in_utf8(c) out = out + in_utf8(c)
@ -455,27 +457,27 @@ function in_utf8(c: int) -> string {
} }
# inject a held key (AI, tutorial, testing, network) — persists until released. # inject a held key (AI, tutorial, testing, network) — persists until released.
function input_press(k: int) -> void { in_init(); in_bit_set(in_sim, k, true) } function input_press(rt_input_st: mut RtInputState, k: int) -> void { in_init(rt_input_st); in_bit_set(rt_input_st.in_sim, k, true) }
function input_release(k: int) -> void { in_init(); in_bit_set(in_sim, k, false) } function input_release(rt_input_st: mut RtInputState, k: int) -> void { in_init(rt_input_st); in_bit_set(rt_input_st.in_sim, k, false) }
# ---- analog from keys ------------------------------------------------------ # ---- analog from keys ------------------------------------------------------
# A digital axis: +1.0 if the positive key is held, -1.0 if the negative, else 0. # A digital axis: +1.0 if the positive key is held, -1.0 if the negative, else 0.
function input_axis(neg: int, pos: int) -> fixed { function input_axis(rt_input_st: mut RtInputState, neg: int, pos: int) -> fixed {
in_init() in_init(rt_input_st)
var v = fixed(0) var v = fixed(0)
if in_bit_get(in_held, pos) { v += fixed(1) } if in_bit_get(rt_input_st.in_held, pos) { v += fixed(1) }
if in_bit_get(in_held, neg) { v -= fixed(1) } if in_bit_get(rt_input_st.in_held, neg) { v -= fixed(1) }
return v return v
} }
# #79 — a directional intent as a plain int: +1 if the positive key is held, -1 if # #79 — a directional intent as a plain int: +1 if the positive key is held, -1 if
# the negative, 0 if neither or both. Reads the multi-key device set, so it needs # the negative, 0 if neither or both. Reads the multi-key device set, so it needs
# no bool->int glue (the `dx = ki(key_down('d')) - ki(key_down('a'))` boilerplate) # no bool->int glue (the `dx = ki(key_down('d')) - ki(key_down('a'))` boilerplate)
# and feeds an int mover (TopDown.move) straight: dx = Input.axis_i('a','d'). # and feeds an int mover (TopDown.move) straight: dx = Input.axis_i('a','d').
function input_axis_i(neg: int, pos: int) -> int { function input_axis_i(rt_input_st: mut RtInputState, neg: int, pos: int) -> int {
in_init() in_init(rt_input_st)
var v = 0 var v = 0
if in_bit_get(in_held, pos) { v += 1 } if in_bit_get(rt_input_st.in_held, pos) { v += 1 }
if in_bit_get(in_held, neg) { v -= 1 } if in_bit_get(rt_input_st.in_held, neg) { v -= 1 }
return v return v
} }
# a stick has to leave its centre by this much before it counts as a direction # a stick has to leave its centre by this much before it counts as a direction
@ -486,13 +488,13 @@ const STICK_LEFT_Y: int = 1
# The standard top-down movement intent as -1/0/1 per axis: WASD or the arrow # The standard top-down movement intent as -1/0/1 per axis: WASD or the arrow
# keys, and the left stick of pad 0 (past the deadzone) when one is connected. # keys, and the left stick of pad 0 (past the deadzone) when one is connected.
function input_move_i() -> IVec2 { function input_move_i(rt_input_st: mut RtInputState) -> IVec2 {
in_init() in_init(rt_input_st)
var x = input_axis_i(Key.A, Key.D) + input_axis_i(Key.Left, Key.Right) var x = input_axis_i(rt_input_st, Key.A, Key.D) + input_axis_i(rt_input_st, Key.Left, Key.Right)
var y = input_axis_i(Key.W, Key.S) + input_axis_i(Key.Up, Key.Down) var y = input_axis_i(rt_input_st, Key.W, Key.S) + input_axis_i(rt_input_st, Key.Up, Key.Down)
if input_pad_connected(0) { if input_pad_connected(rt_input_st, 0) {
let sx = input_pad_axis(0, STICK_LEFT_X) let sx = input_pad_axis(rt_input_st, 0, STICK_LEFT_X)
let sy = input_pad_axis(0, STICK_LEFT_Y) let sy = input_pad_axis(rt_input_st, 0, STICK_LEFT_Y)
if sx > STICK_DEADZONE { x = 1 } if sx > STICK_DEADZONE { x = 1 }
if sx < -STICK_DEADZONE { x = -1 } if sx < -STICK_DEADZONE { x = -1 }
if sy > STICK_DEADZONE { y = 1 } if sy > STICK_DEADZONE { y = 1 }
@ -501,14 +503,14 @@ function input_move_i() -> IVec2 {
return IVec2.make(clamp(x, -1, 1), clamp(y, -1, 1)) return IVec2.make(clamp(x, -1, 1), clamp(y, -1, 1))
} }
# A 2D vector from four direction keys, normalized so a diagonal is not faster. # A 2D vector from four direction keys, normalized so a diagonal is not faster.
function input_vector(left: int, right: int, up: int, down: int) -> Vector { function input_vector(rt_input_st: mut RtInputState, left: int, right: int, up: int, down: int) -> Vector {
in_init() in_init(rt_input_st)
var x = fixed(0) var x = fixed(0)
var y = fixed(0) var y = fixed(0)
if in_bit_get(in_held, right) { x += fixed(1) } if in_bit_get(rt_input_st.in_held, right) { x += fixed(1) }
if in_bit_get(in_held, left) { x -= fixed(1) } if in_bit_get(rt_input_st.in_held, left) { x -= fixed(1) }
if in_bit_get(in_held, down) { y += fixed(1) } if in_bit_get(rt_input_st.in_held, down) { y += fixed(1) }
if in_bit_get(in_held, up) { y -= fixed(1) } if in_bit_get(rt_input_st.in_held, up) { y -= fixed(1) }
if (x != 0) and (y != 0) { # diagonal: scale by 1/sqrt(2) if (x != 0) and (y != 0) { # diagonal: scale by 1/sqrt(2)
x *= 0.7071 # fixed multiply (64-bit intermediate) x *= 0.7071 # fixed multiply (64-bit intermediate)
y *= 0.7071 y *= 0.7071
@ -516,8 +518,8 @@ function input_vector(left: int, right: int, up: int, down: int) -> Vector {
return Vector.make(x, y) return Vector.make(x, y)
} }
# 0.0..1.0 strength of a named action (digital for keys: 1 if any bound key held). # 0.0..1.0 strength of a named action (digital for keys: 1 if any bound key held).
function input_strength(name: pointer) -> fixed { function input_strength(rt_input_st: mut RtInputState, name: pointer) -> fixed {
if input_down(name) { return fixed(1) } if input_down(rt_input_st, name) { return fixed(1) }
return fixed(0) return fixed(0)
} }
@ -534,82 +536,82 @@ function input_strength(name: pointer) -> fixed {
enum CursorMode { Normal, Hidden, Locked, Confined } # Input.cursor_mode(mode:) enum CursorMode { Normal, Hidden, Locked, Confined } # Input.cursor_mode(mode:)
enum PadButton { A, B, X, Y, LeftShoulder, RightShoulder, Back, Start } # Input.bind_pad(button:) / pad_button enum PadButton { A, B, X, Y, LeftShoulder, RightShoulder, Back, Start } # Input.bind_pad(button:) / pad_button
enum MouseButton { Left, Right, Middle } # Input.mouse_down(button:) enum MouseButton { Left, Right, Middle } # Input.mouse_down(button:)
function input_cursor_mode(mode: int) -> void { function input_cursor_mode(rt_input_st: mut RtInputState, mode: int) -> void {
if mode != in_cursor_mode { in_mouse_rebase = true } if mode != rt_input_st.in_cursor_mode { rt_input_st.in_mouse_rebase = true }
in_cursor_mode = mode rt_input_st.in_cursor_mode = mode
if is_windowed() { win_cursor_mode(mode) } if is_windowed() { win_cursor_mode(mode) }
} }
# ---- mouse ----------------------------------------------------------------- # ---- mouse -----------------------------------------------------------------
function input_mouse_x() -> int { in_init(); return in_mx } function input_mouse_x(rt_input_st: mut RtInputState) -> int { in_init(rt_input_st); return rt_input_st.in_mx }
function input_mouse_y() -> int { in_init(); return in_my } function input_mouse_y(rt_input_st: mut RtInputState) -> int { in_init(rt_input_st); return rt_input_st.in_my }
function input_mouse_dx() -> int { in_init(); return in_mdx } function input_mouse_dx(rt_input_st: mut RtInputState) -> int { in_init(rt_input_st); return rt_input_st.in_mdx }
function input_mouse_dy() -> int { in_init(); return in_mdy } function input_mouse_dy(rt_input_st: mut RtInputState) -> int { in_init(rt_input_st); return rt_input_st.in_mdy }
function input_mouse_down(btn: int) -> bool { in_init(); return (in_mbtn & (1 << btn)) != 0 } function input_mouse_down(rt_input_st: mut RtInputState, btn: int) -> bool { in_init(rt_input_st); return (rt_input_st.in_mbtn & (1 << btn)) != 0 }
function input_wheel() -> int { in_init(); return in_wheel } function input_wheel(rt_input_st: mut RtInputState) -> int { in_init(rt_input_st); return rt_input_st.in_wheel }
# inject the mouse (headless / AI / testing). buttons is a bitmask; wheel is this # inject the mouse (headless / AI / testing). buttons is a bitmask; wheel is this
# frame's delta. # frame's delta.
function input_set_mouse(x: int, y: int, buttons: int, wheel: int) -> void { function input_set_mouse(rt_input_st: mut RtInputState, x: int, y: int, buttons: int, wheel: int) -> void {
in_init() in_init(rt_input_st)
in_mx = x; in_my = y; in_mbtn = buttons; in_wheel = wheel rt_input_st.in_mx = x; rt_input_st.in_my = y; rt_input_st.in_mbtn = buttons; rt_input_st.in_wheel = wheel
} }
# ---- gamepads -------------------------------------------------------------- # ---- gamepads --------------------------------------------------------------
function input_pad_connected(pad: int) -> bool { function input_pad_connected(rt_input_st: mut RtInputState, pad: int) -> bool {
in_init() in_init(rt_input_st)
if (pad < 0) or (pad >= IN_PADS) { return false } if (pad < 0) or (pad >= IN_PADS) { return false }
return in_pad_conn[pad] != 0 return rt_input_st.in_pad_conn[pad] != 0
} }
function input_pad_button(pad: int, btn: int) -> bool { function input_pad_button(rt_input_st: mut RtInputState, pad: int, btn: int) -> bool {
in_init() in_init(rt_input_st)
if (pad < 0) or (pad >= IN_PADS) { return false } if (pad < 0) or (pad >= IN_PADS) { return false }
return (in_pad_btn[pad] & (1 << btn)) != 0 return (rt_input_st.in_pad_btn[pad] & (1 << btn)) != 0
} }
function input_pad_axis(pad: int, axis: int) -> fixed { function input_pad_axis(rt_input_st: mut RtInputState, pad: int, axis: int) -> fixed {
in_init() in_init(rt_input_st)
if (pad < 0) or (pad >= IN_PADS) { return fixed(0) } if (pad < 0) or (pad >= IN_PADS) { return fixed(0) }
if (axis < 0) or (axis >= IN_AXES) { return fixed(0) } if (axis < 0) or (axis >= IN_AXES) { return fixed(0) }
return in_pad_axis[pad * IN_AXES + axis] return rt_input_st.in_pad_axis[pad * IN_AXES + axis]
} }
# inject a gamepad's whole state: connected, button bitmask, and four fixed axes. # inject a gamepad's whole state: connected, button bitmask, and four fixed axes.
function input_set_pad(pad: int, connected: bool, buttons: int, lx: fixed, ly: fixed, rx: fixed, ry: fixed) -> void { function input_set_pad(rt_input_st: mut RtInputState, pad: int, connected: bool, buttons: int, lx: fixed, ly: fixed, rx: fixed, ry: fixed) -> void {
in_init() in_init(rt_input_st)
if (pad < 0) or (pad >= IN_PADS) { return } if (pad < 0) or (pad >= IN_PADS) { return }
var c = 0 var c = 0
if connected { c = 1 } if connected { c = 1 }
in_pad_conn[pad] = c rt_input_st.in_pad_conn[pad] = c
in_pad_btn[pad] = buttons rt_input_st.in_pad_btn[pad] = buttons
let b = pad * IN_AXES let b = pad * IN_AXES
in_pad_axis[b] = lx rt_input_st.in_pad_axis[b] = lx
in_pad_axis[b + 1] = ly rt_input_st.in_pad_axis[b + 1] = ly
in_pad_axis[b + 2] = rx rt_input_st.in_pad_axis[b + 2] = rx
in_pad_axis[b + 3] = ry rt_input_st.in_pad_axis[b + 3] = ry
} }
# ---- touch ----------------------------------------------------------------- # ---- touch -----------------------------------------------------------------
function input_touch_count() -> int { function input_touch_count(rt_input_st: mut RtInputState) -> int {
in_init() in_init(rt_input_st)
var n = 0 var n = 0
var i = 0 var i = 0
while i < IN_TOUCH { if in_touch_on[i] != 0 { n += 1 }; i += 1 } while i < IN_TOUCH { if rt_input_st.in_touch_on[i] != 0 { n += 1 }; i += 1 }
return n return n
} }
function input_touch_x(i: int) -> int { in_init(); if (i < 0) or (i >= IN_TOUCH) { return 0 }; return in_touch_x[i] } function input_touch_x(rt_input_st: mut RtInputState, i: int) -> int { in_init(rt_input_st); if (i < 0) or (i >= IN_TOUCH) { return 0 }; return rt_input_st.in_touch_x[i] }
function input_touch_y(i: int) -> int { in_init(); if (i < 0) or (i >= IN_TOUCH) { return 0 }; return in_touch_y[i] } function input_touch_y(rt_input_st: mut RtInputState, i: int) -> int { in_init(rt_input_st); if (i < 0) or (i >= IN_TOUCH) { return 0 }; return rt_input_st.in_touch_y[i] }
# inject a touch point i: active with a position, or inactive. # inject a touch point i: active with a position, or inactive.
function input_set_touch(i: int, x: int, y: int, active: bool) -> void { function input_set_touch(rt_input_st: mut RtInputState, i: int, x: int, y: int, active: bool) -> void {
in_init() in_init(rt_input_st)
if (i < 0) or (i >= IN_TOUCH) { return } if (i < 0) or (i >= IN_TOUCH) { return }
var a = 0 var a = 0
if active { a = 1 } if active { a = 1 }
in_touch_on[i] = a rt_input_st.in_touch_on[i] = a
in_touch_x[i] = x rt_input_st.in_touch_x[i] = x
in_touch_y[i] = y rt_input_st.in_touch_y[i] = y
} }
# is the named action held on the frame last polled? # is the named action held on the frame last polled?
function input_down(name: pointer) -> bool { function input_down(rt_input_st: mut RtInputState, name: pointer) -> bool {
return input_slot_has(input_find(name), input_frame) return input_slot_has(rt_input_st, input_find(rt_input_st, name), rt_input_st.input_frame)
} }
# #83 — the Input-Manager reads: an action is *active* when any of its bound # #83 — the Input-Manager reads: an action is *active* when any of its bound
@ -618,55 +620,55 @@ function input_down(name: pointer) -> bool {
# see the whole device layer (hold left AND jump), and are device-agnostic. The # see the whole device layer (hold left AND jump), and are device-agnostic. The
# frame loop now commits the device layer automatically (input_poll), so these read # frame loop now commits the device layer automatically (input_poll), so these read
# live without the game calling Input.poll by hand. # live without the game calling Input.poll by hand.
function input_active_in(name: pointer, held: words, padmask: int) -> bool { function input_active_in(rt_input_st: mut RtInputState, name: pointer, held: words, padmask: int) -> bool {
let s = input_find(name) let s = input_find(rt_input_st, name)
if s < 0 { return false } if s < 0 { return false }
let base = s * INPUT_MAX_KEYS let base = s * INPUT_MAX_KEYS
var i = 0 var i = 0
while i < INPUT_MAX_KEYS { while i < INPUT_MAX_KEYS {
let k = input_keys[base + i] let k = rt_input_st.input_keys[base + i]
if (k != 0) and in_bit_get(held, k) { return true } if (k != 0) and in_bit_get(held, k) { return true }
let pb = input_pads[base + i] let pb = rt_input_st.input_pads[base + i]
if (pb != 0) and ((padmask & (1 << (pb - 1))) != 0) { return true } if (pb != 0) and ((padmask & (1 << (pb - 1))) != 0) { return true }
i += 1 i += 1
} }
return false return false
} }
function input_active(name: pointer) -> bool { function input_active(rt_input_st: mut RtInputState, name: pointer) -> bool {
in_init() in_init(rt_input_st)
return input_active_in(name, in_held, in_pad_btn[0]) return input_active_in(rt_input_st, name, rt_input_st.in_held, rt_input_st.in_pad_btn[0])
} }
# went active this frame (active now, not last frame) — the deterministic on-press. # went active this frame (active now, not last frame) — the deterministic on-press.
function input_just_pressed(name: pointer) -> bool { function input_just_pressed(rt_input_st: mut RtInputState, name: pointer) -> bool {
in_init() in_init(rt_input_st)
let now = input_active_in(name, in_held, in_pad_btn[0]) let now = input_active_in(rt_input_st, name, rt_input_st.in_held, rt_input_st.in_pad_btn[0])
let was = input_active_in(name, in_prev, in_pad_btn0[0]) let was = input_active_in(rt_input_st, name, rt_input_st.in_prev, rt_input_st.in_pad_btn0[0])
return now and (not was) return now and (not was)
} }
# went inactive this frame (not active now, was last frame) — the on-release. # went inactive this frame (not active now, was last frame) — the on-release.
function input_just_released(name: pointer) -> bool { function input_just_released(rt_input_st: mut RtInputState, name: pointer) -> bool {
in_init() in_init(rt_input_st)
let now = input_active_in(name, in_held, in_pad_btn[0]) let now = input_active_in(rt_input_st, name, rt_input_st.in_held, rt_input_st.in_pad_btn[0])
let was = input_active_in(name, in_prev, in_pad_btn0[0]) let was = input_active_in(rt_input_st, name, rt_input_st.in_prev, rt_input_st.in_pad_btn0[0])
return (not now) and was return (not now) and was
} }
# did the named action go down this frame (down now, not down last frame)? # did the named action go down this frame (down now, not down last frame)?
function input_pressed(name: pointer) -> bool { function input_pressed(rt_input_st: mut RtInputState, name: pointer) -> bool {
let s = input_find(name) let s = input_find(rt_input_st, name)
return input_slot_has(s, input_frame) and (not input_slot_has(s, input_last)) return input_slot_has(rt_input_st, s, rt_input_st.input_frame) and (not input_slot_has(rt_input_st, s, rt_input_st.input_last))
} }
# begin recording polled input from the next frame (resets the tape). # begin recording polled input from the next frame (resets the tape).
function input_record() -> void { function input_record(rt_input_st: mut RtInputState) -> void {
if input_rec == null { input_rec = words(INPUT_REC_CAP) } if rt_input_st.input_rec == null { rt_input_st.input_rec = words(INPUT_REC_CAP) }
input_recn = 0 rt_input_st.input_recn = 0
input_pos = 0 rt_input_st.input_pos = 0
input_mode = 1 rt_input_st.input_mode = 1
} }
# replay the recording from its start; subsequent Input.poll calls read the tape. # replay the recording from its start; subsequent Input.poll calls read the tape.
function input_replay() -> void { function input_replay(rt_input_st: mut RtInputState) -> void {
input_pos = 0 rt_input_st.input_pos = 0
input_mode = 2 rt_input_st.input_mode = 2
} }

View file

@ -49,45 +49,57 @@ const JK_PRIMES: int = 3 # compute: how many primes are <= arg
const JK_ALL: int = 4 # Promise group: succeeds when every member does const JK_ALL: int = 4 # Promise group: succeeds when every member does
const JK_RACE: int = 5 # Promise group: succeeds when the first member does const JK_RACE: int = 5 # Promise group: succeeds when the first member does
var jb_ready: bool = false export state RtJobsState {
var jb_state: words = null # J_* jb_ready: bool = false
var jb_kind: words = null # JK_* jb_state: words = null # J_*
var jb_result: words = null # the success value jb_kind: words = null # JK_*
var jb_error: words = null # the failure code jb_result: words = null # the success value
var jb_arg: words = null # compute input n jb_error: words = null # the failure code
var jb_i: words = null # compute progress counter jb_arg: words = null # compute input n
var jb_acc: words = null # compute accumulator jb_i: words = null # compute progress counter
var jb_acc2: words = null # compute second accumulator (Fibonacci) jb_acc: words = null # compute accumulator
var jb_nmem: words = null # group member count jb_acc2: words = null # compute second accumulator (Fibonacci)
var jb_mem: words = null # flat [JOB_SLOTS * JOB_MAXMEM] of member handles jb_nmem: words = null # group member count
jb_mem: words = null # flat [JOB_SLOTS * JOB_MAXMEM] of member handles
sy_ready: bool = false
mx_used: words = null
mx_obj: pointers = null # the native mutex behind each handle
at_used: words = null
at_cell: pointers = null # the int each atomic handle names (a words(1) of its own)
ch_used: words = null
ch_head: words = null
ch_count: words = null
ch_buf: words = null # flat [SYNC_CHAN * CHAN_CAP]
ch_lock: pointers = null # a mutex per channel
}
function jb_init() -> void { function jb_init(rt_jobs_st: mut RtJobsState) -> void {
if jb_ready { return } if rt_jobs_st.jb_ready { return }
jb_state = words(JOB_SLOTS); fill(jb_state, 0, JOB_SLOTS * 4) rt_jobs_st.jb_state = words(JOB_SLOTS); fill(rt_jobs_st.jb_state, 0, JOB_SLOTS * 4)
jb_kind = words(JOB_SLOTS); fill(jb_kind, 0, JOB_SLOTS * 4) rt_jobs_st.jb_kind = words(JOB_SLOTS); fill(rt_jobs_st.jb_kind, 0, JOB_SLOTS * 4)
jb_result = words(JOB_SLOTS); fill(jb_result, 0, JOB_SLOTS * 4) rt_jobs_st.jb_result = words(JOB_SLOTS); fill(rt_jobs_st.jb_result, 0, JOB_SLOTS * 4)
jb_error = words(JOB_SLOTS); fill(jb_error, 0, JOB_SLOTS * 4) rt_jobs_st.jb_error = words(JOB_SLOTS); fill(rt_jobs_st.jb_error, 0, JOB_SLOTS * 4)
jb_arg = words(JOB_SLOTS); fill(jb_arg, 0, JOB_SLOTS * 4) rt_jobs_st.jb_arg = words(JOB_SLOTS); fill(rt_jobs_st.jb_arg, 0, JOB_SLOTS * 4)
jb_i = words(JOB_SLOTS); fill(jb_i, 0, JOB_SLOTS * 4) rt_jobs_st.jb_i = words(JOB_SLOTS); fill(rt_jobs_st.jb_i, 0, JOB_SLOTS * 4)
jb_acc = words(JOB_SLOTS); fill(jb_acc, 0, JOB_SLOTS * 4) rt_jobs_st.jb_acc = words(JOB_SLOTS); fill(rt_jobs_st.jb_acc, 0, JOB_SLOTS * 4)
jb_acc2 = words(JOB_SLOTS); fill(jb_acc2, 0, JOB_SLOTS * 4) rt_jobs_st.jb_acc2 = words(JOB_SLOTS); fill(rt_jobs_st.jb_acc2, 0, JOB_SLOTS * 4)
jb_nmem = words(JOB_SLOTS); fill(jb_nmem, 0, JOB_SLOTS * 4) rt_jobs_st.jb_nmem = words(JOB_SLOTS); fill(rt_jobs_st.jb_nmem, 0, JOB_SLOTS * 4)
jb_mem = words(JOB_SLOTS * JOB_MAXMEM); fill(jb_mem, 0, JOB_SLOTS * JOB_MAXMEM * 4) rt_jobs_st.jb_mem = words(JOB_SLOTS * JOB_MAXMEM); fill(rt_jobs_st.jb_mem, 0, JOB_SLOTS * JOB_MAXMEM * 4)
jb_ready = true rt_jobs_st.jb_ready = true
} }
# claim a free slot as PENDING with the given kind; returns a 1-based handle, or # claim a free slot as PENDING with the given kind; returns a 1-based handle, or
# 0 if the table is full. # 0 if the table is full.
function jb_alloc(kind: int) -> int { function jb_alloc(rt_jobs_st: mut RtJobsState, kind: int) -> int {
jb_init() jb_init(rt_jobs_st)
var i = 0 var i = 0
while i < JOB_SLOTS { while i < JOB_SLOTS {
if jb_state[i] == J_FREE { if rt_jobs_st.jb_state[i] == J_FREE {
jb_state[i] = J_PENDING rt_jobs_st.jb_state[i] = J_PENDING
jb_kind[i] = kind rt_jobs_st.jb_kind[i] = kind
jb_result[i] = 0; jb_error[i] = 0 rt_jobs_st.jb_result[i] = 0; rt_jobs_st.jb_error[i] = 0
jb_arg[i] = 0; jb_i[i] = 0; jb_acc[i] = 0; jb_acc2[i] = 0 rt_jobs_st.jb_arg[i] = 0; rt_jobs_st.jb_i[i] = 0; rt_jobs_st.jb_acc[i] = 0; rt_jobs_st.jb_acc2[i] = 0
jb_nmem[i] = 0 rt_jobs_st.jb_nmem[i] = 0
return i + 1 return i + 1
} }
i += 1 i += 1
@ -95,71 +107,71 @@ function jb_alloc(kind: int) -> int {
return 0 return 0
} }
function jb_valid(h: int) -> bool { function jb_valid(rt_jobs_st: mut RtJobsState, h: int) -> bool {
jb_init() jb_init(rt_jobs_st)
if (h < 1) or (h > JOB_SLOTS) { return false } if (h < 1) or (h > JOB_SLOTS) { return false }
return jb_state[h - 1] != J_FREE return rt_jobs_st.jb_state[h - 1] != J_FREE
} }
# ---- the safe tier: futures ------------------------------------------------ # ---- the safe tier: futures ------------------------------------------------
# A hand-driven future: PENDING until you call Job.fulfill / Job.fail on it. # A hand-driven future: PENDING until you call Job.fulfill / Job.fail on it.
function job_defer() -> int { return jb_alloc(JK_DEFER) } function job_defer(rt_jobs_st: mut RtJobsState) -> int { return jb_alloc(rt_jobs_st, JK_DEFER) }
# Kick off a background compute job (kind = JK_SUM / JK_FIB / JK_PRIMES). It runs # Kick off a background compute job (kind = JK_SUM / JK_FIB / JK_PRIMES). It runs
# a little each Job.pump and resolves when it finishes. `arg` is its input. # a little each Job.pump and resolves when it finishes. `arg` is its input.
function job_run(kind: int, arg: int) -> int { function job_run(rt_jobs_st: mut RtJobsState, kind: int, arg: int) -> int {
let h = jb_alloc(kind) let h = jb_alloc(rt_jobs_st, kind)
if h == 0 { return 0 } if h == 0 { return 0 }
let s = h - 1 let s = h - 1
jb_arg[s] = arg rt_jobs_st.jb_arg[s] = arg
if kind == JK_FIB { jb_acc[s] = 0; jb_acc2[s] = 1 } # fib(0)=0, fib(1)=1 if kind == JK_FIB { rt_jobs_st.jb_acc[s] = 0; rt_jobs_st.jb_acc2[s] = 1 } # fib(0)=0, fib(1)=1
return h return h
} }
# Resolve a pending job successfully with `value` (no-op once resolved). # Resolve a pending job successfully with `value` (no-op once resolved).
function job_fulfill(h: int, value: int) -> void { function job_fulfill(rt_jobs_st: mut RtJobsState, h: int, value: int) -> void {
if not jb_valid(h) { return } if not jb_valid(rt_jobs_st, h) { return }
let s = h - 1 let s = h - 1
if jb_state[s] != J_PENDING { return } if rt_jobs_st.jb_state[s] != J_PENDING { return }
jb_state[s] = J_DONE rt_jobs_st.jb_state[s] = J_DONE
jb_result[s] = value rt_jobs_st.jb_result[s] = value
} }
# Resolve a pending job as failed with error code `err` (no-op once resolved). # Resolve a pending job as failed with error code `err` (no-op once resolved).
function job_fail(h: int, err: int) -> void { function job_fail(rt_jobs_st: mut RtJobsState, h: int, err: int) -> void {
if not jb_valid(h) { return } if not jb_valid(rt_jobs_st, h) { return }
let s = h - 1 let s = h - 1
if jb_state[s] != J_PENDING { return } if rt_jobs_st.jb_state[s] != J_PENDING { return }
jb_state[s] = J_FAILED rt_jobs_st.jb_state[s] = J_FAILED
jb_error[s] = err rt_jobs_st.jb_error[s] = err
} }
# Cancel a pending job (no-op if it already resolved). # Cancel a pending job (no-op if it already resolved).
function job_cancel(h: int) -> void { function job_cancel(rt_jobs_st: mut RtJobsState, h: int) -> void {
if not jb_valid(h) { return } if not jb_valid(rt_jobs_st, h) { return }
let s = h - 1 let s = h - 1
if jb_state[s] == J_PENDING { jb_state[s] = J_CANCELLED } if rt_jobs_st.jb_state[s] == J_PENDING { rt_jobs_st.jb_state[s] = J_CANCELLED }
} }
# Recompute a group job (JK_ALL / JK_RACE) from its members. A no-op unless the # Recompute a group job (JK_ALL / JK_RACE) from its members. A no-op unless the
# slot is a still-PENDING group. This is what "resolve on the main thread" means: # slot is a still-PENDING group. This is what "resolve on the main thread" means:
# a Promise settles only when you look at it (done/ok/...) or pump. # a Promise settles only when you look at it (done/ok/...) or pump.
function jb_refresh_group(s: int) -> void { function jb_refresh_group(rt_jobs_st: mut RtJobsState, s: int) -> void {
if jb_state[s] != J_PENDING { return } if rt_jobs_st.jb_state[s] != J_PENDING { return }
let k = jb_kind[s] let k = rt_jobs_st.jb_kind[s]
if (k != JK_ALL) and (k != JK_RACE) { return } if (k != JK_ALL) and (k != JK_RACE) { return }
let n = jb_nmem[s] let n = rt_jobs_st.jb_nmem[s]
let base = s * JOB_MAXMEM let base = s * JOB_MAXMEM
var i = 0 var i = 0
var settled = 0 # members in a terminal state var settled = 0 # members in a terminal state
var ok = 0 # members that succeeded var ok = 0 # members that succeeded
var first_ok = 0 # winning handle for RACE var first_ok = 0 # winning handle for RACE
while i < n { while i < n {
let mh = jb_mem[base + i] let mh = rt_jobs_st.jb_mem[base + i]
if jb_valid(mh) { if jb_valid(rt_jobs_st, mh) {
let ms = mh - 1 let ms = mh - 1
let mst = jb_state[ms] let mst = rt_jobs_st.jb_state[ms]
if mst != J_PENDING { if mst != J_PENDING {
settled += 1 settled += 1
if mst == J_DONE { if mst == J_DONE {
@ -173,99 +185,99 @@ function jb_refresh_group(s: int) -> void {
i += 1 i += 1
} }
if k == JK_ALL { if k == JK_ALL {
if ok == n { jb_state[s] = J_DONE; jb_result[s] = n } if ok == n { rt_jobs_st.jb_state[s] = J_DONE; rt_jobs_st.jb_result[s] = n }
else { if settled == n { jb_state[s] = J_FAILED; jb_error[s] = n - ok } } else { if settled == n { rt_jobs_st.jb_state[s] = J_FAILED; rt_jobs_st.jb_error[s] = n - ok } }
} else { } else {
if first_ok != 0 { jb_state[s] = J_DONE; jb_result[s] = first_ok } if first_ok != 0 { rt_jobs_st.jb_state[s] = J_DONE; rt_jobs_st.jb_result[s] = first_ok }
else { if settled == n { jb_state[s] = J_FAILED; jb_error[s] = n } } else { if settled == n { rt_jobs_st.jb_state[s] = J_FAILED; rt_jobs_st.jb_error[s] = n } }
} }
} }
# resolved in any terminal state? # resolved in any terminal state?
function job_done(h: int) -> bool { function job_done(rt_jobs_st: mut RtJobsState, h: int) -> bool {
if not jb_valid(h) { return false } if not jb_valid(rt_jobs_st, h) { return false }
jb_refresh_group(h - 1) jb_refresh_group(rt_jobs_st, h - 1)
return jb_state[h - 1] != J_PENDING return rt_jobs_st.jb_state[h - 1] != J_PENDING
} }
function job_ok(h: int) -> bool { function job_ok(rt_jobs_st: mut RtJobsState, h: int) -> bool {
if not jb_valid(h) { return false } if not jb_valid(rt_jobs_st, h) { return false }
jb_refresh_group(h - 1) jb_refresh_group(rt_jobs_st, h - 1)
return jb_state[h - 1] == J_DONE return rt_jobs_st.jb_state[h - 1] == J_DONE
} }
function job_failed(h: int) -> bool { function job_failed(rt_jobs_st: mut RtJobsState, h: int) -> bool {
if not jb_valid(h) { return false } if not jb_valid(rt_jobs_st, h) { return false }
jb_refresh_group(h - 1) jb_refresh_group(rt_jobs_st, h - 1)
return jb_state[h - 1] == J_FAILED return rt_jobs_st.jb_state[h - 1] == J_FAILED
} }
function job_cancelled(h: int) -> bool { function job_cancelled(rt_jobs_st: mut RtJobsState, h: int) -> bool {
if not jb_valid(h) { return false } if not jb_valid(rt_jobs_st, h) { return false }
return jb_state[h - 1] == J_CANCELLED return rt_jobs_st.jb_state[h - 1] == J_CANCELLED
} }
# the success value (0 unless the job is done-ok) # the success value (0 unless the job is done-ok)
function job_result(h: int) -> int { function job_result(rt_jobs_st: mut RtJobsState, h: int) -> int {
if not jb_valid(h) { return 0 } if not jb_valid(rt_jobs_st, h) { return 0 }
jb_refresh_group(h - 1) jb_refresh_group(rt_jobs_st, h - 1)
if jb_state[h - 1] != J_DONE { return 0 } if rt_jobs_st.jb_state[h - 1] != J_DONE { return 0 }
return jb_result[h - 1] return rt_jobs_st.jb_result[h - 1]
} }
# the failure code (0 unless the job failed) # the failure code (0 unless the job failed)
function job_error(h: int) -> int { function job_error(rt_jobs_st: mut RtJobsState, h: int) -> int {
if not jb_valid(h) { return 0 } if not jb_valid(rt_jobs_st, h) { return 0 }
jb_refresh_group(h - 1) jb_refresh_group(rt_jobs_st, h - 1)
if jb_state[h - 1] != J_FAILED { return 0 } if rt_jobs_st.jb_state[h - 1] != J_FAILED { return 0 }
return jb_error[h - 1] return rt_jobs_st.jb_error[h - 1]
} }
# how many jobs are still pending (a ready-made loading-screen denominator). # how many jobs are still pending (a ready-made loading-screen denominator).
function job_pending() -> int { function job_pending(rt_jobs_st: mut RtJobsState) -> int {
jb_init() jb_init(rt_jobs_st)
var n = 0 var n = 0
var i = 0 var i = 0
while i < JOB_SLOTS { while i < JOB_SLOTS {
if jb_state[i] == J_PENDING { n += 1 } if rt_jobs_st.jb_state[i] == J_PENDING { n += 1 }
i += 1 i += 1
} }
return n return n
} }
# release a slot back to the pool. # release a slot back to the pool.
function job_free(h: int) -> void { function job_free(rt_jobs_st: mut RtJobsState, h: int) -> void {
if not jb_valid(h) { return } if not jb_valid(rt_jobs_st, h) { return }
jb_state[h - 1] = J_FREE rt_jobs_st.jb_state[h - 1] = J_FREE
} }
# advance one compute job by a single step; returns 1 if it just finished. # advance one compute job by a single step; returns 1 if it just finished.
function jb_step(s: int) -> int { function jb_step(rt_jobs_st: mut RtJobsState, s: int) -> int {
let k = jb_kind[s] let k = rt_jobs_st.jb_kind[s]
let n = jb_arg[s] let n = rt_jobs_st.jb_arg[s]
var i = jb_i[s] var i = rt_jobs_st.jb_i[s]
if k == JK_SUM { if k == JK_SUM {
jb_acc[s] = jb_acc[s] + (i + 1) rt_jobs_st.jb_acc[s] = rt_jobs_st.jb_acc[s] + (i + 1)
i += 1 i += 1
jb_i[s] = i rt_jobs_st.jb_i[s] = i
if i >= n { jb_state[s] = J_DONE; jb_result[s] = jb_acc[s]; return 1 } if i >= n { rt_jobs_st.jb_state[s] = J_DONE; rt_jobs_st.jb_result[s] = rt_jobs_st.jb_acc[s]; return 1 }
return 0 return 0
} }
if k == JK_FIB { if k == JK_FIB {
if i >= n { jb_state[s] = J_DONE; jb_result[s] = jb_acc[s]; return 1 } if i >= n { rt_jobs_st.jb_state[s] = J_DONE; rt_jobs_st.jb_result[s] = rt_jobs_st.jb_acc[s]; return 1 }
let t = jb_acc[s] + jb_acc2[s] let t = rt_jobs_st.jb_acc[s] + rt_jobs_st.jb_acc2[s]
jb_acc[s] = jb_acc2[s] rt_jobs_st.jb_acc[s] = rt_jobs_st.jb_acc2[s]
jb_acc2[s] = t rt_jobs_st.jb_acc2[s] = t
i += 1 i += 1
jb_i[s] = i rt_jobs_st.jb_i[s] = i
if i >= n { jb_state[s] = J_DONE; jb_result[s] = jb_acc[s]; return 1 } if i >= n { rt_jobs_st.jb_state[s] = J_DONE; rt_jobs_st.jb_result[s] = rt_jobs_st.jb_acc[s]; return 1 }
return 0 return 0
} }
if k == JK_PRIMES { if k == JK_PRIMES {
if jb_is_prime(i) { jb_acc[s] += 1 } if jb_is_prime(i) { rt_jobs_st.jb_acc[s] += 1 }
i += 1 i += 1
jb_i[s] = i rt_jobs_st.jb_i[s] = i
if i > n { jb_state[s] = J_DONE; jb_result[s] = jb_acc[s]; return 1 } if i > n { rt_jobs_st.jb_state[s] = J_DONE; rt_jobs_st.jb_result[s] = rt_jobs_st.jb_acc[s]; return 1 }
return 0 return 0
} }
return 0 return 0
@ -285,17 +297,17 @@ function jb_is_prime(v: int) -> bool {
# resolve any Promise groups. Call it once per frame (or wherever you want the # resolve any Promise groups. Call it once per frame (or wherever you want the
# results to land). Returns how many jobs finished during this call. `budget` <= # results to land). Returns how many jobs finished during this call. `budget` <=
# 0 means "run every compute job to completion right now". # 0 means "run every compute job to completion right now".
function job_pump(budget: int) -> int { function job_pump(rt_jobs_st: mut RtJobsState, budget: int) -> int {
jb_init() jb_init(rt_jobs_st)
var completed = 0 var completed = 0
var spent = 0 var spent = 0
var s = 0 var s = 0
while s < JOB_SLOTS { while s < JOB_SLOTS {
let k = jb_kind[s] let k = rt_jobs_st.jb_kind[s]
let compute = (k == JK_SUM) or (k == JK_FIB) or (k == JK_PRIMES) let compute = (k == JK_SUM) or (k == JK_FIB) or (k == JK_PRIMES)
while (jb_state[s] == J_PENDING) and compute { while (rt_jobs_st.jb_state[s] == J_PENDING) and compute {
if (budget > 0) and (spent >= budget) { s = JOB_SLOTS + 1; break } if (budget > 0) and (spent >= budget) { s = JOB_SLOTS + 1; break }
let fin = jb_step(s) let fin = jb_step(rt_jobs_st, s)
spent += 1 spent += 1
if fin == 1 { completed += 1 } if fin == 1 { completed += 1 }
} }
@ -304,11 +316,11 @@ function job_pump(budget: int) -> int {
# settle groups after the compute jobs advanced this frame. # settle groups after the compute jobs advanced this frame.
s = 0 s = 0
while s < JOB_SLOTS { while s < JOB_SLOTS {
if jb_state[s] == J_PENDING { if rt_jobs_st.jb_state[s] == J_PENDING {
let k = jb_kind[s] let k = rt_jobs_st.jb_kind[s]
if (k == JK_ALL) or (k == JK_RACE) { if (k == JK_ALL) or (k == JK_RACE) {
jb_refresh_group(s) jb_refresh_group(rt_jobs_st, s)
if jb_state[s] != J_PENDING { completed += 1 } if rt_jobs_st.jb_state[s] != J_PENDING { completed += 1 }
} }
} }
s += 1 s += 1
@ -319,51 +331,51 @@ function job_pump(budget: int) -> int {
# ---- Promise combinators (over a []int of job handles) --------------------- # ---- Promise combinators (over a []int of job handles) ---------------------
# store up to JOB_MAXMEM handles as the members of group slot `s`. # store up to JOB_MAXMEM handles as the members of group slot `s`.
function jb_set_members(s: int, handles: []int) -> void { function jb_set_members(rt_jobs_st: mut RtJobsState, s: int, handles: []int) -> void {
var n = len(handles) var n = len(handles)
if n > JOB_MAXMEM { n = JOB_MAXMEM } if n > JOB_MAXMEM { n = JOB_MAXMEM }
let base = s * JOB_MAXMEM let base = s * JOB_MAXMEM
var i = 0 var i = 0
while i < n { jb_mem[base + i] = handles[i]; i += 1 } while i < n { rt_jobs_st.jb_mem[base + i] = handles[i]; i += 1 }
jb_nmem[s] = n rt_jobs_st.jb_nmem[s] = n
} }
# A promise that succeeds once every member has succeeded, and fails as soon as # A promise that succeeds once every member has succeeded, and fails as soon as
# the whole set has settled with at least one non-success. Returns a job handle. # the whole set has settled with at least one non-success. Returns a job handle.
function prom_all(handles: []int) -> int { function prom_all(rt_jobs_st: mut RtJobsState, handles: []int) -> int {
let h = jb_alloc(JK_ALL) let h = jb_alloc(rt_jobs_st, JK_ALL)
if h == 0 { return 0 } if h == 0 { return 0 }
jb_set_members(h - 1, handles) jb_set_members(rt_jobs_st, h - 1, handles)
jb_refresh_group(h - 1) jb_refresh_group(rt_jobs_st, h - 1)
return h return h
} }
# A promise that succeeds as soon as the first member succeeds (its handle is the # A promise that succeeds as soon as the first member succeeds (its handle is the
# result), and fails only if every member settles without success. # result), and fails only if every member settles without success.
function prom_race(handles: []int) -> int { function prom_race(rt_jobs_st: mut RtJobsState, handles: []int) -> int {
let h = jb_alloc(JK_RACE) let h = jb_alloc(rt_jobs_st, JK_RACE)
if h == 0 { return 0 } if h == 0 { return 0 }
jb_set_members(h - 1, handles) jb_set_members(rt_jobs_st, h - 1, handles)
jb_refresh_group(h - 1) jb_refresh_group(rt_jobs_st, h - 1)
return h return h
} }
# how many of `handles` have resolved (any terminal state) — a loading bar's # how many of `handles` have resolved (any terminal state) — a loading bar's
# numerator; pair with len(handles) for the denominator. # numerator; pair with len(handles) for the denominator.
function prom_count_done(handles: []int) -> int { function prom_count_done(rt_jobs_st: mut RtJobsState, handles: []int) -> int {
var n = 0 var n = 0
var i = 0 var i = 0
while i < len(handles) { while i < len(handles) {
if job_done(handles[i]) { n += 1 } if job_done(rt_jobs_st, handles[i]) { n += 1 }
i += 1 i += 1
} }
return n return n
} }
function prom_all_done(handles: []int) -> bool { function prom_all_done(rt_jobs_st: mut RtJobsState, handles: []int) -> bool {
var i = 0 var i = 0
while i < len(handles) { while i < len(handles) {
if not job_done(handles[i]) { return false } if not job_done(rt_jobs_st, handles[i]) { return false }
i += 1 i += 1
} }
return true return true
@ -401,39 +413,29 @@ extern function thr_cas(p: pointer, expect: int, next: int) -> int = "thr_cas"
extern function thr_load(p: pointer) -> int = "thr_load" extern function thr_load(p: pointer) -> int = "thr_load"
extern function thr_store(p: pointer, v: int) = "thr_store" extern function thr_store(p: pointer, v: int) = "thr_store"
var sy_ready: bool = false
var mx_used: words = null
var mx_obj: pointers = null # the native mutex behind each handle
var at_used: words = null
var at_cell: pointers = null # the int each atomic handle names (a words(1) of its own)
var ch_used: words = null
var ch_head: words = null
var ch_count: words = null
var ch_buf: words = null # flat [SYNC_CHAN * CHAN_CAP]
var ch_lock: pointers = null # a mutex per channel
function sy_init() -> void { function sy_init(rt_jobs_st: mut RtJobsState) -> void {
if sy_ready { return } if rt_jobs_st.sy_ready { return }
mx_used = words(SYNC_MUTEX); fill(mx_used, 0, SYNC_MUTEX * 4) rt_jobs_st.mx_used = words(SYNC_MUTEX); fill(rt_jobs_st.mx_used, 0, SYNC_MUTEX * 4)
mx_obj = pointers(SYNC_MUTEX); fill(mx_obj, 0, SYNC_MUTEX * 8) rt_jobs_st.mx_obj = pointers(SYNC_MUTEX); fill(rt_jobs_st.mx_obj, 0, SYNC_MUTEX * 8)
at_used = words(SYNC_ATOMIC); fill(at_used, 0, SYNC_ATOMIC * 4) rt_jobs_st.at_used = words(SYNC_ATOMIC); fill(rt_jobs_st.at_used, 0, SYNC_ATOMIC * 4)
at_cell = pointers(SYNC_ATOMIC); fill(at_cell, 0, SYNC_ATOMIC * 8) rt_jobs_st.at_cell = pointers(SYNC_ATOMIC); fill(rt_jobs_st.at_cell, 0, SYNC_ATOMIC * 8)
ch_used = words(SYNC_CHAN); fill(ch_used, 0, SYNC_CHAN * 4) rt_jobs_st.ch_used = words(SYNC_CHAN); fill(rt_jobs_st.ch_used, 0, SYNC_CHAN * 4)
ch_head = words(SYNC_CHAN); fill(ch_head, 0, SYNC_CHAN * 4) rt_jobs_st.ch_head = words(SYNC_CHAN); fill(rt_jobs_st.ch_head, 0, SYNC_CHAN * 4)
ch_count = words(SYNC_CHAN); fill(ch_count, 0, SYNC_CHAN * 4) rt_jobs_st.ch_count = words(SYNC_CHAN); fill(rt_jobs_st.ch_count, 0, SYNC_CHAN * 4)
ch_buf = words(SYNC_CHAN * CHAN_CAP); fill(ch_buf, 0, SYNC_CHAN * CHAN_CAP * 4) rt_jobs_st.ch_buf = words(SYNC_CHAN * CHAN_CAP); fill(rt_jobs_st.ch_buf, 0, SYNC_CHAN * CHAN_CAP * 4)
ch_lock = pointers(SYNC_CHAN); fill(ch_lock, 0, SYNC_CHAN * 8) rt_jobs_st.ch_lock = pointers(SYNC_CHAN); fill(rt_jobs_st.ch_lock, 0, SYNC_CHAN * 8)
sy_ready = true rt_jobs_st.sy_ready = true
} }
# ---- mutex ----------------------------------------------------------------- # ---- mutex -----------------------------------------------------------------
function sync_mutex() -> int { function sync_mutex(rt_jobs_st: mut RtJobsState) -> int {
sy_init() sy_init(rt_jobs_st)
var i = 0 var i = 0
while i < SYNC_MUTEX { while i < SYNC_MUTEX {
if mx_used[i] == 0 { if rt_jobs_st.mx_used[i] == 0 {
mx_used[i] = 1 rt_jobs_st.mx_used[i] = 1
if mx_obj[i] == null { mx_obj[i] = thr_mutex_new() } if rt_jobs_st.mx_obj[i] == null { rt_jobs_st.mx_obj[i] = thr_mutex_new() }
return i + 1 return i + 1
} }
i += 1 i += 1
@ -441,34 +443,34 @@ function sync_mutex() -> int {
return 0 return 0
} }
function sync_lock(m: int) -> void { function sync_lock(rt_jobs_st: mut RtJobsState, m: int) -> void {
sy_init() sy_init(rt_jobs_st)
if (m < 1) or (m > SYNC_MUTEX) { return } if (m < 1) or (m > SYNC_MUTEX) { return }
thr_lock(mx_obj[m - 1]) thr_lock(rt_jobs_st.mx_obj[m - 1])
} }
function sync_unlock(m: int) -> void { function sync_unlock(rt_jobs_st: mut RtJobsState, m: int) -> void {
sy_init() sy_init(rt_jobs_st)
if (m < 1) or (m > SYNC_MUTEX) { return } if (m < 1) or (m > SYNC_MUTEX) { return }
thr_unlock(mx_obj[m - 1]) thr_unlock(rt_jobs_st.mx_obj[m - 1])
} }
# take the lock only if it is free; returns whether it was taken. # take the lock only if it is free; returns whether it was taken.
function sync_try_lock(m: int) -> bool { function sync_try_lock(rt_jobs_st: mut RtJobsState, m: int) -> bool {
sy_init() sy_init(rt_jobs_st)
if (m < 1) or (m > SYNC_MUTEX) { return false } if (m < 1) or (m > SYNC_MUTEX) { return false }
return thr_trylock(mx_obj[m - 1]) == 1 return thr_trylock(rt_jobs_st.mx_obj[m - 1]) == 1
} }
# ---- atomic counter -------------------------------------------------------- # ---- atomic counter --------------------------------------------------------
function sync_atomic() -> int { function sync_atomic(rt_jobs_st: mut RtJobsState) -> int {
sy_init() sy_init(rt_jobs_st)
var i = 0 var i = 0
while i < SYNC_ATOMIC { while i < SYNC_ATOMIC {
if at_used[i] == 0 { if rt_jobs_st.at_used[i] == 0 {
at_used[i] = 1 rt_jobs_st.at_used[i] = 1
if at_cell[i] == null { at_cell[i] = data_of(words(1)) } # the atomics take the cell's address, not its slice if rt_jobs_st.at_cell[i] == null { rt_jobs_st.at_cell[i] = data_of(words(1)) } # the atomics take the cell's address, not its slice
thr_store(at_cell[i], 0) thr_store(rt_jobs_st.at_cell[i], 0)
return i + 1 return i + 1
} }
i += 1 i += 1
@ -476,40 +478,40 @@ function sync_atomic() -> int {
return 0 return 0
} }
function sync_get(a: int) -> int { function sync_get(rt_jobs_st: mut RtJobsState, a: int) -> int {
sy_init() sy_init(rt_jobs_st)
if (a < 1) or (a > SYNC_ATOMIC) { return 0 } if (a < 1) or (a > SYNC_ATOMIC) { return 0 }
return thr_load(at_cell[a - 1]) return thr_load(rt_jobs_st.at_cell[a - 1])
} }
function sync_set(a: int, v: int) -> void { function sync_set(rt_jobs_st: mut RtJobsState, a: int, v: int) -> void {
sy_init() sy_init(rt_jobs_st)
if (a < 1) or (a > SYNC_ATOMIC) { return } if (a < 1) or (a > SYNC_ATOMIC) { return }
thr_store(at_cell[a - 1], v) thr_store(rt_jobs_st.at_cell[a - 1], v)
} }
# add `delta` and return the new value. # add `delta` and return the new value.
function sync_add(a: int, delta: int) -> int { function sync_add(rt_jobs_st: mut RtJobsState, a: int, delta: int) -> int {
sy_init() sy_init(rt_jobs_st)
if (a < 1) or (a > SYNC_ATOMIC) { return 0 } if (a < 1) or (a > SYNC_ATOMIC) { return 0 }
return thr_atomic_add(at_cell[a - 1], delta) return thr_atomic_add(rt_jobs_st.at_cell[a - 1], delta)
} }
# compare-and-set: if the value equals `expect`, store `next` and return true. # compare-and-set: if the value equals `expect`, store `next` and return true.
function sync_cas(a: int, expect: int, next: int) -> bool { function sync_cas(rt_jobs_st: mut RtJobsState, a: int, expect: int, next: int) -> bool {
sy_init() sy_init(rt_jobs_st)
if (a < 1) or (a > SYNC_ATOMIC) { return false } if (a < 1) or (a > SYNC_ATOMIC) { return false }
return thr_cas(at_cell[a - 1], expect, next) == 1 return thr_cas(rt_jobs_st.at_cell[a - 1], expect, next) == 1
} }
# ---- channel (a bounded int FIFO, behind its own mutex) --------------------- # ---- channel (a bounded int FIFO, behind its own mutex) ---------------------
function sync_channel() -> int { function sync_channel(rt_jobs_st: mut RtJobsState) -> int {
sy_init() sy_init(rt_jobs_st)
var i = 0 var i = 0
while i < SYNC_CHAN { while i < SYNC_CHAN {
if ch_used[i] == 0 { if rt_jobs_st.ch_used[i] == 0 {
ch_used[i] = 1; ch_head[i] = 0; ch_count[i] = 0 rt_jobs_st.ch_used[i] = 1; rt_jobs_st.ch_head[i] = 0; rt_jobs_st.ch_count[i] = 0
if ch_lock[i] == null { ch_lock[i] = thr_mutex_new() } if rt_jobs_st.ch_lock[i] == null { rt_jobs_st.ch_lock[i] = thr_mutex_new() }
return i + 1 return i + 1
} }
i += 1 i += 1
@ -518,52 +520,52 @@ function sync_channel() -> int {
} }
# enqueue `v`; returns false if the channel is full. # enqueue `v`; returns false if the channel is full.
function sync_send(c: int, v: int) -> bool { function sync_send(rt_jobs_st: mut RtJobsState, c: int, v: int) -> bool {
sy_init() sy_init(rt_jobs_st)
if (c < 1) or (c > SYNC_CHAN) { return false } if (c < 1) or (c > SYNC_CHAN) { return false }
let s = c - 1 let s = c - 1
thr_lock(ch_lock[s]) thr_lock(rt_jobs_st.ch_lock[s])
if ch_count[s] >= CHAN_CAP { thr_unlock(ch_lock[s]); return false } if rt_jobs_st.ch_count[s] >= CHAN_CAP { thr_unlock(rt_jobs_st.ch_lock[s]); return false }
let pos = ch_head[s] + ch_count[s] let pos = rt_jobs_st.ch_head[s] + rt_jobs_st.ch_count[s]
var idx = pos var idx = pos
if idx >= CHAN_CAP { idx -= CHAN_CAP } if idx >= CHAN_CAP { idx -= CHAN_CAP }
ch_buf[s * CHAN_CAP + idx] = v rt_jobs_st.ch_buf[s * CHAN_CAP + idx] = v
ch_count[s] += 1 rt_jobs_st.ch_count[s] += 1
thr_unlock(ch_lock[s]) thr_unlock(rt_jobs_st.ch_lock[s])
return true return true
} }
# dequeue the oldest value; returns 0 on an empty channel (guard with can_recv). # dequeue the oldest value; returns 0 on an empty channel (guard with can_recv).
function sync_recv(c: int) -> int { function sync_recv(rt_jobs_st: mut RtJobsState, c: int) -> int {
sy_init() sy_init(rt_jobs_st)
if (c < 1) or (c > SYNC_CHAN) { return 0 } if (c < 1) or (c > SYNC_CHAN) { return 0 }
let s = c - 1 let s = c - 1
thr_lock(ch_lock[s]) thr_lock(rt_jobs_st.ch_lock[s])
if ch_count[s] == 0 { thr_unlock(ch_lock[s]); return 0 } if rt_jobs_st.ch_count[s] == 0 { thr_unlock(rt_jobs_st.ch_lock[s]); return 0 }
let v = ch_buf[s * CHAN_CAP + ch_head[s]] let v = rt_jobs_st.ch_buf[s * CHAN_CAP + rt_jobs_st.ch_head[s]]
var nh = ch_head[s] + 1 var nh = rt_jobs_st.ch_head[s] + 1
if nh >= CHAN_CAP { nh = 0 } if nh >= CHAN_CAP { nh = 0 }
ch_head[s] = nh rt_jobs_st.ch_head[s] = nh
ch_count[s] -= 1 rt_jobs_st.ch_count[s] -= 1
thr_unlock(ch_lock[s]) thr_unlock(rt_jobs_st.ch_lock[s])
return v return v
} }
function sync_can_recv(c: int) -> bool { function sync_can_recv(rt_jobs_st: mut RtJobsState, c: int) -> bool {
sy_init() sy_init(rt_jobs_st)
if (c < 1) or (c > SYNC_CHAN) { return false } if (c < 1) or (c > SYNC_CHAN) { return false }
thr_lock(ch_lock[c - 1]) thr_lock(rt_jobs_st.ch_lock[c - 1])
let has = ch_count[c - 1] > 0 let has = rt_jobs_st.ch_count[c - 1] > 0
thr_unlock(ch_lock[c - 1]) thr_unlock(rt_jobs_st.ch_lock[c - 1])
return has return has
} }
function sync_len(c: int) -> int { function sync_len(rt_jobs_st: mut RtJobsState, c: int) -> int {
sy_init() sy_init(rt_jobs_st)
if (c < 1) or (c > SYNC_CHAN) { return 0 } if (c < 1) or (c > SYNC_CHAN) { return 0 }
thr_lock(ch_lock[c - 1]) thr_lock(rt_jobs_st.ch_lock[c - 1])
let n = ch_count[c - 1] let n = rt_jobs_st.ch_count[c - 1]
thr_unlock(ch_lock[c - 1]) thr_unlock(rt_jobs_st.ch_lock[c - 1])
return n return n
} }

View file

@ -34,26 +34,34 @@
# Up to 64 rectangular occluders, stored flat as (x, y, w, h) i32 quads. A game # Up to 64 rectangular occluders, stored flat as (x, y, w, h) i32 quads. A game
# clears them each frame (Light.clear_occluders) and re-registers the geometry # clears them each frame (Light.clear_occluders) and re-registers the geometry
# that should cast shadows this frame. # that should cast shadows this frame.
var rt_light_occ: words = null # occluder rects: 4 i32 each — x, y, w, h export state RtLightState {
var rt_light_occ_n: int = 0 # number of occluders currently stored rt_light_occ: words = null # occluder rects: 4 i32 each — x, y, w, h
rt_light_occ_n: int = 0 # number of occluders currently stored
rt_light_falloff: int = 1 # brightness ramp exponent: 1 linear, 2 quadratic, 3 cubic…
rt_light_soft: int = 0 # penumbra radius in px (0 = hard single-sample shadow)
rt_light_gel: int = 0 # outer gel colour 0x00RRGGBB (used only when rt_light_gel_on)
rt_light_gel_on: bool = false # gel active? off = a flat single-colour light
rt_light_h: int = 64 # virtual light height above the surface, for normal-map N·L
rt_light_nrm: words = null
}
function light_occ_init() -> void { function light_occ_init(rt_light_st: mut RtLightState) -> void {
if rt_light_occ == null { rt_light_occ = words(64 * 4) } if rt_light_st.rt_light_occ == null { rt_light_st.rt_light_occ = words(64 * 4) }
} }
# forget every occluder — call once per frame before re-registering geometry. # forget every occluder — call once per frame before re-registering geometry.
function light_clear_occluders() -> void { rt_light_occ_n = 0 } function light_clear_occluders(rt_light_st: mut RtLightState) -> void { rt_light_st.rt_light_occ_n = 0 }
# register a rectangular shadow caster (screen space). Silently ignored past 64. # register a rectangular shadow caster (screen space). Silently ignored past 64.
function light_occlude(x: int, y: int, w: int, h: int) -> void { function light_occlude(rt_light_st: mut RtLightState, x: int, y: int, w: int, h: int) -> void {
light_occ_init() light_occ_init(rt_light_st)
if rt_light_occ_n >= 64 { return } if rt_light_st.rt_light_occ_n >= 64 { return }
let i = rt_light_occ_n * 4 let i = rt_light_st.rt_light_occ_n * 4
rt_light_occ[i] = x rt_light_st.rt_light_occ[i] = x
rt_light_occ[i + 1] = y rt_light_st.rt_light_occ[i + 1] = y
rt_light_occ[i + 2] = w rt_light_st.rt_light_occ[i + 2] = w
rt_light_occ[i + 3] = h rt_light_st.rt_light_occ[i + 3] = h
rt_light_occ_n += 1 rt_light_st.rt_light_occ_n += 1
} }
# ---- shadow geometry ------------------------------------------------------ # ---- shadow geometry ------------------------------------------------------
@ -85,14 +93,14 @@ function light_pt_in_rect(px: int, py: int, rx: int, ry: int, rw: int, rh: int)
# is the segment from light (lx,ly) to pixel (px,py) blocked by any occluder? # is the segment from light (lx,ly) to pixel (px,py) blocked by any occluder?
# A pixel inside an occluder is in shadow; otherwise the ray is blocked if it # A pixel inside an occluder is in shadow; otherwise the ray is blocked if it
# crosses any of the rectangle's four edges. # crosses any of the rectangle's four edges.
function light_blocked(lx: int, ly: int, px: int, py: int) -> bool { function light_blocked(rt_light_st: RtLightState, lx: int, ly: int, px: int, py: int) -> bool {
var k = 0 var k = 0
while k < rt_light_occ_n { while k < rt_light_st.rt_light_occ_n {
let i = k * 4 let i = k * 4
let rx = rt_light_occ[i] let rx = rt_light_st.rt_light_occ[i]
let ry = rt_light_occ[i + 1] let ry = rt_light_st.rt_light_occ[i + 1]
let rw = rt_light_occ[i + 2] let rw = rt_light_st.rt_light_occ[i + 2]
let rh = rt_light_occ[i + 3] let rh = rt_light_st.rt_light_occ[i + 3]
if light_pt_in_rect(px, py, rx, ry, rw, rh) { return true } if light_pt_in_rect(px, py, rx, ry, rw, rh) { return true }
let x0 = rx let x0 = rx
let y0 = ry let y0 = ry
@ -132,19 +140,14 @@ function light_fsqrt(v: fixed) -> fixed {
# occluder store — a game (or the engine ECS system) sets them before emitting a # occluder store — a game (or the engine ECS system) sets them before emitting a
# light and they stay until changed, so the simple Light.point call keeps its # light and they stay until changed, so the simple Light.point call keeps its
# short signature while spot/soft/falloff/gel ride on this side-band state. # short signature while spot/soft/falloff/gel ride on this side-band state.
var rt_light_falloff: int = 1 # brightness ramp exponent: 1 linear, 2 quadratic, 3 cubic…
var rt_light_soft: int = 0 # penumbra radius in px (0 = hard single-sample shadow)
var rt_light_gel: int = 0 # outer gel colour 0x00RRGGBB (used only when rt_light_gel_on)
var rt_light_gel_on: bool = false # gel active? off = a flat single-colour light
var rt_light_h: int = 64 # virtual light height above the surface, for normal-map N·L
function light_set_falloff(exp: int) -> void { function light_set_falloff(rt_light_st: mut RtLightState, exp: int) -> void {
if exp < 1 { rt_light_falloff = 1 } else { rt_light_falloff = exp } if exp < 1 { rt_light_st.rt_light_falloff = 1 } else { rt_light_st.rt_light_falloff = exp }
} }
function light_set_soft(radius: int) -> void { rt_light_soft = radius } function light_set_soft(rt_light_st: mut RtLightState, radius: int) -> void { rt_light_st.rt_light_soft = radius }
function light_set_gel(outer: int) -> void { rt_light_gel = outer; rt_light_gel_on = true } function light_set_gel(rt_light_st: mut RtLightState, outer: int) -> void { rt_light_st.rt_light_gel = outer; rt_light_st.rt_light_gel_on = true }
function light_clear_gel() -> void { rt_light_gel_on = false } function light_clear_gel(rt_light_st: mut RtLightState) -> void { rt_light_st.rt_light_gel_on = false }
function light_set_height(h: int) -> void { if h > 0 { rt_light_h = h } } function light_set_height(rt_light_st: mut RtLightState, h: int) -> void { if h > 0 { rt_light_st.rt_light_h = h } }
# ---- normal buffer -------------------------------------------------------- # ---- normal buffer --------------------------------------------------------
# An optional per-pixel surface-normal G-buffer, parallel to the framebuffer. # An optional per-pixel surface-normal G-buffer, parallel to the framebuffer.
@ -152,25 +155,24 @@ function light_set_height(h: int) -> void { if h > 0 { rt_light_h = h } }
# here" so an unstamped scene lights exactly as before (factor 1). nz is recovered # here" so an unstamped scene lights exactly as before (factor 1). nz is recovered
# from the unit constraint, so a Light2D shades a surface by the angle it faces, # from the unit constraint, so a Light2D shades a surface by the angle it faces,
# not distance alone (tier 3). Allocated only when a game stamps a normal. # not distance alone (tier 3). Allocated only when a game stamps a normal.
var rt_light_nrm: words = null
function light_nrm_init() -> void { function light_nrm_init(rt_light_st: mut RtLightState) -> void {
if rt_light_nrm == null { rt_light_nrm = words(rt_fbw * rt_fbh) } if rt_light_st.rt_light_nrm == null { rt_light_st.rt_light_nrm = words(rt_fbw * rt_fbh) }
} }
# forget every stamped normal — call once per frame before re-stamping surfaces. # forget every stamped normal — call once per frame before re-stamping surfaces.
function light_clear_normals() -> void { function light_clear_normals(rt_light_st: mut RtLightState) -> void {
if rt_light_nrm == null { return } if rt_light_st.rt_light_nrm == null { return }
let n = rt_fbw * rt_fbh let n = rt_fbw * rt_fbh
var i = 0 var i = 0
while i < n { rt_light_nrm[i] = 0; i += 1 } while i < n { rt_light_st.rt_light_nrm[i] = 0; i += 1 }
} }
# stamp a rectangular region's surface normal. nx, ny are the normal's x/y as a # stamp a rectangular region's surface normal. nx, ny are the normal's x/y as a
# Q16.16 fixed in [-1, 1] (a flat surface facing the camera is nx = ny = 0); nz is # Q16.16 fixed in [-1, 1] (a flat surface facing the camera is nx = ny = 0); nz is
# derived. Screen space, clipped to the framebuffer. # derived. Screen space, clipped to the framebuffer.
function light_normal_rect(x: int, y: int, w: int, h: int, nx: fixed, ny: fixed) -> void { function light_normal_rect(rt_light_st: mut RtLightState, x: int, y: int, w: int, h: int, nx: fixed, ny: fixed) -> void {
light_nrm_init() light_nrm_init(rt_light_st)
var nxq = floor(nx * fixed(127)) + 128 var nxq = floor(nx * fixed(127)) + 128
var nyq = floor(ny * fixed(127)) + 128 var nyq = floor(ny * fixed(127)) + 128
if nxq < 0 { nxq = 0 }; if nxq > 255 { nxq = 255 } if nxq < 0 { nxq = 0 }; if nxq > 255 { nxq = 255 }
@ -181,7 +183,7 @@ function light_normal_rect(x: int, y: int, w: int, h: int, nx: fixed, ny: fixed)
if (py >= 0) and (py < rt_fbh) { if (py >= 0) and (py < rt_fbh) {
var px = x var px = x
while px < x + w { while px < x + w {
if (px >= 0) and (px < rt_fbw) { rt_light_nrm[py * rt_fbw + px] = packed } if (px >= 0) and (px < rt_fbw) { rt_light_st.rt_light_nrm[py * rt_fbw + px] = packed }
px += 1 px += 1
} }
} }
@ -239,33 +241,33 @@ function light_pow_t(t: fixed, exp: int) -> fixed {
# only its centre (0 or 1); a soft light (rt_light_soft > 0) samples a small cross # only its centre (0 or 1); a soft light (rt_light_soft > 0) samples a small cross
# on the light disk and averages, so an occluder edge fades through a penumbra # on the light disk and averages, so an occluder edge fades through a penumbra
# instead of cutting sharply (tier 4). # instead of cutting sharply (tier 4).
function light_vis(cx: int, cy: int, px: int, py: int) -> fixed { function light_vis(rt_light_st: RtLightState, cx: int, cy: int, px: int, py: int) -> fixed {
if rt_light_soft <= 0 { if rt_light_st.rt_light_soft <= 0 {
if light_blocked(cx, cy, px, py) { return fixed(0) } if light_blocked(rt_light_st, cx, cy, px, py) { return fixed(0) }
return fixed(1) return fixed(1)
} }
let s = rt_light_soft let s = rt_light_st.rt_light_soft
var hit = 0 var hit = 0
if not light_blocked(cx, cy, px, py) { hit += 1 } if not light_blocked(rt_light_st, cx, cy, px, py) { hit += 1 }
if not light_blocked(cx + s, cy, px, py) { hit += 1 } if not light_blocked(rt_light_st, cx + s, cy, px, py) { hit += 1 }
if not light_blocked(cx - s, cy, px, py) { hit += 1 } if not light_blocked(rt_light_st, cx - s, cy, px, py) { hit += 1 }
if not light_blocked(cx, cy + s, px, py) { hit += 1 } if not light_blocked(rt_light_st, cx, cy + s, px, py) { hit += 1 }
if not light_blocked(cx, cy - s, px, py) { hit += 1 } if not light_blocked(rt_light_st, cx, cy - s, px, py) { hit += 1 }
return fixed(hit) / fixed(5) return fixed(hit) / fixed(5)
} }
# Lambert factor at a pixel from the normal G-buffer, 0..1 (Q16.16). Flat / no # Lambert factor at a pixel from the normal G-buffer, 0..1 (Q16.16). Flat / no
# normal -> 1 (unchanged). Otherwise N·L with L the (normalized) direction from # normal -> 1 (unchanged). Otherwise N·L with L the (normalized) direction from
# the surface to the light in 3D, the light lifted rt_light_h above the plane. # the surface to the light in 3D, the light lifted rt_light_h above the plane.
function light_normal_factor(cx: int, cy: int, px: int, py: int) -> fixed { function light_normal_factor(rt_light_st: RtLightState, cx: int, cy: int, px: int, py: int) -> fixed {
if rt_light_nrm == null { return fixed(1) } if rt_light_st.rt_light_nrm == null { return fixed(1) }
let packed = rt_light_nrm[py * rt_fbw + px] let packed = rt_light_st.rt_light_nrm[py * rt_fbw + px]
if packed == 0 { return fixed(1) } if packed == 0 { return fixed(1) }
let nxq = (packed & 255) - 128 let nxq = (packed & 255) - 128
let nyq = ((packed >> 8) & 255) - 128 let nyq = ((packed >> 8) & 255) - 128
let lx = cx - px let lx = cx - px
let ly = cy - py let ly = cy - py
let lz = rt_light_h let lz = rt_light_st.rt_light_h
let lm = light_isqrt(lx * lx + ly * ly + lz * lz) let lm = light_isqrt(lx * lx + ly * ly + lz * lz)
if lm <= 0 { return fixed(1) } if lm <= 0 { return fixed(1) }
let lxf = fixed(lx) / fixed(lm) let lxf = fixed(lx) / fixed(lm)
@ -285,18 +287,18 @@ function light_normal_factor(cx: int, cy: int, px: int, py: int) -> fixed {
# Multiply the whole scene by an ambient tint (0x00RRGGBB): the CanvasModulate # Multiply the whole scene by an ambient tint (0x00RRGGBB): the CanvasModulate
# that gives a night/cave mood before any light adds brightness back. Ambient # that gives a night/cave mood before any light adds brightness back. Ambient
# 0xFFFFFF is a no-op; darker/colored tints dim and gel the scene. # 0xFFFFFF is a no-op; darker/colored tints dim and gel the scene.
function light_ambient(color: int) -> void { function light_ambient(rt_core_st: mut RtCoreState, color: int) -> void {
let ar = (color >> 16) & 255 let ar = (color >> 16) & 255
let ag = (color >> 8) & 255 let ag = (color >> 8) & 255
let ab = color & 255 let ab = color & 255
let n = rt_fbw * rt_fbh let n = rt_fbw * rt_fbh
var i = 0 var i = 0
while i < n { while i < n {
let cur = rt_fb[i] let cur = rt_core_st.rt_fb[i]
let nr = (((cur >> 16) & 255) * ar) / 255 let nr = (((cur >> 16) & 255) * ar) / 255
let ng = (((cur >> 8) & 255) * ag) / 255 let ng = (((cur >> 8) & 255) * ag) / 255
let nb = ((cur & 255) * ab) / 255 let nb = ((cur & 255) * ab) / 255
rt_fb[i] = (nr << 16) | (ng << 8) | nb rt_core_st.rt_fb[i] = (nr << 16) | (ng << 8) | nb
i += 1 i += 1
} }
} }
@ -315,14 +317,14 @@ function light_ambient(color: int) -> void {
# Brightness is clamped per channel at 255; only the bounding box is touched. # Brightness is clamped per channel at 255; only the bounding box is touched.
const LIGHT_FEATHER: int = 6 # cone-edge softening, in degrees const LIGHT_FEATHER: int = 6 # cone-edge softening, in degrees
function light_emit(cx: int, cy: int, radius: int, color: int, energy: fixed, dir_deg: int, spread_deg: int) -> void { function light_emit(rt_core_st: mut RtCoreState, rt_light_st: RtLightState, cx: int, cy: int, radius: int, color: int, energy: fixed, dir_deg: int, spread_deg: int) -> void {
if radius <= 0 { return } if radius <= 0 { return }
let lr = (color >> 16) & 255 let lr = (color >> 16) & 255
let lg = (color >> 8) & 255 let lg = (color >> 8) & 255
let lb = color & 255 let lb = color & 255
let gr = (rt_light_gel >> 16) & 255 let gr = (rt_light_st.rt_light_gel >> 16) & 255
let gg = (rt_light_gel >> 8) & 255 let gg = (rt_light_st.rt_light_gel >> 8) & 255
let gb = rt_light_gel & 255 let gb = rt_light_st.rt_light_gel & 255
var py = cy - radius var py = cy - radius
while py <= cy + radius { while py <= cy + radius {
if (py >= 0) and (py < rt_fbh) { if (py >= 0) and (py < rt_fbh) {
@ -344,26 +346,26 @@ function light_emit(cx: int, cy: int, radius: int, color: int, energy: fixed, di
} }
} }
if cone > 0 { if cone > 0 {
let vis = light_vis(cx, cy, px, py) let vis = light_vis(rt_light_st, cx, cy, px, py)
if vis > 0 { if vis > 0 {
let t_lin = fixed(radius - d) / fixed(radius) # 1 at centre, 0 at rim let t_lin = fixed(radius - d) / fixed(radius) # 1 at centre, 0 at rim
let atten = light_pow_t(t_lin, rt_light_falloff) let atten = light_pow_t(t_lin, rt_light_st.rt_light_falloff)
let nf = light_normal_factor(cx, cy, px, py) let nf = light_normal_factor(rt_light_st, cx, cy, px, py)
let k = atten * energy * vis * cone * nf let k = atten * energy * vis * cone * nf
# gel: mix the light colour toward the rim colour by (1 - t_lin). # gel: mix the light colour toward the rim colour by (1 - t_lin).
var cr = lr; var cg = lg; var cb = lb var cr = lr; var cg = lg; var cb = lb
if rt_light_gel_on { if rt_light_st.rt_light_gel_on {
let mix = fixed(1) - t_lin let mix = fixed(1) - t_lin
cr = lr + floor(fixed(gr - lr) * mix) cr = lr + floor(fixed(gr - lr) * mix)
cg = lg + floor(fixed(gg - lg) * mix) cg = lg + floor(fixed(gg - lg) * mix)
cb = lb + floor(fixed(gb - lb) * mix) cb = lb + floor(fixed(gb - lb) * mix)
} }
let idx = py * rt_fbw + px let idx = py * rt_fbw + px
let cur = rt_fb[idx] let cur = rt_core_st.rt_fb[idx]
let nr = min(255, ((cur >> 16) & 255) + floor(fixed(cr) * k)) let nr = min(255, ((cur >> 16) & 255) + floor(fixed(cr) * k))
let ng = min(255, ((cur >> 8) & 255) + floor(fixed(cg) * k)) let ng = min(255, ((cur >> 8) & 255) + floor(fixed(cg) * k))
let nb = min(255, (cur & 255) + floor(fixed(cb) * k)) let nb = min(255, (cur & 255) + floor(fixed(cb) * k))
rt_fb[idx] = (nr << 16) | (ng << 8) | nb rt_core_st.rt_fb[idx] = (nr << 16) | (ng << 8) | nb
} }
} }
} }
@ -377,15 +379,15 @@ function light_emit(cx: int, cy: int, radius: int, color: int, energy: fixed, di
# a radial (omnidirectional) point light — the original short-signature call, # a radial (omnidirectional) point light — the original short-signature call,
# now a thin wrapper over light_emit with the cone disabled. # now a thin wrapper over light_emit with the cone disabled.
function light_point(cx: int, cy: int, radius: int, color: int, energy: fixed) -> void { function light_point(rt_core_st: mut RtCoreState, rt_light_st: RtLightState, cx: int, cy: int, radius: int, color: int, energy: fixed) -> void {
light_emit(cx, cy, radius, color, energy, 0, -1) light_emit(rt_core_st, rt_light_st, cx, cy, radius, color, energy, 0, -1)
} }
# a cone / spot light aimed at `direction` degrees (0 = +x, CCW) with a half-angle # a cone / spot light aimed at `direction` degrees (0 = +x, CCW) with a half-angle
# `spread` in degrees — a flashlight, a lamp cone. Shares every tier control (soft # `spread` in degrees — a flashlight, a lamp cone. Shares every tier control (soft
# shadows, falloff, gel, normals) with the radial form. # shadows, falloff, gel, normals) with the radial form.
function light_spot(cx: int, cy: int, radius: int, color: int, energy: fixed, direction: int, spread: int) -> void { function light_spot(rt_core_st: mut RtCoreState, rt_light_st: RtLightState, cx: int, cy: int, radius: int, color: int, energy: fixed, direction: int, spread: int) -> void {
light_emit(cx, cy, radius, color, energy, direction, spread) light_emit(rt_core_st, rt_light_st, cx, cy, radius, color, energy, direction, spread)
} }
# ---- day / night ---------------------------------------------------------- # ---- day / night ----------------------------------------------------------
@ -393,7 +395,7 @@ function light_spot(cx: int, cy: int, radius: int, color: int, energy: fixed, di
# 0.25 dawn, 0.5 noon, 0.75 dusk). A cosine-free triangle ramps a deep-blue night # 0.25 dawn, 0.5 noon, 0.75 dusk). A cosine-free triangle ramps a deep-blue night
# up to full daylight and back, so a game animates one value and the world's mood # up to full daylight and back, so a game animates one value and the world's mood
# follows. Deterministic; drives the same light_ambient modulate. # follows. Deterministic; drives the same light_ambient modulate.
function light_time_of_day(t: fixed) -> void { function light_time_of_day(rt_core_st: mut RtCoreState, t: fixed) -> void {
# day factor 0..1: 0 at midnight, 1 at noon (triangle over the day). # day factor 0..1: 0 at midnight, 1 at noon (triangle over the day).
var day = t * fixed(2) # 0..2 across the day var day = t * fixed(2) # 0..2 across the day
if day > fixed(1) { day = fixed(2) - day } # fold 0.5..1 back down: peak at noon if day > fixed(1) { day = fixed(2) - day } # fold 0.5..1 back down: peak at noon
@ -404,5 +406,5 @@ function light_time_of_day(t: fixed) -> void {
let r = nr + floor(fixed(dr - nr) * day) let r = nr + floor(fixed(dr - nr) * day)
let g = ng + floor(fixed(dg - ng) * day) let g = ng + floor(fixed(dg - ng) * day)
let b = nb + floor(fixed(db - nb) * day) let b = nb + floor(fixed(db - nb) * day)
light_ambient((r << 16) | (g << 8) | b) light_ambient(rt_core_st, (r << 16) | (g << 8) | b)
} }

View file

@ -94,58 +94,61 @@ function rx_node(op: int) -> RNode {
property Prog { code: IVec, cls: IVec, ngroups: int, ok: int } property Prog { code: IVec, cls: IVec, ngroups: int, ok: int }
# ---- parser state ----------------------------------------------------------- # ---- parser state -----------------------------------------------------------
var rx_pat: pointer = "" export state RtRegexState {
var rx_pos: int = 0 rx_pat: pointer = ""
var rx_len: int = 0 rx_pos: int = 0
var rx_err: int = 0 rx_len: int = 0
var rx_ngroup: int = 0 rx_err: int = 0
var rx_prog: Prog = null rx_ngroup: int = 0
rx_prog: Prog = null
rx_named_gidx: int = 0
}
function rx_peek() -> int { if rx_pos < rx_len { return rx_pat[rx_pos] & 255 }; return -1 } function rx_peek(rt_regex_st: RtRegexState) -> int { if rt_regex_st.rx_pos < rt_regex_st.rx_len { return rt_regex_st.rx_pat[rt_regex_st.rx_pos] & 255 }; return -1 }
function rx_peek2() -> int { if rx_pos + 1 < rx_len { return rx_pat[rx_pos + 1] & 255 }; return -1 } function rx_peek2(rt_regex_st: RtRegexState) -> int { if rt_regex_st.rx_pos + 1 < rt_regex_st.rx_len { return rt_regex_st.rx_pat[rt_regex_st.rx_pos + 1] & 255 }; return -1 }
function rx_adv() -> int { let c = rx_peek(); rx_pos += 1; return c } function rx_adv(rt_regex_st: mut RtRegexState) -> int { let c = rx_peek(rt_regex_st); rt_regex_st.rx_pos += 1; return c }
# ---- character classes ------------------------------------------------------ # ---- character classes ------------------------------------------------------
# a class is 8 i32 words (256 bits) in prog.cls; class k occupies cls[8k .. 8k+8) # a class is 8 i32 words (256 bits) in prog.cls; class k occupies cls[8k .. 8k+8)
function rx_class_new() -> int { function rx_class_new(rt_regex_st: RtRegexState) -> int {
let idx = rx_prog.cls.n / 8 let idx = rt_regex_st.rx_prog.cls.n / 8
var i = 0 var i = 0
while i < 8 { iv_push(rx_prog.cls, 0); i += 1 } while i < 8 { iv_push(rt_regex_st.rx_prog.cls, 0); i += 1 }
return idx return idx
} }
function rx_class_set(idx: int, c: int) -> void { function rx_class_set(rt_regex_st: mut RtRegexState, idx: int, c: int) -> void {
let w = idx * 8 + (c >> 5) let w = idx * 8 + (c >> 5)
rx_prog.cls.d[w] = rx_prog.cls.d[w] | (1 << (c & 31)) rt_regex_st.rx_prog.cls.d[w] = rt_regex_st.rx_prog.cls.d[w] | (1 << (c & 31))
} }
function rx_class_set_range(idx: int, a: int, b: int) -> void { function rx_class_set_range(rt_regex_st: mut RtRegexState, idx: int, a: int, b: int) -> void {
var c = a var c = a
while c <= b { rx_class_set(idx, c); c += 1 } while c <= b { rx_class_set(rt_regex_st, idx, c); c += 1 }
} }
function rx_class_negate(idx: int) -> void { function rx_class_negate(rt_regex_st: mut RtRegexState, idx: int) -> void {
var i = 0 var i = 0
while i < 8 { let w = idx * 8 + i; rx_prog.cls.d[w] = ~rx_prog.cls.d[w]; i += 1 } while i < 8 { let w = idx * 8 + i; rt_regex_st.rx_prog.cls.d[w] = ~rt_regex_st.rx_prog.cls.d[w]; i += 1 }
} }
function rx_class_unset(idx: int, c: int) -> void { function rx_class_unset(rt_regex_st: mut RtRegexState, idx: int, c: int) -> void {
let w = idx * 8 + (c >> 5) let w = idx * 8 + (c >> 5)
rx_prog.cls.d[w] = rx_prog.cls.d[w] & ~(1 << (c & 31)) rt_regex_st.rx_prog.cls.d[w] = rt_regex_st.rx_prog.cls.d[w] & ~(1 << (c & 31))
} }
function rx_class_unset_range(idx: int, a: int, b: int) -> void { function rx_class_unset_range(rt_regex_st: mut RtRegexState, idx: int, a: int, b: int) -> void {
var c = a var c = a
while c <= b { rx_class_unset(idx, c); c += 1 } while c <= b { rx_class_unset(rt_regex_st, idx, c); c += 1 }
} }
function rx_class_set_all(idx: int) -> void { function rx_class_set_all(rt_regex_st: mut RtRegexState, idx: int) -> void {
var i = 0 var i = 0
while i < 8 { rx_prog.cls.d[idx * 8 + i] = -1; i += 1 } while i < 8 { rt_regex_st.rx_prog.cls.d[idx * 8 + i] = -1; i += 1 }
} }
function rx_class_set_word(idx: int) -> void { function rx_class_set_word(rt_regex_st: mut RtRegexState, idx: int) -> void {
rx_class_set_range(idx, 48, 57) rx_class_set_range(rt_regex_st, idx, 48, 57)
rx_class_set_range(idx, 65, 90) rx_class_set_range(rt_regex_st, idx, 65, 90)
rx_class_set_range(idx, 97, 122) rx_class_set_range(rt_regex_st, idx, 97, 122)
rx_class_set(idx, 95) rx_class_set(rt_regex_st, idx, 95)
} }
function rx_class_set_ws(idx: int) -> void { function rx_class_set_ws(rt_regex_st: mut RtRegexState, idx: int) -> void {
rx_class_set(idx, 32); rx_class_set(idx, 9); rx_class_set(idx, 10) rx_class_set(rt_regex_st, idx, 32); rx_class_set(rt_regex_st, idx, 9); rx_class_set(rt_regex_st, idx, 10)
rx_class_set(idx, 13); rx_class_set(idx, 12); rx_class_set(idx, 11) rx_class_set(rt_regex_st, idx, 13); rx_class_set(rt_regex_st, idx, 12); rx_class_set(rt_regex_st, idx, 11)
} }
function rx_class_has(prog: Prog, idx: int, c: int) -> bool { function rx_class_has(prog: Prog, idx: int, c: int) -> bool {
let w = prog.cls.d[idx * 8 + (c >> 5)] let w = prog.cls.d[idx * 8 + (c >> 5)]
@ -161,49 +164,49 @@ function rx_is_ws(c: int) -> bool {
# OR the set named by \d \D \w \W \s \S into class idx. The negated forms OR in # OR the set named by \d \D \w \W \s \S into class idx. The negated forms OR in
# the complement set bit-by-bit (never via set-all+unset, which would clobber a # the complement set bit-by-bit (never via set-all+unset, which would clobber a
# previously-set member — e.g. the literal `1` in [1\Da] must survive \D). # previously-set member — e.g. the literal `1` in [1\Da] must survive \D).
function rx_class_add_pre(idx: int, kind: int) -> void { function rx_class_add_pre(rt_regex_st: mut RtRegexState, idx: int, kind: int) -> void {
if kind == 100 { rx_class_set_range(idx, 48, 57); return } # \d if kind == 100 { rx_class_set_range(rt_regex_st, idx, 48, 57); return } # \d
if kind == 119 { rx_class_set_word(idx); return } # \w if kind == 119 { rx_class_set_word(rt_regex_st, idx); return } # \w
if kind == 115 { rx_class_set_ws(idx); return } # \s if kind == 115 { rx_class_set_ws(rt_regex_st, idx); return } # \s
var c = 0 var c = 0
while c < 256 { while c < 256 {
if kind == 68 { if not rx_is_digit(c) { rx_class_set(idx, c) } } # \D if kind == 68 { if not rx_is_digit(c) { rx_class_set(rt_regex_st, idx, c) } } # \D
else if kind == 87 { if not rx_is_word(c) { rx_class_set(idx, c) } } # \W else if kind == 87 { if not rx_is_word(c) { rx_class_set(rt_regex_st, idx, c) } } # \W
else if kind == 83 { if not rx_is_ws(c) { rx_class_set(idx, c) } } # \S else if kind == 83 { if not rx_is_ws(c) { rx_class_set(rt_regex_st, idx, c) } } # \S
c += 1 c += 1
} }
} }
# parse a [...] class starting at '['; returns an N_CLASS node # parse a [...] class starting at '['; returns an N_CLASS node
function rx_parse_class() -> RNode { function rx_parse_class(rt_regex_st: mut RtRegexState) -> RNode {
rx_adv() # consume '[' rx_adv(rt_regex_st) # consume '['
let idx = rx_class_new() let idx = rx_class_new(rt_regex_st)
var neg = false var neg = false
if rx_peek() == 94 { neg = true; rx_adv() } # [^ ...] if rx_peek(rt_regex_st) == 94 { neg = true; rx_adv(rt_regex_st) } # [^ ...]
# a ']' as the first char is a literal # a ']' as the first char is a literal
if rx_peek() == 93 { rx_class_set(idx, 93); rx_adv() } if rx_peek(rt_regex_st) == 93 { rx_class_set(rt_regex_st, idx, 93); rx_adv(rt_regex_st) }
while rx_peek() != 93 and rx_peek() >= 0 { while rx_peek(rt_regex_st) != 93 and rx_peek(rt_regex_st) >= 0 {
var lo = rx_adv() var lo = rx_adv(rt_regex_st)
if lo == 92 { # escape inside class if lo == 92 { # escape inside class
let e = rx_adv() let e = rx_adv(rt_regex_st)
if e == 'd' or e == 'D' or e == 'w' or e == 'W' or e == 's' or e == 'S' { if e == 'd' or e == 'D' or e == 'w' or e == 'W' or e == 's' or e == 'S' {
rx_class_add_pre(idx, e) rx_class_add_pre(rt_regex_st, idx, e)
continue continue
} }
lo = rx_class_escape_char(e) lo = rx_class_escape_char(e)
} }
# a range a-b (but a trailing '-' before ']' is literal) # a range a-b (but a trailing '-' before ']' is literal)
if rx_peek() == 45 and rx_peek2() != 93 and rx_peek2() >= 0 { if rx_peek(rt_regex_st) == 45 and rx_peek2(rt_regex_st) != 93 and rx_peek2(rt_regex_st) >= 0 {
rx_adv() # consume '-' rx_adv(rt_regex_st) # consume '-'
var hi = rx_adv() var hi = rx_adv(rt_regex_st)
if hi == 92 { hi = rx_class_escape_char(rx_adv()) } if hi == 92 { hi = rx_class_escape_char(rx_adv(rt_regex_st)) }
rx_class_set_range(idx, lo, hi) rx_class_set_range(rt_regex_st, idx, lo, hi)
} else { } else {
rx_class_set(idx, lo) rx_class_set(rt_regex_st, idx, lo)
} }
} }
if rx_peek() != 93 { rx_err = 1 } else { rx_adv() } # consume ']' if rx_peek(rt_regex_st) != 93 { rt_regex_st.rx_err = 1 } else { rx_adv(rt_regex_st) } # consume ']'
if neg { rx_class_negate(idx) } if neg { rx_class_negate(rt_regex_st, idx) }
let node = rx_node(N_CLASS) let node = rx_node(N_CLASS)
node.cls = idx node.cls = idx
return node return node
@ -220,17 +223,17 @@ function rx_class_escape_char(e: int) -> int {
} }
# ---- escapes outside a class ------------------------------------------------ # ---- escapes outside a class ------------------------------------------------
function rx_parse_escape() -> RNode { function rx_parse_escape(rt_regex_st: mut RtRegexState) -> RNode {
rx_adv() # consume '\' rx_adv(rt_regex_st) # consume '\'
let e = rx_adv() let e = rx_adv(rt_regex_st)
if e == 'd' or e == 'D' or e == 'w' or e == 'W' or e == 's' or e == 'S' { if e == 'd' or e == 'D' or e == 'w' or e == 'W' or e == 's' or e == 'S' {
let idx = rx_class_new() let idx = rx_class_new(rt_regex_st)
rx_class_add_pre(idx, e) rx_class_add_pre(rt_regex_st, idx, e)
let node = rx_node(N_CLASS) let node = rx_node(N_CLASS)
node.cls = idx node.cls = idx
return node return node
} }
if e >= '1' and e <= '9' { rx_err = 1; return rx_node(N_EMPTY) } # backrefs unsupported if e >= '1' and e <= '9' { rt_regex_st.rx_err = 1; return rx_node(N_EMPTY) } # backrefs unsupported
var ch = e var ch = e
if e == 'n' { ch = 10 } if e == 'n' { ch = 10 }
else if e == 't' { ch = 9 } else if e == 't' { ch = 9 }
@ -238,165 +241,164 @@ function rx_parse_escape() -> RNode {
else if e == 'f' { ch = 12 } else if e == 'f' { ch = 12 }
else if e == 'v' { ch = 11 } else if e == 'v' { ch = 11 }
else if e == '0' { ch = 0 } else if e == '0' { ch = 0 }
else if e < 0 { rx_err = 1; return rx_node(N_EMPTY) } else if e < 0 { rt_regex_st.rx_err = 1; return rx_node(N_EMPTY) }
let node = rx_node(N_LIT) let node = rx_node(N_LIT)
node.ch = ch node.ch = ch
return node return node
} }
# ---- parser ----------------------------------------------------------------- # ---- parser -----------------------------------------------------------------
function rx_parse_alt() -> RNode { function rx_parse_alt(rt_regex_st: mut RtRegexState) -> RNode {
let first = rx_parse_concat() let first = rx_parse_concat(rt_regex_st)
if rx_peek() != 124 { return first } if rx_peek(rt_regex_st) != 124 { return first }
let alt = rx_node(N_ALT) let alt = rx_node(N_ALT)
push(alt.kids, first) push(alt.kids, first)
while rx_peek() == 124 { while rx_peek(rt_regex_st) == 124 {
rx_adv() rx_adv(rt_regex_st)
push(alt.kids, rx_parse_concat()) push(alt.kids, rx_parse_concat(rt_regex_st))
if rx_err == 1 { break } if rt_regex_st.rx_err == 1 { break }
} }
return alt return alt
} }
function rx_parse_concat() -> RNode { function rx_parse_concat(rt_regex_st: mut RtRegexState) -> RNode {
let cat = rx_node(N_CONCAT) let cat = rx_node(N_CONCAT)
while true { while true {
let c = rx_peek() let c = rx_peek(rt_regex_st)
if c < 0 or c == '|' or c == ')' { break } if c < 0 or c == '|' or c == ')' { break }
push(cat.kids, rx_parse_repeat()) push(cat.kids, rx_parse_repeat(rt_regex_st))
if rx_err == 1 { break } if rt_regex_st.rx_err == 1 { break }
} }
if len(cat.kids) == 1 { return cat.kids[0] } if len(cat.kids) == 1 { return cat.kids[0] }
if len(cat.kids) == 0 { return rx_node(N_EMPTY) } if len(cat.kids) == 0 { return rx_node(N_EMPTY) }
return cat return cat
} }
# read the optional lazy '?' after a quantifier; returns greedy flag (0 = lazy) # read the optional lazy '?' after a quantifier; returns greedy flag (0 = lazy)
function rx_lazy() -> int { function rx_lazy(rt_regex_st: mut RtRegexState) -> int {
if rx_peek() == 63 { rx_adv(); return 0 } if rx_peek(rt_regex_st) == 63 { rx_adv(rt_regex_st); return 0 }
return 1 return 1
} }
function rx_parse_repeat() -> RNode { function rx_parse_repeat(rt_regex_st: mut RtRegexState) -> RNode {
let atom = rx_parse_atom() let atom = rx_parse_atom(rt_regex_st)
if rx_err == 1 { return atom } if rt_regex_st.rx_err == 1 { return atom }
let c = rx_peek() let c = rx_peek(rt_regex_st)
if c == '*' or c == '+' or c == '?' { if c == '*' or c == '+' or c == '?' {
rx_adv() rx_adv(rt_regex_st)
var op = N_STAR var op = N_STAR
if c == '+' { op = N_PLUS } if c == '+' { op = N_PLUS }
if c == '?' { op = N_QUEST } if c == '?' { op = N_QUEST }
let r = rx_node(op) let r = rx_node(op)
r.greedy = rx_lazy() r.greedy = rx_lazy(rt_regex_st)
push(r.kids, atom) push(r.kids, atom)
return r return r
} }
if c == '{' { return rx_parse_brace(atom) } if c == '{' { return rx_parse_brace(rt_regex_st, atom) }
return atom return atom
} }
# {n} {n,} {n,m} # {n} {n,} {n,m}
function rx_parse_brace(atom: RNode) -> RNode { function rx_parse_brace(rt_regex_st: mut RtRegexState, atom: RNode) -> RNode {
let save = rx_pos let save = rt_regex_st.rx_pos
rx_adv() # consume '{' rx_adv(rt_regex_st) # consume '{'
var lo = 0 var lo = 0
var haslo = false var haslo = false
while rx_peek() >= 48 and rx_peek() <= 57 { lo = lo * 10 + (rx_adv() - 48); haslo = true } while rx_peek(rt_regex_st) >= 48 and rx_peek(rt_regex_st) <= 57 { lo = lo * 10 + (rx_adv(rt_regex_st) - 48); haslo = true }
var hi = lo var hi = lo
var hasComma = false var hasComma = false
if rx_peek() == 44 { hasComma = true; rx_adv(); hi = -1 if rx_peek(rt_regex_st) == 44 { hasComma = true; rx_adv(rt_regex_st); hi = -1
var hashi = false var hashi = false
while rx_peek() >= 48 and rx_peek() <= 57 { if not hashi { hi = 0 }; hi = hi * 10 + (rx_adv() - 48); hashi = true } while rx_peek(rt_regex_st) >= 48 and rx_peek(rt_regex_st) <= 57 { if not hashi { hi = 0 }; hi = hi * 10 + (rx_adv(rt_regex_st) - 48); hashi = true }
} }
if rx_peek() != 125 or not haslo { # not a valid brace -> literal '{' if rx_peek(rt_regex_st) != 125 or not haslo { # not a valid brace -> literal '{'
rx_pos = save rt_regex_st.rx_pos = save
rx_adv() rx_adv(rt_regex_st)
let n = rx_node(N_LIT); n.ch = 123; return n let n = rx_node(N_LIT); n.ch = 123; return n
} }
rx_adv() # consume '}' rx_adv(rt_regex_st) # consume '}'
let r = rx_node(N_REP) let r = rx_node(N_REP)
r.lo = lo r.lo = lo
r.hi = hi r.hi = hi
r.greedy = rx_lazy() r.greedy = rx_lazy(rt_regex_st)
push(r.kids, atom) push(r.kids, atom)
return r return r
} }
function rx_parse_atom() -> RNode { function rx_parse_atom(rt_regex_st: mut RtRegexState) -> RNode {
let c = rx_peek() let c = rx_peek(rt_regex_st)
if c == '(' { # '(' if c == '(' { # '('
rx_adv() rx_adv(rt_regex_st)
var gidx = -1 var gidx = -1
if rx_peek() == 63 { # (? ... if rx_peek(rt_regex_st) == 63 { # (? ...
rx_adv() rx_adv(rt_regex_st)
let d = rx_peek() let d = rx_peek(rt_regex_st)
if d == ':' { rx_adv() } # (?: non-capturing if d == ':' { rx_adv(rt_regex_st) } # (?: non-capturing
else if d == 'P' { rx_adv(); rx_skip_name() } # (?P<name> (captured, name ignored for now) else if d == 'P' { rx_adv(rt_regex_st); rx_skip_name(rt_regex_st) } # (?P<name> (captured, name ignored for now)
else if d == '<' { rx_skip_name() } # (?<name> else if d == '<' { rx_skip_name(rt_regex_st) } # (?<name>
else { rx_err = 1; return rx_node(N_EMPTY) } else { rt_regex_st.rx_err = 1; return rx_node(N_EMPTY) }
} else { } else {
rx_ngroup += 1 rt_regex_st.rx_ngroup += 1
gidx = rx_ngroup gidx = rt_regex_st.rx_ngroup
} }
let inner = rx_parse_alt() let inner = rx_parse_alt(rt_regex_st)
if rx_peek() != 41 { rx_err = 1; return rx_node(N_EMPTY) } if rx_peek(rt_regex_st) != 41 { rt_regex_st.rx_err = 1; return rx_node(N_EMPTY) }
rx_adv() # consume ')' rx_adv(rt_regex_st) # consume ')'
let g = rx_node(N_GROUP) let g = rx_node(N_GROUP)
g.gidx = gidx g.gidx = gidx
push(g.kids, inner) push(g.kids, inner)
return g return g
} }
if c == '[' { return rx_parse_class() } if c == '[' { return rx_parse_class(rt_regex_st) }
if c == '.' { rx_adv(); return rx_node(N_ANY) } if c == '.' { rx_adv(rt_regex_st); return rx_node(N_ANY) }
if c == '^' { rx_adv(); return rx_node(N_BOL) } if c == '^' { rx_adv(rt_regex_st); return rx_node(N_BOL) }
if c == '$' { rx_adv(); return rx_node(N_EOL) } if c == '$' { rx_adv(rt_regex_st); return rx_node(N_EOL) }
if c == '\\' { return rx_parse_escape() } if c == '\\' { return rx_parse_escape(rt_regex_st) }
if c == '*' or c == '+' or c == '?' or c == ')' { rx_err = 1; return rx_node(N_EMPTY) } if c == '*' or c == '+' or c == '?' or c == ')' { rt_regex_st.rx_err = 1; return rx_node(N_EMPTY) }
if c < 0 { rx_err = 1; return rx_node(N_EMPTY) } if c < 0 { rt_regex_st.rx_err = 1; return rx_node(N_EMPTY) }
rx_adv() rx_adv(rt_regex_st)
let n = rx_node(N_LIT) let n = rx_node(N_LIT)
n.ch = c n.ch = c
return n return n
} }
# skip a (?P<name> / (?<name> group name up to '>'; leaves a capturing group # skip a (?P<name> / (?<name> group name up to '>'; leaves a capturing group
function rx_skip_name() -> void { function rx_skip_name(rt_regex_st: mut RtRegexState) -> void {
if rx_peek() == 80 { rx_adv() } # already consumed by caller in (?P case? guard if rx_peek(rt_regex_st) == 80 { rx_adv(rt_regex_st) } # already consumed by caller in (?P case? guard
if rx_peek() == 60 { rx_adv() } # consume '<' if rx_peek(rt_regex_st) == 60 { rx_adv(rt_regex_st) } # consume '<'
while rx_peek() != 62 and rx_peek() >= 0 { rx_adv() } while rx_peek(rt_regex_st) != 62 and rx_peek(rt_regex_st) >= 0 { rx_adv(rt_regex_st) }
if rx_peek() == 62 { rx_adv() } # consume '>' if rx_peek(rt_regex_st) == 62 { rx_adv(rt_regex_st) } # consume '>'
rx_ngroup += 1 rt_regex_st.rx_ngroup += 1
# note: the caller set gidx = -1; fix it up to a real capture index # note: the caller set gidx = -1; fix it up to a real capture index
rx_named_gidx = rx_ngroup rt_regex_st.rx_named_gidx = rt_regex_st.rx_ngroup
} }
var rx_named_gidx: int = 0
# ---- compile AST -> program ------------------------------------------------- # ---- compile AST -> program -------------------------------------------------
function pg_emit(op: int, a: int, b: int) -> int { function pg_emit(rt_regex_st: RtRegexState, op: int, a: int, b: int) -> int {
let pc = rx_prog.code.n / 3 let pc = rt_regex_st.rx_prog.code.n / 3
iv_push(rx_prog.code, op) iv_push(rt_regex_st.rx_prog.code, op)
iv_push(rx_prog.code, a) iv_push(rt_regex_st.rx_prog.code, a)
iv_push(rx_prog.code, b) iv_push(rt_regex_st.rx_prog.code, b)
return pc return pc
} }
function pg_set_a(pc: int, a: int) -> void { rx_prog.code.d[3 * pc + 1] = a } function pg_set_a(rt_regex_st: mut RtRegexState, pc: int, a: int) -> void { rt_regex_st.rx_prog.code.d[3 * pc + 1] = a }
function pg_set_b(pc: int, b: int) -> void { rx_prog.code.d[3 * pc + 2] = b } function pg_set_b(rt_regex_st: mut RtRegexState, pc: int, b: int) -> void { rt_regex_st.rx_prog.code.d[3 * pc + 2] = b }
function pg_pc() -> int { return rx_prog.code.n / 3 } function pg_pc(rt_regex_st: RtRegexState) -> int { return rt_regex_st.rx_prog.code.n / 3 }
function rx_compile(node: RNode) -> void { function rx_compile(rt_regex_st: mut RtRegexState, node: RNode) -> void {
let op = node.op let op = node.op
if op == N_EMPTY { return } if op == N_EMPTY { return }
if op == N_LIT { pg_emit(OP_CHAR, node.ch, 0); return } if op == N_LIT { pg_emit(rt_regex_st, OP_CHAR, node.ch, 0); return }
if op == N_ANY { pg_emit(OP_ANY, 0, 0); return } if op == N_ANY { pg_emit(rt_regex_st, OP_ANY, 0, 0); return }
if op == N_CLASS { pg_emit(OP_CLASS, node.cls, 0); return } if op == N_CLASS { pg_emit(rt_regex_st, OP_CLASS, node.cls, 0); return }
if op == N_BOL { pg_emit(OP_BOL, 0, 0); return } if op == N_BOL { pg_emit(rt_regex_st, OP_BOL, 0, 0); return }
if op == N_EOL { pg_emit(OP_EOL, 0, 0); return } if op == N_EOL { pg_emit(rt_regex_st, OP_EOL, 0, 0); return }
if op == N_CONCAT { if op == N_CONCAT {
var i = 0 var i = 0
while i < len(node.kids) { rx_compile(node.kids[i]); i += 1 } while i < len(node.kids) { rx_compile(rt_regex_st, node.kids[i]); i += 1 }
return return
} }
if op == N_GROUP { if op == N_GROUP {
if node.gidx >= 0 { if node.gidx >= 0 {
pg_emit(OP_SAVE, 2 * node.gidx, 0) pg_emit(rt_regex_st, OP_SAVE, 2 * node.gidx, 0)
rx_compile(node.kids[0]) rx_compile(rt_regex_st, node.kids[0])
pg_emit(OP_SAVE, 2 * node.gidx + 1, 0) pg_emit(rt_regex_st, OP_SAVE, 2 * node.gidx + 1, 0)
} else { } else {
rx_compile(node.kids[0]) rx_compile(rt_regex_st, node.kids[0])
} }
return return
} }
@ -405,67 +407,67 @@ function rx_compile(node: RNode) -> void {
var i = 0 var i = 0
while i < len(node.kids) { while i < len(node.kids) {
if i < len(node.kids) - 1 { if i < len(node.kids) - 1 {
let sp = pg_emit(OP_SPLIT, 0, 0) let sp = pg_emit(rt_regex_st, OP_SPLIT, 0, 0)
pg_set_a(sp, pg_pc()) pg_set_a(rt_regex_st, sp, pg_pc(rt_regex_st))
rx_compile(node.kids[i]) rx_compile(rt_regex_st, node.kids[i])
let j = pg_emit(OP_JMP, 0, 0) let j = pg_emit(rt_regex_st, OP_JMP, 0, 0)
iv_push(jmps, j) iv_push(jmps, j)
pg_set_b(sp, pg_pc()) pg_set_b(rt_regex_st, sp, pg_pc(rt_regex_st))
} else { } else {
rx_compile(node.kids[i]) rx_compile(rt_regex_st, node.kids[i])
} }
i += 1 i += 1
} }
let end = pg_pc() let end = pg_pc(rt_regex_st)
i = 0 i = 0
while i < jmps.n { pg_set_a(jmps.d[i], end); i += 1 } while i < jmps.n { pg_set_a(rt_regex_st, jmps.d[i], end); i += 1 }
return return
} }
if op == N_STAR { if op == N_STAR {
let l1 = pg_pc() let l1 = pg_pc(rt_regex_st)
let sp = pg_emit(OP_SPLIT, 0, 0) let sp = pg_emit(rt_regex_st, OP_SPLIT, 0, 0)
let l2 = pg_pc() let l2 = pg_pc(rt_regex_st)
rx_compile(node.kids[0]) rx_compile(rt_regex_st, node.kids[0])
pg_emit(OP_JMP, l1, 0) pg_emit(rt_regex_st, OP_JMP, l1, 0)
let l3 = pg_pc() let l3 = pg_pc(rt_regex_st)
if node.greedy == 1 { pg_set_a(sp, l2); pg_set_b(sp, l3) } if node.greedy == 1 { pg_set_a(rt_regex_st, sp, l2); pg_set_b(rt_regex_st, sp, l3) }
else { pg_set_a(sp, l3); pg_set_b(sp, l2) } else { pg_set_a(rt_regex_st, sp, l3); pg_set_b(rt_regex_st, sp, l2) }
return return
} }
if op == N_PLUS { if op == N_PLUS {
let l1 = pg_pc() let l1 = pg_pc(rt_regex_st)
rx_compile(node.kids[0]) rx_compile(rt_regex_st, node.kids[0])
let sp = pg_emit(OP_SPLIT, 0, 0) let sp = pg_emit(rt_regex_st, OP_SPLIT, 0, 0)
let l3 = pg_pc() let l3 = pg_pc(rt_regex_st)
if node.greedy == 1 { pg_set_a(sp, l1); pg_set_b(sp, l3) } if node.greedy == 1 { pg_set_a(rt_regex_st, sp, l1); pg_set_b(rt_regex_st, sp, l3) }
else { pg_set_a(sp, l3); pg_set_b(sp, l1) } else { pg_set_a(rt_regex_st, sp, l3); pg_set_b(rt_regex_st, sp, l1) }
return return
} }
if op == N_QUEST { if op == N_QUEST {
let sp = pg_emit(OP_SPLIT, 0, 0) let sp = pg_emit(rt_regex_st, OP_SPLIT, 0, 0)
let l2 = pg_pc() let l2 = pg_pc(rt_regex_st)
rx_compile(node.kids[0]) rx_compile(rt_regex_st, node.kids[0])
let l3 = pg_pc() let l3 = pg_pc(rt_regex_st)
if node.greedy == 1 { pg_set_a(sp, l2); pg_set_b(sp, l3) } if node.greedy == 1 { pg_set_a(rt_regex_st, sp, l2); pg_set_b(rt_regex_st, sp, l3) }
else { pg_set_a(sp, l3); pg_set_b(sp, l2) } else { pg_set_a(rt_regex_st, sp, l3); pg_set_b(rt_regex_st, sp, l2) }
return return
} }
if op == N_REP { if op == N_REP {
let kid = node.kids[0] let kid = node.kids[0]
var i = 0 var i = 0
while i < node.lo { rx_compile(kid); i += 1 } while i < node.lo { rx_compile(rt_regex_st, kid); i += 1 }
if node.hi < 0 { if node.hi < 0 {
let st = rx_node(N_STAR) let st = rx_node(N_STAR)
st.greedy = node.greedy st.greedy = node.greedy
push(st.kids, kid) push(st.kids, kid)
rx_compile(st) rx_compile(rt_regex_st, st)
} else { } else {
i = 0 i = 0
while i < node.hi - node.lo { while i < node.hi - node.lo {
let q = rx_node(N_QUEST) let q = rx_node(N_QUEST)
q.greedy = node.greedy q.greedy = node.greedy
push(q.kids, kid) push(q.kids, kid)
rx_compile(q) rx_compile(rt_regex_st, q)
i += 1 i += 1
} }
} }
@ -474,32 +476,32 @@ function rx_compile(node: RNode) -> void {
} }
# regex_compile(pattern) -> Prog (null on a syntax error) # regex_compile(pattern) -> Prog (null on a syntax error)
function regex_compile(pattern: pointer) -> Prog { function regex_compile(rt_regex_st: mut RtRegexState, pattern: pointer) -> Prog {
let p = new Prog let p = new Prog
p.code = iv_new() p.code = iv_new()
p.cls = iv_new() p.cls = iv_new()
p.ngroups = 0 p.ngroups = 0
p.ok = 1 p.ok = 1
rx_prog = p rt_regex_st.rx_prog = p
rx_pat = pattern rt_regex_st.rx_pat = pattern
rx_pos = 0 rt_regex_st.rx_pos = 0
rx_len = rx_slen(pattern) rt_regex_st.rx_len = rx_slen(pattern)
rx_err = 0 rt_regex_st.rx_err = 0
rx_ngroup = 0 rt_regex_st.rx_ngroup = 0
let root = rx_parse_alt() let root = rx_parse_alt(rt_regex_st)
if rx_err == 1 or rx_pos != rx_len { return null } if rt_regex_st.rx_err == 1 or rt_regex_st.rx_pos != rt_regex_st.rx_len { return null }
p.ngroups = rx_ngroup p.ngroups = rt_regex_st.rx_ngroup
# unanchored lazy .*? prefix so a match may start at any position # unanchored lazy .*? prefix so a match may start at any position
let sp0 = pg_emit(OP_SPLIT, 0, 0) let sp0 = pg_emit(rt_regex_st, OP_SPLIT, 0, 0)
let consume = pg_pc() let consume = pg_pc(rt_regex_st)
pg_emit(OP_ANYNL, 0, 0) pg_emit(rt_regex_st, OP_ANYNL, 0, 0)
pg_emit(OP_JMP, sp0, 0) pg_emit(rt_regex_st, OP_JMP, sp0, 0)
let body = pg_pc() let body = pg_pc(rt_regex_st)
pg_set_a(sp0, body) pg_set_a(rt_regex_st, sp0, body)
pg_set_b(sp0, consume) pg_set_b(rt_regex_st, sp0, consume)
pg_emit(OP_SAVE, 0, 0) pg_emit(rt_regex_st, OP_SAVE, 0, 0)
rx_compile(root) rx_compile(rt_regex_st, root)
pg_emit(OP_SAVE, 1, 0) pg_emit(rt_regex_st, OP_SAVE, 1, 0)
pg_emit(OP_MATCH, 0, 0) pg_emit(rt_regex_st, OP_MATCH, 0, 0)
return p return p
} }

View file

@ -7,66 +7,68 @@
# ---- Pike VM ---------------------------------------------------------------- # ---- Pike VM ----------------------------------------------------------------
property TList { pc: words, caps: words, n: int } property TList { pc: words, caps: words, n: int }
var rx_seen: words = null export state RtRegexVmState {
var rx_gen: int = 0 rx_seen: words = null
var rx_nc: int = 0 rx_gen: int = 0
var rx_code: words = null rx_nc: int = 0
rx_code: words = null
}
function rx_add(list: TList, pc: int, caps: words, sp: int, s: pointer, slen: int) -> void { function rx_add(rt_regex_vm_st: mut RtRegexVmState, list: TList, pc: int, caps: words, sp: int, s: pointer, slen: int) -> void {
if rx_seen[pc] == rx_gen { return } if rt_regex_vm_st.rx_seen[pc] == rt_regex_vm_st.rx_gen { return }
rx_seen[pc] = rx_gen rt_regex_vm_st.rx_seen[pc] = rt_regex_vm_st.rx_gen
let op = rx_code[3 * pc] let op = rt_regex_vm_st.rx_code[3 * pc]
if op == OP_JMP { rx_add(list, rx_code[3 * pc + 1], caps, sp, s, slen); return } if op == OP_JMP { rx_add(rt_regex_vm_st, list, rt_regex_vm_st.rx_code[3 * pc + 1], caps, sp, s, slen); return }
if op == OP_SPLIT { if op == OP_SPLIT {
rx_add(list, rx_code[3 * pc + 1], caps, sp, s, slen) rx_add(rt_regex_vm_st, list, rt_regex_vm_st.rx_code[3 * pc + 1], caps, sp, s, slen)
rx_add(list, rx_code[3 * pc + 2], caps, sp, s, slen) rx_add(rt_regex_vm_st, list, rt_regex_vm_st.rx_code[3 * pc + 2], caps, sp, s, slen)
return return
} }
if op == OP_SAVE { if op == OP_SAVE {
let slot = rx_code[3 * pc + 1] let slot = rt_regex_vm_st.rx_code[3 * pc + 1]
let old = caps[slot] let old = caps[slot]
caps[slot] = sp caps[slot] = sp
rx_add(list, pc + 1, caps, sp, s, slen) rx_add(rt_regex_vm_st, list, pc + 1, caps, sp, s, slen)
caps[slot] = old caps[slot] = old
return return
} }
if op == OP_BOL { if op == OP_BOL {
if sp == 0 { rx_add(list, pc + 1, caps, sp, s, slen) } if sp == 0 { rx_add(rt_regex_vm_st, list, pc + 1, caps, sp, s, slen) }
return return
} }
if op == OP_EOL { if op == OP_EOL {
if sp == slen { rx_add(list, pc + 1, caps, sp, s, slen) } if sp == slen { rx_add(rt_regex_vm_st, list, pc + 1, caps, sp, s, slen) }
else if sp == slen - 1 and (s[sp] & 255) == 10 { rx_add(list, pc + 1, caps, sp, s, slen) } else if sp == slen - 1 and (s[sp] & 255) == 10 { rx_add(rt_regex_vm_st, list, pc + 1, caps, sp, s, slen) }
return return
} }
# a leaf that consumes (CHAR/ANY/ANYNL/CLASS) or MATCH: record it # a leaf that consumes (CHAR/ANY/ANYNL/CLASS) or MATCH: record it
let t = list.n let t = list.n
list.pc[t] = pc list.pc[t] = pc
var i = 0 var i = 0
while i < rx_nc { list.caps[t * rx_nc + i] = caps[i]; i += 1 } while i < rt_regex_vm_st.rx_nc { list.caps[t * rt_regex_vm_st.rx_nc + i] = caps[i]; i += 1 }
list.n += 1 list.n += 1
} }
# run prog over s (length slen) from startpos; returns caps words or null # run prog over s (length slen) from startpos; returns caps words or null
function rx_run(prog: Prog, s: pointer, slen: int, startpos: int) -> words { function rx_run(rt_regex_vm_st: mut RtRegexVmState, prog: Prog, s: pointer, slen: int, startpos: int) -> words {
rx_code = prog.code.d rt_regex_vm_st.rx_code = prog.code.d
rx_nc = 2 * (prog.ngroups + 1) rt_regex_vm_st.rx_nc = 2 * (prog.ngroups + 1)
let ncode = prog.code.n / 3 let ncode = prog.code.n / 3
rx_seen = words(ncode) rt_regex_vm_st.rx_seen = words(ncode)
var i = 0 var i = 0
while i < ncode { rx_seen[i] = 0; i += 1 } while i < ncode { rt_regex_vm_st.rx_seen[i] = 0; i += 1 }
rx_gen = 0 rt_regex_vm_st.rx_gen = 0
var clist = new TList var clist = new TList
clist.pc = words(ncode); clist.caps = words(ncode * rx_nc); clist.n = 0 clist.pc = words(ncode); clist.caps = words(ncode * rt_regex_vm_st.rx_nc); clist.n = 0
var nlist = new TList var nlist = new TList
nlist.pc = words(ncode); nlist.caps = words(ncode * rx_nc); nlist.n = 0 nlist.pc = words(ncode); nlist.caps = words(ncode * rt_regex_vm_st.rx_nc); nlist.n = 0
let wcaps = words(rx_nc) let wcaps = words(rt_regex_vm_st.rx_nc)
var matched: words = null var matched: words = null
rx_gen += 1 rt_regex_vm_st.rx_gen += 1
i = 0 i = 0
while i < rx_nc { wcaps[i] = -1; i += 1 } while i < rt_regex_vm_st.rx_nc { wcaps[i] = -1; i += 1 }
rx_add(clist, 0, wcaps, startpos, s, slen) rx_add(rt_regex_vm_st, clist, 0, wcaps, startpos, s, slen)
var sp = startpos var sp = startpos
while true { while true {
@ -74,26 +76,26 @@ function rx_run(prog: Prog, s: pointer, slen: int, startpos: int) -> words {
var c = -1 var c = -1
if sp < slen { c = s[sp] & 255 } if sp < slen { c = s[sp] & 255 }
nlist.n = 0 nlist.n = 0
rx_gen += 1 rt_regex_vm_st.rx_gen += 1
var ti = 0 var ti = 0
var stop = false var stop = false
while ti < clist.n and not stop { while ti < clist.n and not stop {
let pc = clist.pc[ti] let pc = clist.pc[ti]
var k = 0 var k = 0
while k < rx_nc { wcaps[k] = clist.caps[ti * rx_nc + k]; k += 1 } while k < rt_regex_vm_st.rx_nc { wcaps[k] = clist.caps[ti * rt_regex_vm_st.rx_nc + k]; k += 1 }
let op = rx_code[3 * pc] let op = rt_regex_vm_st.rx_code[3 * pc]
if op == OP_CHAR { if op == OP_CHAR {
if c >= 0 and c == rx_code[3 * pc + 1] { rx_add(nlist, pc + 1, wcaps, sp + 1, s, slen) } if c >= 0 and c == rt_regex_vm_st.rx_code[3 * pc + 1] { rx_add(rt_regex_vm_st, nlist, pc + 1, wcaps, sp + 1, s, slen) }
} else if op == OP_ANY { } else if op == OP_ANY {
if c >= 0 and c != '\n' { rx_add(nlist, pc + 1, wcaps, sp + 1, s, slen) } if c >= 0 and c != '\n' { rx_add(rt_regex_vm_st, nlist, pc + 1, wcaps, sp + 1, s, slen) }
} else if op == OP_ANYNL { } else if op == OP_ANYNL {
if c >= 0 { rx_add(nlist, pc + 1, wcaps, sp + 1, s, slen) } if c >= 0 { rx_add(rt_regex_vm_st, nlist, pc + 1, wcaps, sp + 1, s, slen) }
} else if op == OP_CLASS { } else if op == OP_CLASS {
if c >= 0 and rx_class_has(prog, rx_code[3 * pc + 1], c) { rx_add(nlist, pc + 1, wcaps, sp + 1, s, slen) } if c >= 0 and rx_class_has(prog, rt_regex_vm_st.rx_code[3 * pc + 1], c) { rx_add(rt_regex_vm_st, nlist, pc + 1, wcaps, sp + 1, s, slen) }
} else if op == OP_MATCH { } else if op == OP_MATCH {
if matched == null { matched = words(rx_nc) } if matched == null { matched = words(rt_regex_vm_st.rx_nc) }
k = 0 k = 0
while k < rx_nc { matched[k] = wcaps[k]; k += 1 } while k < rt_regex_vm_st.rx_nc { matched[k] = wcaps[k]; k += 1 }
stop = true stop = true
} }
ti += 1 ti += 1
@ -108,33 +110,33 @@ function rx_run(prog: Prog, s: pointer, slen: int, startpos: int) -> words {
# ---- public API ------------------------------------------------------------- # ---- public API -------------------------------------------------------------
property Match { str: pointer = null, ng: int = 0, caps: words = null } property Match { str: pointer = null, ng: int = 0, caps: words = null }
function regex_matches(str: pointer, pattern: pointer) -> bool { function regex_matches(rt_regex_st: mut RtRegexState, rt_regex_vm_st: mut RtRegexVmState, str: pointer, pattern: pointer) -> bool {
let p = regex_compile(pattern) let p = regex_compile(rt_regex_st, pattern)
if p == null { return false } if p == null { return false }
return rx_run(p, str, rx_slen(str), 0) != null return rx_run(rt_regex_vm_st, p, str, rx_slen(str), 0) != null
} }
function regex_test(str: pointer, re: Prog) -> bool { function regex_test(rt_regex_vm_st: mut RtRegexVmState, str: pointer, re: Prog) -> bool {
if re == null { return false } if re == null { return false }
return rx_run(re, str, rx_slen(str), 0) != null return rx_run(rt_regex_vm_st, re, str, rx_slen(str), 0) != null
} }
function regex_exec(str: pointer, re: Prog) -> Match { function regex_exec(rt_regex_vm_st: mut RtRegexVmState, str: pointer, re: Prog) -> Match {
if re == null { return null } if re == null { return null }
let caps = rx_run(re, str, rx_slen(str), 0) let caps = rx_run(rt_regex_vm_st, re, str, rx_slen(str), 0)
if caps == null { return null } if caps == null { return null }
let m = new Match let m = new Match
m.str = str; m.ng = re.ngroups; m.caps = caps m.str = str; m.ng = re.ngroups; m.caps = caps
return m return m
} }
function regex_find(str: pointer, pattern: pointer) -> Match { function regex_find(rt_regex_st: mut RtRegexState, rt_regex_vm_st: mut RtRegexVmState, str: pointer, pattern: pointer) -> Match {
let p = regex_compile(pattern) let p = regex_compile(rt_regex_st, pattern)
if p == null { return null } if p == null { return null }
return regex_exec(str, p) return regex_exec(rt_regex_vm_st, str, p)
} }
function regex_next(str: pointer, re: Prog, from: int) -> Match { function regex_next(rt_regex_vm_st: mut RtRegexVmState, str: pointer, re: Prog, from: int) -> Match {
if re == null { return null } if re == null { return null }
let n = rx_slen(str) let n = rx_slen(str)
if from > n { return null } if from > n { return null }
let caps = rx_run(re, str, n, from) let caps = rx_run(rt_regex_vm_st, re, str, n, from)
if caps == null { return null } if caps == null { return null }
let m = new Match let m = new Match
m.str = str; m.ng = re.ngroups; m.caps = caps m.str = str; m.ng = re.ngroups; m.caps = caps
@ -161,7 +163,7 @@ function regex_group(m: Match, n: int) -> string {
out[b - a] = 0 out[b - a] = 0
return out return out
} }
function regex_valid(pattern: pointer) -> bool { return regex_compile(pattern) != null } function regex_valid(rt_regex_st: mut RtRegexState, pattern: pointer) -> bool { return regex_compile(rt_regex_st, pattern) != null }
# regex_replace(str, pattern, repl): replace all non-overlapping matches. # regex_replace(str, pattern, repl): replace all non-overlapping matches.
# repl expands \0..\9 (groups; \0 = whole match) and \\ (a literal backslash). # repl expands \0..\9 (groups; \0 = whole match) and \\ (a literal backslash).
@ -169,8 +171,8 @@ function rx_append(out: IVec, s: pointer, a: int, b: int) -> void {
var i = a var i = a
while i < b { iv_push(out, s[i] & 255); i += 1 } while i < b { iv_push(out, s[i] & 255); i += 1 }
} }
function regex_replace(str: pointer, pattern: pointer, repl: pointer) -> string { function regex_replace(rt_regex_st: mut RtRegexState, rt_regex_vm_st: mut RtRegexVmState, str: pointer, pattern: pointer, repl: pointer) -> string {
let p = regex_compile(pattern) let p = regex_compile(rt_regex_st, pattern)
if p == null { return str } if p == null { return str }
let n = rx_slen(str) let n = rx_slen(str)
let rn = rx_slen(repl) let rn = rx_slen(repl)
@ -178,7 +180,7 @@ function regex_replace(str: pointer, pattern: pointer, repl: pointer) -> string
var pos = 0 var pos = 0
var prev = 0 var prev = 0
while pos <= n { while pos <= n {
let caps = rx_run(p, str, n, pos) let caps = rx_run(rt_regex_vm_st, p, str, n, pos)
if caps == null { break } if caps == null { break }
let ms = caps[0] let ms = caps[0]
let me = caps[1] let me = caps[1]

View file

@ -1,63 +1,65 @@
# rng.ludic — the engine's seeded random numbers (Random.*, seed, rng_*): xorshift32, the same # rng.ludic — the engine's seeded random numbers (Random.*, seed, rng_*): xorshift32, the same
# stream on every machine. Its own file so a program with no ECS - a tool, a test program - gets # stream on every machine. Its own file so a program with no ECS - a tool, a test program - gets
# Random.* too: the compiler splices it on demand, and core.ludic imports it for a game. # Random.* too: the compiler splices it on demand, and core.ludic imports it for a game.
var rt_rng: int = 305419896 # xorshift32 state export state RtRngState {
rt_rng: int = 305419896 # xorshift32 state
}
# ---- rng (xorshift32) ----------------------------------------------------- # ---- rng (xorshift32) -----------------------------------------------------
function rt_seed(s: int) -> void { function rt_seed(rt_rng_st: mut RtRngState, s: int) -> void {
if s == 0 { if s == 0 {
rt_rng = 305419896 rt_rng_st.rt_rng = 305419896
return return
} }
rt_rng = s rt_rng_st.rt_rng = s
} }
# xorshift32 (Marsaglia). Runs on the raw 32-bit pattern, so the sign bit is # xorshift32 (Marsaglia). Runs on the raw 32-bit pattern, so the sign bit is
# masked off only when a caller asks for a number. # masked off only when a caller asks for a number.
function rt_next_rand() -> int { function rt_next_rand(rt_rng_st: mut RtRngState) -> int {
var x = rt_rng var x = rt_rng_st.rt_rng
x = (x ^ (x << 13)) x = (x ^ (x << 13))
x = (x ^ (x >> 17)) x = (x ^ (x >> 17))
x = (x ^ (x << 5)) x = (x ^ (x << 5))
rt_rng = x rt_rng_st.rt_rng = x
return (x & 2147483647) return (x & 2147483647)
} }
function rt_rng_range(lo: int, hi: int) -> int { function rt_rng_range(rt_rng_st: mut RtRngState, lo: int, hi: int) -> int {
if hi <= lo { return lo } if hi <= lo { return lo }
return lo + rt_next_rand() % (hi - lo + 1) return lo + rt_next_rand(rt_rng_st) % (hi - lo + 1)
} }
function rt_rng_chance(pct: int) -> bool { function rt_rng_chance(rt_rng_st: mut RtRngState, pct: int) -> bool {
return rt_next_rand() % 100 < pct return rt_next_rand(rt_rng_st) % 100 < pct
} }
# a deterministic fixed-point value in [0, 1) — the raw 0..65535 is exactly the # a deterministic fixed-point value in [0, 1) — the raw 0..65535 is exactly the
# Q16.16 fraction (fixed and int share the i32 representation). # Q16.16 fraction (fixed and int share the i32 representation).
function rt_rng_value() -> fixed { function rt_rng_value(rt_rng_st: mut RtRngState) -> fixed {
return rt_rng_range(0, 65535) return rt_rng_range(rt_rng_st, 0, 65535)
} }
# a deterministic integer in [0, max) — 0 when max <= 0 # a deterministic integer in [0, max) — 0 when max <= 0
function rt_rng_int(max: int) -> int { function rt_rng_int(rt_rng_st: mut RtRngState, max: int) -> int {
if max <= 0 { return 0 } if max <= 0 { return 0 }
return rt_rng_range(0, max - 1) return rt_rng_range(rt_rng_st, 0, max - 1)
} }
# a deterministic +1 or -1 # a deterministic +1 or -1
function rt_rng_sign() -> int { function rt_rng_sign(rt_rng_st: mut RtRngState) -> int {
if rt_rng_chance(50) { return 1 } if rt_rng_chance(rt_rng_st, 50) { return 1 }
return -1 return -1
} }
# Random.weighted(weights): an index drawn in proportion to its weight (0 = never); # Random.weighted(weights): an index drawn in proportion to its weight (0 = never);
# -1 when every weight is 0. Deterministic, from the seeded RNG. # -1 when every weight is 0. Deterministic, from the seeded RNG.
function rt_rng_weighted(weights: []int) -> int { function rt_rng_weighted(rt_rng_st: mut RtRngState, weights: []int) -> int {
var total = 0 var total = 0
var i = 0 var i = 0
while i < len(weights) { if weights[i] > 0 { total += weights[i] }; i += 1 } while i < len(weights) { if weights[i] > 0 { total += weights[i] }; i += 1 }
if total <= 0 { return -1 } if total <= 0 { return -1 }
var roll = rt_rng_range(0, total - 1) var roll = rt_rng_range(rt_rng_st, 0, total - 1)
i = 0 i = 0
while i < len(weights) { while i < len(weights) {
if weights[i] > 0 { if weights[i] > 0 {

View file

@ -35,47 +35,49 @@ function esys_div(a: int, b: int) -> int {
# same shape the input action map uses; string names compare by byte-pointer # same shape the input action map uses; string names compare by byte-pointer
# identity (a string literal interns to one pointer per program). # identity (a string literal interns to one pointer per program).
const ANIM_MAX_CLIPS: int = 32 const ANIM_MAX_CLIPS: int = 32
var anim_clip_names: pointers = null # clip name per slot export state RtSystemsState {
var anim_clip_fps: words = null anim_clip_names: pointers = null # clip name per slot
var anim_clip_frames: words = null anim_clip_fps: words = null
var anim_clip_mode: words = null anim_clip_frames: words = null
var anim_nclips: int = 0 anim_clip_mode: words = null
anim_nclips: int = 0
}
function anim_clip_init() -> void { function anim_clip_init(rt_systems_st: mut RtSystemsState) -> void {
if anim_clip_names == null { if rt_systems_st.anim_clip_names == null {
anim_clip_names = pointers(ANIM_MAX_CLIPS) # a pointer (8 bytes) per slot rt_systems_st.anim_clip_names = pointers(ANIM_MAX_CLIPS) # a pointer (8 bytes) per slot
anim_clip_fps = words(ANIM_MAX_CLIPS) rt_systems_st.anim_clip_fps = words(ANIM_MAX_CLIPS)
anim_clip_frames = words(ANIM_MAX_CLIPS) rt_systems_st.anim_clip_frames = words(ANIM_MAX_CLIPS)
anim_clip_mode = words(ANIM_MAX_CLIPS) rt_systems_st.anim_clip_mode = words(ANIM_MAX_CLIPS)
} }
} }
# register (or update) a named clip. mode is the SpriteAnim mode: 0 loop, 1 once, # register (or update) a named clip. mode is the SpriteAnim mode: 0 loop, 1 once,
# 2 ping-pong. # 2 ping-pong.
function anim_clip(name: pointer, frames: int, fps: int, mode: int) -> void { function anim_clip(rt_systems_st: mut RtSystemsState, name: pointer, frames: int, fps: int, mode: int) -> void {
anim_clip_init() anim_clip_init(rt_systems_st)
var i = 0 var i = 0
while i < anim_nclips { while i < rt_systems_st.anim_nclips {
if anim_clip_names[i] == name { if rt_systems_st.anim_clip_names[i] == name {
anim_clip_frames[i] = frames; anim_clip_fps[i] = fps; anim_clip_mode[i] = mode rt_systems_st.anim_clip_frames[i] = frames; rt_systems_st.anim_clip_fps[i] = fps; rt_systems_st.anim_clip_mode[i] = mode
return return
} }
i += 1 i += 1
} }
if anim_nclips >= ANIM_MAX_CLIPS { return } # silently ignore past capacity if rt_systems_st.anim_nclips >= ANIM_MAX_CLIPS { return } # silently ignore past capacity
let s = anim_nclips let s = rt_systems_st.anim_nclips
anim_clip_names[s] = name rt_systems_st.anim_clip_names[s] = name
anim_clip_frames[s] = frames rt_systems_st.anim_clip_frames[s] = frames
anim_clip_fps[s] = fps rt_systems_st.anim_clip_fps[s] = fps
anim_clip_mode[s] = mode rt_systems_st.anim_clip_mode[s] = mode
anim_nclips += 1 rt_systems_st.anim_nclips += 1
} }
function anim_clip_find(name: pointer) -> int { function anim_clip_find(rt_systems_st: mut RtSystemsState, name: pointer) -> int {
anim_clip_init() anim_clip_init(rt_systems_st)
var i = 0 var i = 0
while i < anim_nclips { while i < rt_systems_st.anim_nclips {
if anim_clip_names[i] == name { return i } if rt_systems_st.anim_clip_names[i] == name { return i }
i += 1 i += 1
} }
return -1 return -1
@ -105,10 +107,10 @@ function anim_play(e: int, fps: int, frames: int, mode: int) -> void {
} }
# start a registered clip by name (a no-op if the name is unknown). # start a registered clip by name (a no-op if the name is unknown).
function anim_play_named(e: int, name: pointer) -> void { function anim_play_named(rt_systems_st: mut RtSystemsState, e: int, name: pointer) -> void {
let c = anim_clip_find(name) let c = anim_clip_find(rt_systems_st, name)
if c < 0 { return } if c < 0 { return }
anim_play(e, anim_clip_fps[c], anim_clip_frames[c], anim_clip_mode[c]) anim_play(e, rt_systems_st.anim_clip_fps[c], rt_systems_st.anim_clip_frames[c], rt_systems_st.anim_clip_mode[c])
} }
# arm a frame event: the engine flags SpriteAnim.event_fired = 1 on the tick the # arm a frame event: the engine flags SpriteAnim.event_fired = 1 on the tick the

View file

@ -51,7 +51,7 @@ function esys_pos_y(e: int, p: int, fy: int) -> int {
return 0 return 0
} }
function esys_light2d() -> void { function esys_light2d(rt_core_st: mut RtCoreState, rt_light_st: mut RtLightState) -> void {
let pl = World.prop_id("Light2D") let pl = World.prop_id("Light2D")
if pl < 0 { return } if pl < 0 { return }
@ -62,12 +62,12 @@ function esys_light2d() -> void {
let af = World.field_id(pa, "color") let af = World.field_id(pa, "color")
let ae = World.query_next(pa, 0) let ae = World.query_next(pa, 0)
if ae >= 0 { if ae >= 0 {
if af >= 0 { light_ambient(World.get(ae, pa, af)) } if af >= 0 { light_ambient(rt_core_st, World.get(ae, pa, af)) }
} }
} }
# 2. occluders — re-register every `Occluder` as a shadow caster this frame. # 2. occluders — re-register every `Occluder` as a shadow caster this frame.
light_clear_occluders() light_clear_occluders(rt_light_st)
let po = World.prop_id("Occluder") let po = World.prop_id("Occluder")
if po >= 0 { if po >= 0 {
let oxf = World.field_id(po, "x") let oxf = World.field_id(po, "x")
@ -82,7 +82,7 @@ function esys_light2d() -> void {
var oh = 0 var oh = 0
if owf >= 0 { ow = World.get(oe, po, owf) } if owf >= 0 { ow = World.get(oe, po, owf) }
if ohf >= 0 { oh = World.get(oe, po, ohf) } if ohf >= 0 { oh = World.get(oe, po, ohf) }
light_occlude(ox, oy, ow, oh) light_occlude(rt_light_st, ox, oy, ow, oh)
oe = World.query_next(po, oe + 1) oe = World.query_next(po, oe + 1)
} }
} }
@ -120,18 +120,18 @@ function esys_light2d() -> void {
# cone/soft light does not bleed its settings onto the next. # cone/soft light does not bleed its settings onto the next.
var falloff = 1 var falloff = 1
if lff >= 0 { falloff = World.get(e, pl, lff) } if lff >= 0 { falloff = World.get(e, pl, lff) }
light_set_falloff(falloff) light_set_falloff(rt_light_st, falloff)
var soft = 0 var soft = 0
if lkf >= 0 { soft = World.get(e, pl, lkf) } if lkf >= 0 { soft = World.get(e, pl, lkf) }
light_set_soft(soft) light_set_soft(rt_light_st, soft)
if lgf >= 0 { light_set_gel(World.get(e, pl, lgf)) } else { light_clear_gel() } if lgf >= 0 { light_set_gel(rt_light_st, World.get(e, pl, lgf)) } else { light_clear_gel(rt_light_st) }
var spread = -1 var spread = -1
if lsf >= 0 { spread = World.get(e, pl, lsf) } if lsf >= 0 { spread = World.get(e, pl, lsf) }
var direction = 0 var direction = 0
if ldf >= 0 { direction = World.get(e, pl, ldf) } if ldf >= 0 { direction = World.get(e, pl, ldf) }
if spread >= 0 { light_spot(x, y, radius, color, energy, direction, spread) } if spread >= 0 { light_spot(rt_core_st, rt_light_st, x, y, radius, color, energy, direction, spread) }
else { light_point(x, y, radius, color, energy) } else { light_point(rt_core_st, rt_light_st, x, y, radius, color, energy) }
e = World.query_next(pl, e + 1) e = World.query_next(pl, e + 1)
} }

View file

@ -60,35 +60,37 @@
const PHYS_ONE: int = 65536 # Q16.16 one whole pixel const PHYS_ONE: int = 65536 # Q16.16 one whole pixel
# ---- per-frame config + reflected ids (filled at the top of esys_move) ------- # ---- per-frame config + reflected ids (filled at the top of esys_move) -------
var pm_pos: int = -1 # Position prop id export state RtSystemsMoveState {
var pm_pos_x: int = -1 pm_pos: int = -1 # Position prop id
var pm_pos_y: int = -1 pm_pos_x: int = -1
var pm_col: int = -1 # Collider prop id pm_pos_y: int = -1
var pm_c_w: int = -1 pm_col: int = -1 # Collider prop id
var pm_c_h: int = -1 pm_c_w: int = -1
var pm_c_offx: int = -1 pm_c_h: int = -1
var pm_c_offy: int = -1 pm_c_offx: int = -1
var pm_c_trig: int = -1 pm_c_offy: int = -1
var pm_c_oneway: int = -1 pm_c_trig: int = -1
var pm_c_layer: int = -1 pm_c_oneway: int = -1
var pm_c_mask: int = -1 pm_c_layer: int = -1
var phys_ts: int = 0 # tile size px (0 = grid off) pm_c_mask: int = -1
var phys_wall: int = 0 # solid glyph phys_ts: int = 0 # tile size px (0 = grid off)
var phys_wall2: int = 0 # optional second solid glyph (Solids.solid2), e.g. a closed door phys_wall: int = 0 # solid glyph
var phys_oneway: int = 0 # one-way platform glyph (0 = none) phys_wall2: int = 0 # optional second solid glyph (Solids.solid2), e.g. a closed door
phys_oneway: int = 0 # one-way platform glyph (0 = none)
phys_cur_layer: int = 0
phys_cur_mask: int = 0
phys_hit_x: int = 0 # X sweep blocked
phys_hit_up: int = 0 # Y sweep blocked moving up
phys_hit_down: int = 0 # Y sweep blocked moving down
phys_trig_prev: words = null
}
# the moving body's own layer/mask, so the solid scan can filter by them # the moving body's own layer/mask, so the solid scan can filter by them
var phys_cur_layer: int = 0
var phys_cur_mask: int = 0
# sweep outputs (Ludic returns one value, so axis clampers report contact here) # sweep outputs (Ludic returns one value, so axis clampers report contact here)
var phys_hit_x: int = 0 # X sweep blocked
var phys_hit_up: int = 0 # Y sweep blocked moving up
var phys_hit_down: int = 0 # Y sweep blocked moving down
# trigger edge tracking: the body a trigger overlapped last frame, stored as # trigger edge tracking: the body a trigger overlapped last frame, stored as
# (entity id + 1) so a zero-filled slot reads as "none" (entity 0 is valid). # (entity id + 1) so a zero-filled slot reads as "none" (entity 0 is valid).
var phys_trig_prev: words = null
# ---- small integer helpers -------------------------------------------------- # ---- small integer helpers --------------------------------------------------
# floor division toward negative infinity (positions can go slightly negative); # floor division toward negative infinity (positions can go slightly negative);
@ -102,42 +104,42 @@ function phys_floordiv(a: int, b: int) -> int {
# a solid (fully blocking) tile: out of bounds counts as a wall, so the map edge # a solid (fully blocking) tile: out of bounds counts as a wall, so the map edge
# is a free arena boundary; otherwise the glyph equals the configured wall. # is a free arena boundary; otherwise the glyph equals the configured wall.
function phys_tile_solid(c: int, r: int) -> bool { function phys_tile_solid(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, c: int, r: int) -> bool {
if c < 0 { return true } if c < 0 { return true }
if r < 0 { return true } if r < 0 { return true }
if c >= rt_mapw { return true } if c >= rt_core_st.rt_mapw { return true }
if r >= rt_maph { return true } if r >= rt_core_st.rt_maph { return true }
let g = rt_tile(c, r) let g = rt_tile(rt_core_st, c, r)
if g == phys_wall { return true } if g == rt_systems_move_st.phys_wall { return true }
return (phys_wall2 != 0) and (g == phys_wall2) return (rt_systems_move_st.phys_wall2 != 0) and (g == rt_systems_move_st.phys_wall2)
} }
# a one-way platform tile (in bounds only — the arena edge is a full wall above). # a one-way platform tile (in bounds only — the arena edge is a full wall above).
function phys_tile_oneway(c: int, r: int) -> bool { function phys_tile_oneway(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, c: int, r: int) -> bool {
if phys_oneway == 0 { return false } if rt_systems_move_st.phys_oneway == 0 { return false }
if c < 0 { return false } if c < 0 { return false }
if r < 0 { return false } if r < 0 { return false }
if c >= rt_mapw { return false } if c >= rt_core_st.rt_mapw { return false }
if r >= rt_maph { return false } if r >= rt_core_st.rt_maph { return false }
return rt_tile(c, r) == phys_oneway return rt_tile(rt_core_st, c, r) == rt_systems_move_st.phys_oneway
} }
# is any full-solid tile present in column c over the row span [r0, r1]? # is any full-solid tile present in column c over the row span [r0, r1]?
function phys_col_solid(c: int, r0: int, r1: int) -> bool { function phys_col_solid(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, c: int, r0: int, r1: int) -> bool {
var r = r0 var r = r0
while r <= r1 { if phys_tile_solid(c, r) { return true }; r += 1 } while r <= r1 { if phys_tile_solid(rt_core_st, rt_systems_move_st, c, r) { return true }; r += 1 }
return false return false
} }
# is any full-solid tile present in row r over the column span [c0, c1]? # is any full-solid tile present in row r over the column span [c0, c1]?
function phys_row_solid(r: int, c0: int, c1: int) -> bool { function phys_row_solid(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, r: int, c0: int, c1: int) -> bool {
var c = c0 var c = c0
while c <= c1 { if phys_tile_solid(c, r) { return true }; c += 1 } while c <= c1 { if phys_tile_solid(rt_core_st, rt_systems_move_st, c, r) { return true }; c += 1 }
return false return false
} }
# is any full-OR-one-way tile present in row r over [c0, c1]? (downward landings) # is any full-OR-one-way tile present in row r over [c0, c1]? (downward landings)
function phys_row_land(r: int, c0: int, c1: int) -> bool { function phys_row_land(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, r: int, c0: int, c1: int) -> bool {
var c = c0 var c = c0
while c <= c1 { while c <= c1 {
if phys_tile_solid(c, r) { return true } if phys_tile_solid(rt_core_st, rt_systems_move_st, c, r) { return true }
if phys_tile_oneway(c, r) { return true } if phys_tile_oneway(rt_core_st, rt_systems_move_st, c, r) { return true }
c += 1 c += 1
} }
return false return false
@ -157,37 +159,37 @@ function phys_match(la: int, ma: int, lb: int, mb: int) -> bool {
} }
# ---- reflected accessors ---------------------------------------------------- # ---- reflected accessors ----------------------------------------------------
function phys_pos_x(e: int) -> int { return World.get(e, pm_pos, pm_pos_x) } function phys_pos_x(rt_systems_move_st: RtSystemsMoveState, e: int) -> int { return World.get(e, rt_systems_move_st.pm_pos, rt_systems_move_st.pm_pos_x) }
function phys_pos_y(e: int) -> int { return World.get(e, pm_pos, pm_pos_y) } function phys_pos_y(rt_systems_move_st: RtSystemsMoveState, e: int) -> int { return World.get(e, rt_systems_move_st.pm_pos, rt_systems_move_st.pm_pos_y) }
function phys_set_x(e: int, v: int) -> void { World.set(e, pm_pos, pm_pos_x, v) } function phys_set_x(rt_systems_move_st: RtSystemsMoveState, e: int, v: int) -> void { World.set(e, rt_systems_move_st.pm_pos, rt_systems_move_st.pm_pos_x, v) }
function phys_set_y(e: int, v: int) -> void { World.set(e, pm_pos, pm_pos_y, v) } function phys_set_y(rt_systems_move_st: RtSystemsMoveState, e: int, v: int) -> void { World.set(e, rt_systems_move_st.pm_pos, rt_systems_move_st.pm_pos_y, v) }
function phys_c_int(e: int, f: int) -> int { function phys_c_int(rt_systems_move_st: RtSystemsMoveState, e: int, f: int) -> int {
if f < 0 { return 0 } if f < 0 { return 0 }
return World.get(e, pm_col, f) return World.get(e, rt_systems_move_st.pm_col, f)
} }
# a collider's AABB left / top edge (Position anchor + offset). # a collider's AABB left / top edge (Position anchor + offset).
function phys_aabb_x(e: int) -> int { return phys_pos_x(e) + phys_c_int(e, pm_c_offx) } function phys_aabb_x(rt_systems_move_st: RtSystemsMoveState, e: int) -> int { return phys_pos_x(rt_systems_move_st, e) + phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_offx) }
function phys_aabb_y(e: int) -> int { return phys_pos_y(e) + phys_c_int(e, pm_c_offy) } function phys_aabb_y(rt_systems_move_st: RtSystemsMoveState, e: int) -> int { return phys_pos_y(rt_systems_move_st, e) + phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_offy) }
# ---- X sweep ---------------------------------------------------------------- # ---- X sweep ----------------------------------------------------------------
# Clamp a body's tentative dx so its AABB (px,py,w,h) stops at the first solid it # Clamp a body's tentative dx so its AABB (px,py,w,h) stops at the first solid it
# would cross this frame — entity colliders and (optionally) the tile grid — and # would cross this frame — entity colliders and (optionally) the tile grid — and
# flag phys_hit_x on contact. One-way platforms never block horizontal motion. # flag phys_hit_x on contact. One-way platforms never block horizontal motion.
function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) -> int { function phys_clamp_x(rt_core_st: RtCoreState, rt_systems_move_st: mut RtSystemsMoveState, self_e: int, px: int, py: int, w: int, h: int, dx: int) -> int {
if dx == 0 { return 0 } if dx == 0 { return 0 }
var best = dx var best = dx
# --- entity solids: every other non-trigger collider we match against # --- entity solids: every other non-trigger collider we match against
if pm_col >= 0 { if rt_systems_move_st.pm_col >= 0 {
var s = World.query_next(pm_col, 0) var s = World.query_next(rt_systems_move_st.pm_col, 0)
while s >= 0 { while s >= 0 {
if s != self_e { if s != self_e {
if phys_c_int(s, pm_c_trig) == 0 { if phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_trig) == 0 {
if phys_match(phys_cur_layer, phys_cur_mask, phys_c_int(s, pm_c_layer), phys_c_int(s, pm_c_mask)) { if phys_match(rt_systems_move_st.phys_cur_layer, rt_systems_move_st.phys_cur_mask, phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_layer), phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_mask)) {
if phys_c_int(s, pm_c_oneway) == 0 { # one-way: pass horizontally if phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_oneway) == 0 { # one-way: pass horizontally
let sx = phys_aabb_x(s); let sy = phys_aabb_y(s) let sx = phys_aabb_x(rt_systems_move_st, s); let sy = phys_aabb_y(rt_systems_move_st, s)
let sw = phys_c_int(s, pm_c_w); let sh = phys_c_int(s, pm_c_h) let sw = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_w); let sh = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_h)
if (py < sy + sh) and (sy < py + h) { # vertical spans overlap if (py < sy + sh) and (sy < py + h) { # vertical spans overlap
if dx > 0 { if dx > 0 {
if px + w <= sx { # currently left of s if px + w <= sx { # currently left of s
@ -205,13 +207,13 @@ function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) ->
} }
} }
} }
s = World.query_next(pm_col, s + 1) s = World.query_next(rt_systems_move_st.pm_col, s + 1)
} }
} }
# --- tile grid: nearest solid column in the swept path over the row span # --- tile grid: nearest solid column in the swept path over the row span
if phys_ts > 0 { if rt_systems_move_st.phys_ts > 0 {
let ts = phys_ts let ts = rt_systems_move_st.phys_ts
let r0 = phys_floordiv(py, ts) let r0 = phys_floordiv(py, ts)
let r1 = phys_floordiv(py + h - 1, ts) let r1 = phys_floordiv(py + h - 1, ts)
if dx > 0 { if dx > 0 {
@ -219,7 +221,7 @@ function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) ->
let endc = phys_floordiv(px + w - 1 + best, ts) let endc = phys_floordiv(px + w - 1 + best, ts)
var c = startc + 1 var c = startc + 1
while c <= endc { while c <= endc {
if phys_col_solid(c, r0, r1) { if phys_col_solid(rt_core_st, rt_systems_move_st, c, r0, r1) {
let allow = c * ts - (px + w) let allow = c * ts - (px + w)
if allow < best { best = allow } if allow < best { best = allow }
c = endc + 1 # nearest wins; stop c = endc + 1 # nearest wins; stop
@ -230,7 +232,7 @@ function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) ->
let endc = phys_floordiv(px + best, ts) let endc = phys_floordiv(px + best, ts)
var c = startc - 1 var c = startc - 1
while c >= endc { while c >= endc {
if phys_col_solid(c, r0, r1) { if phys_col_solid(rt_core_st, rt_systems_move_st, c, r0, r1) {
let allow = (c + 1) * ts - px let allow = (c + 1) * ts - px
if allow > best { best = allow } if allow > best { best = allow }
c = endc - 1 # nearest wins; stop c = endc - 1 # nearest wins; stop
@ -239,7 +241,7 @@ function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) ->
} }
} }
if best != dx { phys_hit_x = 1 } if best != dx { rt_systems_move_st.phys_hit_x = 1 }
return best return best
} }
@ -247,19 +249,19 @@ function phys_clamp_x(self_e: int, px: int, py: int, w: int, h: int, dx: int) ->
# Clamp a body's tentative dy against solids (and one-way platforms on a downward # Clamp a body's tentative dy against solids (and one-way platforms on a downward
# landing), flagging phys_hit_down / phys_hit_up. Must run after the X sweep so # landing), flagging phys_hit_down / phys_hit_up. Must run after the X sweep so
# px is already resolved for this frame. # px is already resolved for this frame.
function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) -> int { function phys_clamp_y(rt_core_st: RtCoreState, rt_systems_move_st: mut RtSystemsMoveState, self_e: int, px: int, py: int, w: int, h: int, dy: int) -> int {
if dy == 0 { return 0 } if dy == 0 { return 0 }
var best = dy var best = dy
if pm_col >= 0 { if rt_systems_move_st.pm_col >= 0 {
var s = World.query_next(pm_col, 0) var s = World.query_next(rt_systems_move_st.pm_col, 0)
while s >= 0 { while s >= 0 {
if s != self_e { if s != self_e {
if phys_c_int(s, pm_c_trig) == 0 { if phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_trig) == 0 {
if phys_match(phys_cur_layer, phys_cur_mask, phys_c_int(s, pm_c_layer), phys_c_int(s, pm_c_mask)) { if phys_match(rt_systems_move_st.phys_cur_layer, rt_systems_move_st.phys_cur_mask, phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_layer), phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_mask)) {
let sx = phys_aabb_x(s); let sy = phys_aabb_y(s) let sx = phys_aabb_x(rt_systems_move_st, s); let sy = phys_aabb_y(rt_systems_move_st, s)
let sw = phys_c_int(s, pm_c_w); let sh = phys_c_int(s, pm_c_h) let sw = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_w); let sh = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_h)
let oneway = phys_c_int(s, pm_c_oneway) let oneway = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_oneway)
if (px < sx + sw) and (sx < px + w) { # horizontal spans overlap if (px < sx + sw) and (sx < px + w) { # horizontal spans overlap
if dy > 0 { if dy > 0 {
# a one-way platform blocks only when the body starts on/above it # a one-way platform blocks only when the body starts on/above it
@ -283,12 +285,12 @@ function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) ->
} }
} }
} }
s = World.query_next(pm_col, s + 1) s = World.query_next(rt_systems_move_st.pm_col, s + 1)
} }
} }
if phys_ts > 0 { if rt_systems_move_st.phys_ts > 0 {
let ts = phys_ts let ts = rt_systems_move_st.phys_ts
let c0 = phys_floordiv(px, ts) let c0 = phys_floordiv(px, ts)
let c1 = phys_floordiv(px + w - 1, ts) let c1 = phys_floordiv(px + w - 1, ts)
if dy > 0 { if dy > 0 {
@ -296,10 +298,10 @@ function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) ->
let endr = phys_floordiv(py + h - 1 + best, ts) let endr = phys_floordiv(py + h - 1 + best, ts)
var r = startr + 1 var r = startr + 1
while r <= endr { while r <= endr {
var block = phys_row_solid(r, c0, c1) var block = phys_row_solid(rt_core_st, rt_systems_move_st, r, c0, c1)
# one-way tiles only catch a body whose feet start at/above the tile top # one-way tiles only catch a body whose feet start at/above the tile top
if not block { if not block {
if phys_row_land(r, c0, c1) { if py + h <= r * ts { block = true } } if phys_row_land(rt_core_st, rt_systems_move_st, r, c0, c1) { if py + h <= r * ts { block = true } }
} }
if block { if block {
let allow = r * ts - (py + h) let allow = r * ts - (py + h)
@ -312,7 +314,7 @@ function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) ->
let endr = phys_floordiv(py + best, ts) let endr = phys_floordiv(py + best, ts)
var r = startr - 1 var r = startr - 1
while r >= endr { while r >= endr {
if phys_row_solid(r, c0, c1) { # one-way never blocks upward if phys_row_solid(rt_core_st, rt_systems_move_st, r, c0, c1) { # one-way never blocks upward
let allow = (r + 1) * ts - py let allow = (r + 1) * ts - py
if allow > best { best = allow } if allow > best { best = allow }
r = endr - 1 r = endr - 1
@ -322,7 +324,7 @@ function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) ->
} }
if best != dy { if best != dy {
if dy > 0 { phys_hit_down = 1 } else { phys_hit_up = 1 } if dy > 0 { rt_systems_move_st.phys_hit_down = 1 } else { rt_systems_move_st.phys_hit_up = 1 }
} }
return best return best
} }
@ -330,40 +332,40 @@ function phys_clamp_y(self_e: int, px: int, py: int, w: int, h: int, dy: int) ->
# ---- ground probe ----------------------------------------------------------- # ---- ground probe -----------------------------------------------------------
# on_ground is a 1px downward overlap test, independent of this frame's velocity, # on_ground is a 1px downward overlap test, independent of this frame's velocity,
# so a body resting under sub-pixel gravity still reads grounded. # so a body resting under sub-pixel gravity still reads grounded.
function phys_grounded(self_e: int, px: int, py: int, w: int, h: int) -> bool { function phys_grounded(rt_core_st: RtCoreState, rt_systems_move_st: RtSystemsMoveState, self_e: int, px: int, py: int, w: int, h: int) -> bool {
# entity solids one pixel below # entity solids one pixel below
if pm_col >= 0 { if rt_systems_move_st.pm_col >= 0 {
var s = World.query_next(pm_col, 0) var s = World.query_next(rt_systems_move_st.pm_col, 0)
while s >= 0 { while s >= 0 {
if s != self_e { if s != self_e {
if phys_c_int(s, pm_c_trig) == 0 { if phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_trig) == 0 {
if phys_match(phys_cur_layer, phys_cur_mask, phys_c_int(s, pm_c_layer), phys_c_int(s, pm_c_mask)) { if phys_match(rt_systems_move_st.phys_cur_layer, rt_systems_move_st.phys_cur_mask, phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_layer), phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_mask)) {
let sx = phys_aabb_x(s); let sy = phys_aabb_y(s) let sx = phys_aabb_x(rt_systems_move_st, s); let sy = phys_aabb_y(rt_systems_move_st, s)
let sw = phys_c_int(s, pm_c_w); let sh = phys_c_int(s, pm_c_h) let sw = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_w); let sh = phys_c_int(rt_systems_move_st, s, rt_systems_move_st.pm_c_h)
if (px < sx + sw) and (sx < px + w) { if (px < sx + sw) and (sx < px + w) {
if (py + h <= sy) and (py + h + 1 > sy) { return true } if (py + h <= sy) and (py + h + 1 > sy) { return true }
} }
} }
} }
} }
s = World.query_next(pm_col, s + 1) s = World.query_next(rt_systems_move_st.pm_col, s + 1)
} }
} }
if phys_ts > 0 { if rt_systems_move_st.phys_ts > 0 {
let ts = phys_ts let ts = rt_systems_move_st.phys_ts
let c0 = phys_floordiv(px, ts) let c0 = phys_floordiv(px, ts)
let c1 = phys_floordiv(px + w - 1, ts) let c1 = phys_floordiv(px + w - 1, ts)
let footr = phys_floordiv(py + h, ts) # tile row just below the feet let footr = phys_floordiv(py + h, ts) # tile row just below the feet
if (py + h) == footr * ts { # feet flush on a tile boundary if (py + h) == footr * ts { # feet flush on a tile boundary
if phys_row_solid(footr, c0, c1) { return true } if phys_row_solid(rt_core_st, rt_systems_move_st, footr, c0, c1) { return true }
if phys_row_land(footr, c0, c1) { return true } if phys_row_land(rt_core_st, rt_systems_move_st, footr, c0, c1) { return true }
} }
} }
return false return false
} }
# ---- the system ------------------------------------------------------------ # ---- the system ------------------------------------------------------------
function esys_move() -> void { function esys_move(rt_core_st: mut RtCoreState, rt_systems_move_st: mut RtSystemsMoveState) -> void {
let pb = World.prop_id("Body") let pb = World.prop_id("Body")
if pb < 0 { return } if pb < 0 { return }
@ -382,23 +384,23 @@ function esys_move() -> void {
let f_ny = World.field_id(pb, "hit_ny") let f_ny = World.field_id(pb, "hit_ny")
# shared / config props (into module globals the helpers read) # shared / config props (into module globals the helpers read)
pm_pos = World.prop_id("Position") rt_systems_move_st.pm_pos = World.prop_id("Position")
if pm_pos >= 0 { if rt_systems_move_st.pm_pos >= 0 {
pm_pos_x = World.field_id(pm_pos, "x") rt_systems_move_st.pm_pos_x = World.field_id(rt_systems_move_st.pm_pos, "x")
pm_pos_y = World.field_id(pm_pos, "y") rt_systems_move_st.pm_pos_y = World.field_id(rt_systems_move_st.pm_pos, "y")
} }
pm_col = World.prop_id("Collider") rt_systems_move_st.pm_col = World.prop_id("Collider")
if pm_col >= 0 { if rt_systems_move_st.pm_col >= 0 {
pm_c_w = World.field_id(pm_col, "w") rt_systems_move_st.pm_c_w = World.field_id(rt_systems_move_st.pm_col, "w")
pm_c_h = World.field_id(pm_col, "h") rt_systems_move_st.pm_c_h = World.field_id(rt_systems_move_st.pm_col, "h")
pm_c_offx = World.field_id(pm_col, "offx") rt_systems_move_st.pm_c_offx = World.field_id(rt_systems_move_st.pm_col, "offx")
pm_c_offy = World.field_id(pm_col, "offy") rt_systems_move_st.pm_c_offy = World.field_id(rt_systems_move_st.pm_col, "offy")
pm_c_trig = World.field_id(pm_col, "is_trigger") rt_systems_move_st.pm_c_trig = World.field_id(rt_systems_move_st.pm_col, "is_trigger")
pm_c_oneway = World.field_id(pm_col, "one_way") rt_systems_move_st.pm_c_oneway = World.field_id(rt_systems_move_st.pm_col, "one_way")
pm_c_layer = World.field_id(pm_col, "layer") rt_systems_move_st.pm_c_layer = World.field_id(rt_systems_move_st.pm_col, "layer")
pm_c_mask = World.field_id(pm_col, "mask") rt_systems_move_st.pm_c_mask = World.field_id(rt_systems_move_st.pm_col, "mask")
} }
phys_ts = 0; phys_wall = 0; phys_wall2 = 0; phys_oneway = 0 rt_systems_move_st.phys_ts = 0; rt_systems_move_st.phys_wall = 0; rt_systems_move_st.phys_wall2 = 0; rt_systems_move_st.phys_oneway = 0
let ps = World.prop_id("Solids") let ps = World.prop_id("Solids")
if ps >= 0 { if ps >= 0 {
let se = World.query_next(ps, 0) let se = World.query_next(ps, 0)
@ -406,24 +408,24 @@ function esys_move() -> void {
let f_tile = World.field_id(ps, "tile") let f_tile = World.field_id(ps, "tile")
let f_wall = World.field_id(ps, "wall") let f_wall = World.field_id(ps, "wall")
let f_ow = World.field_id(ps, "oneway") let f_ow = World.field_id(ps, "oneway")
if f_tile >= 0 { phys_ts = World.get(se, ps, f_tile) } if f_tile >= 0 { rt_systems_move_st.phys_ts = World.get(se, ps, f_tile) }
if f_wall >= 0 { phys_wall = World.get(se, ps, f_wall) } if f_wall >= 0 { rt_systems_move_st.phys_wall = World.get(se, ps, f_wall) }
if f_ow >= 0 { phys_oneway = World.get(se, ps, f_ow) } if f_ow >= 0 { rt_systems_move_st.phys_oneway = World.get(se, ps, f_ow) }
let f_w2 = World.field_id(ps, "solid2") let f_w2 = World.field_id(ps, "solid2")
if f_w2 >= 0 { phys_wall2 = World.get(se, ps, f_w2) } if f_w2 >= 0 { rt_systems_move_st.phys_wall2 = World.get(se, ps, f_w2) }
rt_map_solid1 = phys_wall; rt_map_solid2 = phys_wall2 # Map.is_solid reads the same config rt_core_st.rt_map_solid1 = rt_systems_move_st.phys_wall; rt_core_st.rt_map_solid2 = rt_systems_move_st.phys_wall2 # Map.is_solid reads the same config
if phys_ts > 0 { rt_map_tile_px = phys_ts } if rt_systems_move_st.phys_ts > 0 { rt_core_st.rt_map_tile_px = rt_systems_move_st.phys_ts }
} }
} }
# a Position is required to move a body; a Body with none is inert. # a Position is required to move a body; a Body with none is inert.
if pm_pos < 0 { return } if rt_systems_move_st.pm_pos < 0 { return }
if pm_pos_x < 0 { return } if rt_systems_move_st.pm_pos_x < 0 { return }
if pm_pos_y < 0 { return } if rt_systems_move_st.pm_pos_y < 0 { return }
var e = World.query_next(pb, 0) var e = World.query_next(pb, 0)
while e >= 0 { while e >= 0 {
if World.has(e, pm_pos) != 0 { if World.has(e, rt_systems_move_st.pm_pos) != 0 {
# 1. integrate velocity (+ gravity for a platformer policy) # 1. integrate velocity (+ gravity for a platformer policy)
var vx = 0; if f_vx >= 0 { vx = World.get(e, pb, f_vx) } var vx = 0; if f_vx >= 0 { vx = World.get(e, pb, f_vx) }
var vy = 0; if f_vy >= 0 { vy = World.get(e, pb, f_vy) } var vy = 0; if f_vy >= 0 { vy = World.get(e, pb, f_vy) }
@ -452,42 +454,42 @@ function esys_move() -> void {
# 3. does this body have a solid shape? (a trigger-only body moves freely) # 3. does this body have a solid shape? (a trigger-only body moves freely)
var has_shape = false var has_shape = false
if pm_col >= 0 { if rt_systems_move_st.pm_col >= 0 {
if World.has(e, pm_col) != 0 { if phys_c_int(e, pm_c_trig) == 0 { has_shape = true } } if World.has(e, rt_systems_move_st.pm_col) != 0 { if phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_trig) == 0 { has_shape = true } }
} }
if has_shape { if has_shape {
phys_cur_layer = phys_c_int(e, pm_c_layer) rt_systems_move_st.phys_cur_layer = phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_layer)
phys_cur_mask = phys_c_int(e, pm_c_mask) rt_systems_move_st.phys_cur_mask = phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_mask)
let w = phys_c_int(e, pm_c_w) let w = phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_w)
let h = phys_c_int(e, pm_c_h) let h = phys_c_int(rt_systems_move_st, e, rt_systems_move_st.pm_c_h)
var px = phys_aabb_x(e) var px = phys_aabb_x(rt_systems_move_st, e)
let py0 = phys_aabb_y(e) let py0 = phys_aabb_y(rt_systems_move_st, e)
# X sweep, commit, then Y sweep from the resolved x # X sweep, commit, then Y sweep from the resolved x
phys_hit_x = 0 rt_systems_move_st.phys_hit_x = 0
let cdx = phys_clamp_x(e, px, py0, w, h, dx) let cdx = phys_clamp_x(rt_core_st, rt_systems_move_st, e, px, py0, w, h, dx)
phys_set_x(e, phys_pos_x(e) + cdx) phys_set_x(rt_systems_move_st, e, phys_pos_x(rt_systems_move_st, e) + cdx)
px = phys_aabb_x(e) px = phys_aabb_x(rt_systems_move_st, e)
phys_hit_up = 0; phys_hit_down = 0 rt_systems_move_st.phys_hit_up = 0; rt_systems_move_st.phys_hit_down = 0
let py = phys_aabb_y(e) let py = phys_aabb_y(rt_systems_move_st, e)
let cdy = phys_clamp_y(e, px, py, w, h, dy) let cdy = phys_clamp_y(rt_core_st, rt_systems_move_st, e, px, py, w, h, dy)
phys_set_y(e, phys_pos_y(e) + cdy) phys_set_y(rt_systems_move_st, e, phys_pos_y(rt_systems_move_st, e) + cdy)
# derived flags + contact normal # derived flags + contact normal
if phys_hit_x == 1 { hit_wall = 1; vx = 0; if dx > 0 { nx = -1 } else { nx = 1 } } if rt_systems_move_st.phys_hit_x == 1 { hit_wall = 1; vx = 0; if dx > 0 { nx = -1 } else { nx = 1 } }
if phys_hit_up == 1 { hit_ceiling = 1; vy = 0; ny = 1 } if rt_systems_move_st.phys_hit_up == 1 { hit_ceiling = 1; vy = 0; ny = 1 }
if phys_hit_down == 1 { vy = 0; ny = -1 } if rt_systems_move_st.phys_hit_down == 1 { vy = 0; ny = -1 }
let gy = phys_aabb_y(e) let gy = phys_aabb_y(rt_systems_move_st, e)
if phys_grounded(e, px, gy, w, h) { on_ground = 1 } if phys_grounded(rt_core_st, rt_systems_move_st, e, px, gy, w, h) { on_ground = 1 }
# a body pressed onto the ground carries no downward velocity, whatever # a body pressed onto the ground carries no downward velocity, whatever
# the gravity step was this frame (so on_ground implies a settled vy). # the gravity step was this frame (so on_ground implies a settled vy).
if (on_ground == 1) and (vy > 0) { vy = 0 } if (on_ground == 1) and (vy > 0) { vy = 0 }
} else { } else {
# free integration: apply the whole-pixel step straight to Position # free integration: apply the whole-pixel step straight to Position
phys_set_x(e, phys_pos_x(e) + dx) phys_set_x(rt_systems_move_st, e, phys_pos_x(rt_systems_move_st, e) + dx)
phys_set_y(e, phys_pos_y(e) + dy) phys_set_y(rt_systems_move_st, e, phys_pos_y(rt_systems_move_st, e) + dy)
} }
# 4. write velocity + subpixel remainder + derived outputs back # 4. write velocity + subpixel remainder + derived outputs back
@ -511,35 +513,35 @@ function esys_move() -> void {
# 5. triggers/sensors — overlap that reports but never resolves. For each # 5. triggers/sensors — overlap that reports but never resolves. For each
# trigger collider, find the lowest-id body-shaped collider it overlaps and # trigger collider, find the lowest-id body-shaped collider it overlaps and
# edge-detect enter/exit against last frame (SpriteAnim.event_fired shape). # edge-detect enter/exit against last frame (SpriteAnim.event_fired shape).
esys_triggers() esys_triggers(rt_systems_move_st)
} }
function esys_triggers() -> void { function esys_triggers(rt_systems_move_st: mut RtSystemsMoveState) -> void {
if pm_col < 0 { return } if rt_systems_move_st.pm_col < 0 { return }
if pm_c_trig < 0 { return } if rt_systems_move_st.pm_c_trig < 0 { return }
let f_hit = World.field_id(pm_col, "hit") let f_hit = World.field_id(rt_systems_move_st.pm_col, "hit")
let f_ent = World.field_id(pm_col, "entered") let f_ent = World.field_id(rt_systems_move_st.pm_col, "entered")
let f_ext = World.field_id(pm_col, "exited") let f_ext = World.field_id(rt_systems_move_st.pm_col, "exited")
if (f_hit < 0) and (f_ent < 0) and (f_ext < 0) { return } # no trigger outputs declared if (f_hit < 0) and (f_ent < 0) and (f_ext < 0) { return } # no trigger outputs declared
if phys_trig_prev == null { phys_trig_prev = words(1024) } if rt_systems_move_st.phys_trig_prev == null { rt_systems_move_st.phys_trig_prev = words(1024) }
var t = World.query_next(pm_col, 0) var t = World.query_next(rt_systems_move_st.pm_col, 0)
while t >= 0 { while t >= 0 {
if phys_c_int(t, pm_c_trig) != 0 { if phys_c_int(rt_systems_move_st, t, rt_systems_move_st.pm_c_trig) != 0 {
if World.has(t, pm_pos) != 0 { if World.has(t, rt_systems_move_st.pm_pos) != 0 {
let tx = phys_aabb_x(t); let ty = phys_aabb_y(t) let tx = phys_aabb_x(rt_systems_move_st, t); let ty = phys_aabb_y(rt_systems_move_st, t)
let tw = phys_c_int(t, pm_c_w); let th = phys_c_int(t, pm_c_h) let tw = phys_c_int(rt_systems_move_st, t, rt_systems_move_st.pm_c_w); let th = phys_c_int(rt_systems_move_st, t, rt_systems_move_st.pm_c_h)
let tl = phys_c_int(t, pm_c_layer); let tm = phys_c_int(t, pm_c_mask) let tl = phys_c_int(rt_systems_move_st, t, rt_systems_move_st.pm_c_layer); let tm = phys_c_int(rt_systems_move_st, t, rt_systems_move_st.pm_c_mask)
var cur = -1 var cur = -1
var b = World.query_next(pm_col, 0) var b = World.query_next(rt_systems_move_st.pm_col, 0)
while b >= 0 { while b >= 0 {
if b != t { if b != t {
if phys_c_int(b, pm_c_trig) == 0 { if phys_c_int(rt_systems_move_st, b, rt_systems_move_st.pm_c_trig) == 0 {
if World.has(b, pm_pos) != 0 { if World.has(b, rt_systems_move_st.pm_pos) != 0 {
if phys_match(tl, tm, phys_c_int(b, pm_c_layer), phys_c_int(b, pm_c_mask)) { if phys_match(tl, tm, phys_c_int(rt_systems_move_st, b, rt_systems_move_st.pm_c_layer), phys_c_int(rt_systems_move_st, b, rt_systems_move_st.pm_c_mask)) {
let bx = phys_aabb_x(b); let by = phys_aabb_y(b) let bx = phys_aabb_x(rt_systems_move_st, b); let by = phys_aabb_y(rt_systems_move_st, b)
let bw = phys_c_int(b, pm_c_w); let bh = phys_c_int(b, pm_c_h) let bw = phys_c_int(rt_systems_move_st, b, rt_systems_move_st.pm_c_w); let bh = phys_c_int(rt_systems_move_st, b, rt_systems_move_st.pm_c_h)
if (tx < bx + bw) and (bx < tx + tw) and (ty < by + bh) and (by < ty + th) { if (tx < bx + bw) and (bx < tx + tw) and (ty < by + bh) and (by < ty + th) {
if cur < 0 { cur = b } # lowest id wins (deterministic) if cur < 0 { cur = b } # lowest id wins (deterministic)
} }
@ -547,19 +549,19 @@ function esys_triggers() -> void {
} }
} }
} }
b = World.query_next(pm_col, b + 1) b = World.query_next(rt_systems_move_st.pm_col, b + 1)
} }
let prev = phys_trig_prev[t] - 1 # stored as id+1, 0 = none let prev = rt_systems_move_st.phys_trig_prev[t] - 1 # stored as id+1, 0 = none
var entered = 0 var entered = 0
var exited = 0 var exited = 0
if (prev < 0) and (cur >= 0) { entered = 1 } if (prev < 0) and (cur >= 0) { entered = 1 }
if (prev >= 0) and (cur < 0) { exited = 1 } if (prev >= 0) and (cur < 0) { exited = 1 }
phys_trig_prev[t] = cur + 1 rt_systems_move_st.phys_trig_prev[t] = cur + 1
if f_hit >= 0 { World.set(t, pm_col, f_hit, cur) } if f_hit >= 0 { World.set(t, rt_systems_move_st.pm_col, f_hit, cur) }
if f_ent >= 0 { World.set(t, pm_col, f_ent, entered) } if f_ent >= 0 { World.set(t, rt_systems_move_st.pm_col, f_ent, entered) }
if f_ext >= 0 { World.set(t, pm_col, f_ext, exited) } if f_ext >= 0 { World.set(t, rt_systems_move_st.pm_col, f_ext, exited) }
} }
} }
t = World.query_next(pm_col, t + 1) t = World.query_next(rt_systems_move_st.pm_col, t + 1)
} }
} }

View file

@ -43,7 +43,7 @@ function spr_pos(e: int, axis: int) -> int {
return World.get(e, pp, f) return World.get(e, pp, f)
} }
function esys_sprite() -> void { function esys_sprite(rt_atlas_st: mut RtAtlasState, rt_core_st: mut RtCoreState, rt_image_st: RtImageState) -> void {
let P = World.prop_id("Sprite") let P = World.prop_id("Sprite")
if P < 0 { return } if P < 0 { return }
let f_id = World.field_id(P, "id") let f_id = World.field_id(P, "id")
@ -109,8 +109,8 @@ function esys_sprite() -> void {
if (f_flash >= 0) and (World.get(e, P, f_flash) > 0) { tint = 0xffffff } if (f_flash >= 0) and (World.get(e, P, f_flash) > 0) { tint = 0xffffff }
var is_atlas = 0 var is_atlas = 0
if f_atlas >= 0 { is_atlas = World.get(e, P, f_atlas) } if f_atlas >= 0 { is_atlas = World.get(e, P, f_atlas) }
if is_atlas != 0 { atlas_draw_ex(id, x, y, sc, flip, tint) } # #90 atlas cells / spans if is_atlas != 0 { atlas_draw_ex(rt_atlas_st, rt_core_st, rt_image_st, id, x, y, sc, flip, tint) } # #90 atlas cells / spans
else { rt_draw_sprite_ex(id, x, y, sc, flip, tint) } else { rt_draw_sprite_ex(rt_core_st, rt_image_st, id, x, y, sc, flip, tint) }
} }
e = World.query_next(P, e + 1) e = World.query_next(P, e + 1)
} }

View file

@ -16,7 +16,7 @@
# ============================================================================ # ============================================================================
import "atlas.ludic" import "atlas.ludic"
function esys_tileskin() -> void { function esys_tileskin(rt_atlas_st: mut RtAtlasState, rt_core_st: mut RtCoreState, rt_image_st: RtImageState) -> void {
let P = World.prop_id("TileSkin") let P = World.prop_id("TileSkin")
if P < 0 { return } if P < 0 { return }
let f_glyph = World.field_id(P, "glyph") let f_glyph = World.field_id(P, "glyph")
@ -36,12 +36,12 @@ function esys_tileskin() -> void {
var sc = 1 var sc = 1
if f_scale >= 0 { let cv = World.get(e, P, f_scale); if cv > 1 { sc = cv } } if f_scale >= 0 { let cv = World.get(e, P, f_scale); if cv > 1 { sc = cv } }
var ty = 0 var ty = 0
while ty < rt_maph { while ty < rt_core_st.rt_maph {
var tx = 0 var tx = 0
while tx < rt_mapw { while tx < rt_core_st.rt_mapw {
if rt_tile(tx, ty) == glyph { if rt_tile(rt_core_st, tx, ty) == glyph {
if is_atlas != 0 { atlas_draw_ex(sprite, tx * size, ty * size, sc, 0, 0) } if is_atlas != 0 { atlas_draw_ex(rt_atlas_st, rt_core_st, rt_image_st, sprite, tx * size, ty * size, sc, 0, 0) }
else { rt_draw_sprite_ex(sprite, tx * size, ty * size, sc, 0, 0) } else { rt_draw_sprite_ex(rt_core_st, rt_image_st, sprite, tx * size, ty * size, sc, 0, 0) }
} }
tx += 1 tx += 1
} }

View file

@ -41,18 +41,18 @@ function tiled_csv_list(text: pointer) -> Val {
# a base64 (optionally zlib/gzip-compressed) blob of `count` little-endian u32 # a base64 (optionally zlib/gzip-compressed) blob of `count` little-endian u32
# GIDs -> a Value list of int nodes. # GIDs -> a Value list of int nodes.
function tiled_b64_list(text: pointer, compression: pointer, count: int) -> Val { function tiled_b64_list(rt_inflate_st: mut RtInflateState, rt_zstd_st: mut RtZstdState, text: pointer, compression: pointer, count: int) -> Val {
let comp = bytes(len(text) + 4) let comp = bytes(len(text) + 4)
let clen = b64_decode(text, comp) let clen = b64_decode(text, comp)
let outcap = count * 4 + 16 let outcap = count * 4 + 16
var raw = comp var raw = comp
var rawlen = clen var rawlen = clen
if compression == "zlib" { if compression == "zlib" {
let d = bytes(outcap); let dn = z_uncompress(comp, clen, d, outcap); raw = d; rawlen = dn let d = bytes(outcap); let dn = z_uncompress(rt_inflate_st, comp, clen, d, outcap); raw = d; rawlen = dn
} else { if compression == "gzip" { } else { if compression == "gzip" {
let d = bytes(outcap); let dn = z_gunzip(comp, clen, d, outcap); raw = d; rawlen = dn let d = bytes(outcap); let dn = z_gunzip(rt_inflate_st, comp, clen, d, outcap); raw = d; rawlen = dn
} else { if compression == "zstd" { } else { if compression == "zstd" {
let d = bytes(outcap); let dn = z_zstd(comp, clen, d, outcap); raw = d; rawlen = dn let d = bytes(outcap); let dn = z_zstd(rt_zstd_st, comp, clen, d, outcap); raw = d; rawlen = dn
} } } } } }
let out = value_list() let out = value_list()
var i = 0 var i = 0
@ -65,19 +65,19 @@ function tiled_b64_list(text: pointer, compression: pointer, count: int) -> Val
} }
# decode the text of `node` under encoding `enc` / compression `comp` -> GID list. # decode the text of `node` under encoding `enc` / compression `comp` -> GID list.
function tiled_decode_enc(node: Xml, enc: pointer, comp: pointer, count: int) -> Val { function tiled_decode_enc(rt_inflate_st: mut RtInflateState, rt_zstd_st: mut RtZstdState, node: Xml, enc: pointer, comp: pointer, count: int) -> Val {
if enc == "base64" { return tiled_b64_list(xml_text(node), comp, count) } if enc == "base64" { return tiled_b64_list(rt_inflate_st, rt_zstd_st, xml_text(node), comp, count) }
return tiled_csv_list(xml_text(node)) # csv (or the tag-per-tile form) return tiled_csv_list(xml_text(node)) # csv (or the tag-per-tile form)
} }
# decode a `<data>` element (child of a `<layer>`) into a Value list of GIDs. # decode a `<data>` element (child of a `<layer>`) into a Value list of GIDs.
function tiled_data_list(data: Xml, count: int) -> Val { function tiled_data_list(rt_inflate_st: mut RtInflateState, rt_zstd_st: mut RtZstdState, data: Xml, count: int) -> Val {
return tiled_decode_enc(data, xml_attr(data, "encoding"), xml_attr(data, "compression"), count) return tiled_decode_enc(rt_inflate_st, rt_zstd_st, data, xml_attr(data, "encoding"), xml_attr(data, "compression"), count)
} }
# flatten a chunked (infinite-map) `<data>` — its `<chunk x y width height>` # flatten a chunked (infinite-map) `<data>` — its `<chunk x y width height>`
# children — into a dense GID list; sets `o`'s width/height/data (#74). # children — into a dense GID list; sets `o`'s width/height/data (#74).
function tiled_chunked_layer(data: Xml, o: Val) -> void { function tiled_chunked_layer(rt_inflate_st: mut RtInflateState, rt_zstd_st: mut RtZstdState, data: Xml, o: Val) -> void {
let enc = xml_attr(data, "encoding") let enc = xml_attr(data, "encoding")
let comp = xml_attr(data, "compression") let comp = xml_attr(data, "compression")
# pass 1: bounds over every chunk (tile coordinates) # pass 1: bounds over every chunk (tile coordinates)
@ -112,7 +112,7 @@ function tiled_chunked_layer(data: Xml, o: Val) -> void {
let cy = xml_attr_int(ch, "y", 0) - miny let cy = xml_attr_int(ch, "y", 0) - miny
let cw = xml_attr_int(ch, "width", 0) let cw = xml_attr_int(ch, "width", 0)
let cht = xml_attr_int(ch, "height", 0) let cht = xml_attr_int(ch, "height", 0)
let cdata = tiled_decode_enc(ch, enc, comp, cw * cht) let cdata = tiled_decode_enc(rt_inflate_st, rt_zstd_st, ch, enc, comp, cw * cht)
var yy = 0 var yy = 0
while yy < cht { while yy < cht {
var xx = 0 var xx = 0
@ -340,7 +340,7 @@ function tmx_layer_common(o: Val, el: Xml) -> void {
if len(props.kids) > 0 { value_put(o, "properties", props) } if len(props.kids) > 0 { value_put(o, "properties", props) }
} }
function tmx_tilelayer_to_value(el: Xml, mapw: int, maph: int) -> Val { function tmx_tilelayer_to_value(rt_inflate_st: mut RtInflateState, rt_zstd_st: mut RtZstdState, el: Xml, mapw: int, maph: int) -> Val {
let o = value_object() let o = value_object()
value_put(o, "type", value_str("tilelayer")) value_put(o, "type", value_str("tilelayer"))
tmx_layer_common(o, el) tmx_layer_common(o, el)
@ -348,11 +348,11 @@ function tmx_tilelayer_to_value(el: Xml, mapw: int, maph: int) -> Val {
let h = xml_attr_int(el, "height", maph) let h = xml_attr_int(el, "height", maph)
let data = xml_find(el, "data") let data = xml_find(el, "data")
if xml_count(data, "chunk") > 0 { # infinite map: flatten the chunks if xml_count(data, "chunk") > 0 { # infinite map: flatten the chunks
tiled_chunked_layer(data, o) tiled_chunked_layer(rt_inflate_st, rt_zstd_st, data, o)
} else { } else {
value_put(o, "width", value_int(w)) value_put(o, "width", value_int(w))
value_put(o, "height", value_int(h)) value_put(o, "height", value_int(h))
value_put(o, "data", tiled_data_list(data, w * h)) value_put(o, "data", tiled_data_list(rt_inflate_st, rt_zstd_st, data, w * h))
} }
return o return o
} }
@ -374,7 +374,7 @@ function tmx_objectlayer_to_value(el: Xml) -> Val {
# ---- map ------------------------------------------------------------------- # ---- map -------------------------------------------------------------------
# a `<map>` root -> the intermediate map Value tree (Tiled JSON schema). # a `<map>` root -> the intermediate map Value tree (Tiled JSON schema).
function tmx_to_value(root: Xml) -> Val { function tmx_to_value(rt_inflate_st: mut RtInflateState, rt_zstd_st: mut RtZstdState, root: Xml) -> Val {
let m = value_object() let m = value_object()
value_put(m, "type", value_str("map")) value_put(m, "type", value_str("map"))
value_put(m, "version", value_str(xml_attr(root, "version"))) value_put(m, "version", value_str(xml_attr(root, "version")))
@ -398,10 +398,10 @@ function tmx_to_value(root: Xml) -> Val {
let ch = xml_child(root, i) let ch = xml_child(root, i)
let tag = xml_tag(ch) let tag = xml_tag(ch)
if tag == "tileset" { push(tilesets.kids, tmx_tileset_to_value(ch)) } if tag == "tileset" { push(tilesets.kids, tmx_tileset_to_value(ch)) }
else { if tag == "layer" { push(layers.kids, tmx_tilelayer_to_value(ch, mapw, maph)) } else { if tag == "layer" { push(layers.kids, tmx_tilelayer_to_value(rt_inflate_st, rt_zstd_st, ch, mapw, maph)) }
else { if tag == "objectgroup" { push(layers.kids, tmx_objectlayer_to_value(ch)) } else { if tag == "objectgroup" { push(layers.kids, tmx_objectlayer_to_value(ch)) }
else { if tag == "imagelayer" { push(layers.kids, tmx_imagelayer_to_value(ch)) } else { if tag == "imagelayer" { push(layers.kids, tmx_imagelayer_to_value(ch)) }
else { if tag == "group" { push(layers.kids, tmx_group_to_value(ch, mapw, maph)) } } } } } else { if tag == "group" { push(layers.kids, tmx_group_to_value(rt_inflate_st, rt_zstd_st, ch, mapw, maph)) } } } } }
i += 1 i += 1
} }
let props = tmx_props_list(root) let props = tmx_props_list(root)
@ -424,7 +424,7 @@ function tmx_imagelayer_to_value(el: Xml) -> Val {
} }
# `<group>` -> a Value object carrying its nested layers (P5 renders recursively). # `<group>` -> a Value object carrying its nested layers (P5 renders recursively).
function tmx_group_to_value(el: Xml, mapw: int, maph: int) -> Val { function tmx_group_to_value(rt_inflate_st: mut RtInflateState, rt_zstd_st: mut RtZstdState, el: Xml, mapw: int, maph: int) -> Val {
let o = value_object() let o = value_object()
value_put(o, "type", value_str("group")) value_put(o, "type", value_str("group"))
tmx_layer_common(o, el) tmx_layer_common(o, el)
@ -433,10 +433,10 @@ function tmx_group_to_value(el: Xml, mapw: int, maph: int) -> Val {
while i < xml_child_count(el) { while i < xml_child_count(el) {
let ch = xml_child(el, i) let ch = xml_child(el, i)
let tag = xml_tag(ch) let tag = xml_tag(ch)
if tag == "layer" { push(layers.kids, tmx_tilelayer_to_value(ch, mapw, maph)) } if tag == "layer" { push(layers.kids, tmx_tilelayer_to_value(rt_inflate_st, rt_zstd_st, ch, mapw, maph)) }
else { if tag == "objectgroup" { push(layers.kids, tmx_objectlayer_to_value(ch)) } else { if tag == "objectgroup" { push(layers.kids, tmx_objectlayer_to_value(ch)) }
else { if tag == "imagelayer" { push(layers.kids, tmx_imagelayer_to_value(ch)) } else { if tag == "imagelayer" { push(layers.kids, tmx_imagelayer_to_value(ch)) }
else { if tag == "group" { push(layers.kids, tmx_group_to_value(ch, mapw, maph)) } } } } else { if tag == "group" { push(layers.kids, tmx_group_to_value(rt_inflate_st, rt_zstd_st, ch, mapw, maph)) } } } }
i += 1 i += 1
} }
value_put(o, "layers", layers) value_put(o, "layers", layers)
@ -447,13 +447,13 @@ function tmx_group_to_value(el: Xml, mapw: int, maph: int) -> Val {
# The JSON reader already yields a Value tree; normalise it so it matches the XML # The JSON reader already yields a Value tree; normalise it so it matches the XML
# path: decode any base64 `data` string into a dense GID int list, in place, for # path: decode any base64 `data` string into a dense GID int list, in place, for
# every tile layer (recursing into groups). # every tile layer (recursing into groups).
function tmj_normalize_layer(layer: Val) -> void { function tmj_normalize_layer(rt_inflate_st: mut RtInflateState, rt_zstd_st: mut RtZstdState, layer: Val) -> void {
if value_kind(layer) != 6 { return } if value_kind(layer) != 6 { return }
let ty = value_as_str(value_get(layer, "type")) let ty = value_as_str(value_get(layer, "type"))
if ty == "group" { if ty == "group" {
let ls = value_get(layer, "layers") let ls = value_get(layer, "layers")
var i = 0 var i = 0
while i < value_count(ls) { tmj_normalize_layer(value_at(ls, i)); i += 1 } while i < value_count(ls) { tmj_normalize_layer(rt_inflate_st, rt_zstd_st, value_at(ls, i)); i += 1 }
return return
} }
if ty != "tilelayer" { return } if ty != "tilelayer" { return }
@ -462,26 +462,26 @@ function tmj_normalize_layer(layer: Val) -> void {
# infinite map: flatten the JSON `chunks` array into a dense data list (#74) # infinite map: flatten the JSON `chunks` array into a dense data list (#74)
let chunks = value_get(layer, "chunks") let chunks = value_get(layer, "chunks")
if value_kind(chunks) == 5 and value_count(chunks) > 0 { if value_kind(chunks) == 5 and value_count(chunks) > 0 {
tmj_flatten_chunks(layer, chunks, enc, comp) tmj_flatten_chunks(rt_inflate_st, rt_zstd_st, layer, chunks, enc, comp)
return return
} }
let data = value_get(layer, "data") let data = value_get(layer, "data")
if value_kind(data) == 4 { # a base64 string if value_kind(data) == 4 { # a base64 string
let w = value_as_int(value_get(layer, "width")) let w = value_as_int(value_get(layer, "width"))
let h = value_as_int(value_get(layer, "height")) let h = value_as_int(value_get(layer, "height"))
if enc == "base64" { value_put(layer, "data", tiled_b64_list(value_as_str(data), comp, w * h)) } if enc == "base64" { value_put(layer, "data", tiled_b64_list(rt_inflate_st, rt_zstd_st, value_as_str(data), comp, w * h)) }
} }
} }
# a JSON chunk's `data` (int array, or a base64 string) -> a GID Value list. # a JSON chunk's `data` (int array, or a base64 string) -> a GID Value list.
function tmj_chunk_gids(chunk: Val, enc: pointer, comp: pointer, count: int) -> Val { function tmj_chunk_gids(rt_inflate_st: mut RtInflateState, rt_zstd_st: mut RtZstdState, chunk: Val, enc: pointer, comp: pointer, count: int) -> Val {
let d = value_get(chunk, "data") let d = value_get(chunk, "data")
if value_kind(d) == 4 { return tiled_b64_list(value_as_str(d), comp, count) } # base64 string if value_kind(d) == 4 { return tiled_b64_list(rt_inflate_st, rt_zstd_st, value_as_str(d), comp, count) } # base64 string
return d # already an int array return d # already an int array
} }
# flatten JSON `chunks[]` into a dense data list on `layer`, sizing to the union. # flatten JSON `chunks[]` into a dense data list on `layer`, sizing to the union.
function tmj_flatten_chunks(layer: Val, chunks: Val, enc: pointer, comp: pointer) -> void { function tmj_flatten_chunks(rt_inflate_st: mut RtInflateState, rt_zstd_st: mut RtZstdState, layer: Val, chunks: Val, enc: pointer, comp: pointer) -> void {
var minx = 1000000000 var minx = 1000000000
var miny = 1000000000 var miny = 1000000000
var maxx = -1000000000 var maxx = -1000000000
@ -509,7 +509,7 @@ function tmj_flatten_chunks(layer: Val, chunks: Val, enc: pointer, comp: pointer
let cy = value_as_int(value_get(c, "y")) - miny let cy = value_as_int(value_get(c, "y")) - miny
let cw = value_as_int(value_get(c, "width")) let cw = value_as_int(value_get(c, "width"))
let cht = value_as_int(value_get(c, "height")) let cht = value_as_int(value_get(c, "height"))
let cdata = tmj_chunk_gids(c, enc, comp, cw * cht) let cdata = tmj_chunk_gids(rt_inflate_st, rt_zstd_st, c, enc, comp, cw * cht)
var yy = 0 var yy = 0
while yy < cht { while yy < cht {
var xx = 0 var xx = 0
@ -526,10 +526,10 @@ function tmj_flatten_chunks(layer: Val, chunks: Val, enc: pointer, comp: pointer
value_put(layer, "data", gids) value_put(layer, "data", gids)
} }
function tmj_normalize(m: Val) -> Val { function tmj_normalize(rt_inflate_st: mut RtInflateState, rt_zstd_st: mut RtZstdState, m: Val) -> Val {
let ls = value_get(m, "layers") let ls = value_get(m, "layers")
var i = 0 var i = 0
while i < value_count(ls) { tmj_normalize_layer(value_at(ls, i)); i += 1 } while i < value_count(ls) { tmj_normalize_layer(rt_inflate_st, rt_zstd_st, value_at(ls, i)); i += 1 }
return m return m
} }
@ -547,22 +547,22 @@ function tiled_first_byte(s: pointer) -> int {
# read a map file (TMX or TMJ, auto-detected by first byte) -> the normalised # read a map file (TMX or TMJ, auto-detected by first byte) -> the normalised
# intermediate map Value tree. # intermediate map Value tree.
function tiled_read(path: pointer) -> Val { function tiled_read(rt_inflate_st: mut RtInflateState, rt_xml_st: mut RtXmlState, rt_zstd_st: mut RtZstdState, path: pointer) -> Val {
let text = Fs.read_text(path) let text = Fs.read_text(path)
if text == null { return value_null() } if text == null { return value_null() }
if text == "" { return value_null() } if text == "" { return value_null() }
if tiled_first_byte(text) == 60 { # '<' -> XML if tiled_first_byte(text) == 60 { # '<' -> XML
return tmx_to_value(xml_parse(text)) return tmx_to_value(rt_inflate_st, rt_zstd_st, xml_parse(rt_xml_st, text))
} }
return tmj_normalize(json_parse(text)) # '{' -> JSON return tmj_normalize(rt_inflate_st, rt_zstd_st, json_parse(text)) # '{' -> JSON
} }
# read a tileset file (TSX or TSJ) -> a tileset Value object. # read a tileset file (TSX or TSJ) -> a tileset Value object.
function tiled_read_tsx(path: pointer) -> Val { function tiled_read_tsx(rt_xml_st: mut RtXmlState, path: pointer) -> Val {
let text = Fs.read_text(path) let text = Fs.read_text(path)
if text == null { return value_null() } if text == null { return value_null() }
if text == "" { return value_null() } if text == "" { return value_null() }
if tiled_first_byte(text) == 60 { return tsx_to_value(xml_parse(text)) } if tiled_first_byte(text) == 60 { return tsx_to_value(xml_parse(rt_xml_st, text)) }
return json_parse(text) return json_parse(text)
} }
@ -734,9 +734,9 @@ function tmap_collision_kind(m: Tmap, gid: int) -> int {
# as solid — the fallback source); false to drive collision from per-tile # as solid — the fallback source); false to drive collision from per-tile
# metadata alone (objectgroup shapes / property convention on a visual layer). # metadata alone (objectgroup shapes / property convention on a visual layer).
# solid -> '#' (35), one-way -> '=' (61), trigger/empty -> ' ' (32, passable). # solid -> '#' (35), one-way -> '=' (61), trigger/empty -> ' ' (32, passable).
function tmap_project_layer(m: Tmap, layer: int, whole_layer_solid: int) -> void { function tmap_project_layer(rt_core_st: mut RtCoreState, m: Tmap, layer: int, whole_layer_solid: int) -> void {
m.coll = layer m.coll = layer
rt_map_size(m.w, m.h) # clamps to 96x64, clears to ' ' rt_map_size(rt_core_st, m.w, m.h) # clamps to 96x64, clears to ' '
if layer < 0 or layer >= len(m.layers) { return } if layer < 0 or layer >= len(m.layers) { return }
let l = m.layers[layer] let l = m.layers[layer]
if l.kind != 0 { return } if l.kind != 0 { return }
@ -750,8 +750,8 @@ function tmap_project_layer(m: Tmap, layer: int, whole_layer_solid: int) -> void
if gid != 0 { if gid != 0 {
var k = tmap_collision_kind(m, gid) var k = tmap_collision_kind(m, gid)
if k == 0 and whole_layer_solid != 0 { k = 1 } # collision-layer fallback if k == 0 and whole_layer_solid != 0 { k = 1 } # collision-layer fallback
if k == 1 { rt_map[y * 96 + x] = '#' } # '#' if k == 1 { rt_core_st.rt_map[y * 96 + x] = '#' } # '#'
if k == 2 { rt_map[y * 96 + x] = '=' } # '=' if k == 2 { rt_core_st.rt_map[y * 96 + x] = '=' } # '='
} }
} }
x += 1 x += 1
@ -763,11 +763,11 @@ function tmap_project_layer(m: Tmap, layer: int, whole_layer_solid: int) -> void
# project a designated collision layer (non-zero GID is solid unless its tile # project a designated collision layer (non-zero GID is solid unless its tile
# metadata says otherwise) — the P1 default, called automatically on load. # metadata says otherwise) — the P1 default, called automatically on load.
function tmap_project(m: Tmap, layer: int) -> void { tmap_project_layer(m, layer, 1) } function tmap_project(rt_core_st: mut RtCoreState, m: Tmap, layer: int) -> void { tmap_project_layer(rt_core_st, m, layer, 1) }
# drive collision from per-tile metadata alone (objectgroup hitboxes / property # drive collision from per-tile metadata alone (objectgroup hitboxes / property
# convention) over any layer — a tile with no collision metadata stays passable. # convention) over any layer — a tile with no collision metadata stays passable.
function tmap_collide(m: Tmap, layer: int) -> void { tmap_project_layer(m, layer, 0) } function tmap_collide(rt_core_st: mut RtCoreState, m: Tmap, layer: int) -> void { tmap_project_layer(rt_core_st, m, layer, 0) }
# find a tile layer named collision/solids/walls (case-sensitive), or -1. # find a tile layer named collision/solids/walls (case-sensitive), or -1.
function tmap_find_collision(m: Tmap) -> int { function tmap_find_collision(m: Tmap) -> int {
@ -784,10 +784,10 @@ function tmap_find_collision(m: Tmap) -> int {
# blit one tilew x tileh tile from a tileset image (source origin sx,sy) to the # blit one tilew x tileh tile from a tileset image (source origin sx,sy) to the
# framebuffer at (dx,dy), applying the three flip flags. Square tiles assumed # framebuffer at (dx,dy), applying the three flip flags. Square tiles assumed
# for the diagonal flip (Kenney art is 16x16), which is the orthogonal case. # for the diagonal flip (Kenney art is 16x16), which is the orthogonal case.
function tmap_blit_tile(imgid: int, sx: int, sy: int, tw: int, th: int, dx: int, dy: int, fh: int, fv: int, fd: int) -> void { function tmap_blit_tile(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, imgid: int, sx: int, sy: int, tw: int, th: int, dx: int, dy: int, fh: int, fv: int, fd: int) -> void {
if imgid < 0 { return } if imgid < 0 { return }
let s: words = img_px[imgid] let s: words = rt_image_st.img_px[imgid]
let iw = img_w[imgid] let iw = rt_image_st.img_w[imgid]
var j = 0 var j = 0
while j < th { while j < th {
var i = 0 var i = 0
@ -798,7 +798,7 @@ function tmap_blit_tile(imgid: int, sx: int, sy: int, tw: int, th: int, dx: int,
if fh == 1 { u = tw - 1 - u } if fh == 1 { u = tw - 1 - u }
if fv == 1 { v = th - 1 - v } if fv == 1 { v = th - 1 - v }
let argb = s[(sy + v) * iw + (sx + u)] let argb = s[(sy + v) * iw + (sx + u)]
rt_blend_px(dx + i, dy + j, argb) rt_blend_px(rt_core_st, dx + i, dy + j, argb)
i += 1 i += 1
} }
j += 1 j += 1
@ -806,7 +806,7 @@ function tmap_blit_tile(imgid: int, sx: int, sy: int, tw: int, th: int, dx: int,
} }
# draw one tile GID at map cell (x,y) with the camera offset already applied. # draw one tile GID at map cell (x,y) with the camera offset already applied.
function tmap_draw_gid(m: Tmap, gid: int, dx: int, dy: int) -> void { function tmap_draw_gid(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, m: Tmap, gid: int, dx: int, dy: int) -> void {
if gid == 0 { return } if gid == 0 { return }
let r = tmap_resolve(m, gid) let r = tmap_resolve(m, gid)
if r.tileset < 0 { return } if r.tileset < 0 { return }
@ -821,7 +821,7 @@ function tmap_draw_gid(m: Tmap, gid: int, dx: int, dy: int) -> void {
# Tiled anchors a tile by its bottom-left, so a tile taller than the map cell # Tiled anchors a tile by its bottom-left, so a tile taller than the map cell
# rises above the cell. # rises above the cell.
let ddy = dy - (ts.tileh - m.tileh) let ddy = dy - (ts.tileh - m.tileh)
tmap_blit_tile(ts.imgid, sx, sy, ts.tilew, ts.tileh, dx, ddy, r.fh, r.fv, r.fd) tmap_blit_tile(rt_core_st, rt_image_st, ts.imgid, sx, sy, ts.tilew, ts.tileh, dx, ddy, r.fh, r.fv, r.fd)
} }
# (tmap_draw / tmap_draw_anim are defined in the P3 section below, over the shared # (tmap_draw / tmap_draw_anim are defined in the P3 section below, over the shared
@ -859,7 +859,7 @@ function tiled_join(a: pointer, b: pointer) -> string { return Path.normalize(Pa
# build the runtime map from an intermediate tree, resolving external tilesets # build the runtime map from an intermediate tree, resolving external tilesets
# and loading tileset images relative to `basedir`. # and loading tileset images relative to `basedir`.
function tmap_build(tree: Val, basedir: pointer) -> Tmap { function tmap_build(rt_image_st: mut RtImageState, rt_inflate_st: mut RtInflateState, rt_xml_st: mut RtXmlState, tree: Val, basedir: pointer) -> Tmap {
let m = new Tmap let m = new Tmap
m.tree = tree m.tree = tree
m.layers = new []TmLayer m.layers = new []TmLayer
@ -878,14 +878,14 @@ function tmap_build(tree: Val, basedir: pointer) -> Tmap {
let src = value_as_str(value_get(tv, "source")) let src = value_as_str(value_get(tv, "source"))
if src != "" { # external .tsx/.tsj if src != "" { # external .tsx/.tsj
let tsxpath = tiled_join(basedir, src) let tsxpath = tiled_join(basedir, src)
let ext = tiled_read_tsx(tsxpath) let ext = tiled_read_tsx(rt_xml_st, tsxpath)
value_put(ext, "firstgid", value_get(tv, "firstgid")) value_put(ext, "firstgid", value_get(tv, "firstgid"))
tv = ext tv = ext
imgdir = Path.dir(tsxpath) imgdir = Path.dir(tsxpath)
} }
let ts = tmap_tileset_from_value(tv) let ts = tmap_tileset_from_value(tv)
let img = value_as_str(value_get(tv, "image")) let img = value_as_str(value_get(tv, "image"))
if img != "" { ts.imgid = rt_image_load(tiled_join(imgdir, img)) } if img != "" { ts.imgid = rt_image_load(rt_image_st, rt_inflate_st, tiled_join(imgdir, img)) }
push(m.tilesets, ts) push(m.tilesets, ts)
i += 1 i += 1
} }
@ -898,7 +898,7 @@ function tmap_build(tree: Val, basedir: pointer) -> Tmap {
while i < len(m.layers) { while i < len(m.layers) {
if m.layers[i].kind == 2 { if m.layers[i].kind == 2 {
let img = value_as_str(value_get(m.layers[i].data, "image")) let img = value_as_str(value_get(m.layers[i].data, "image"))
if img != "" { m.layers[i].imgid = rt_image_load(tiled_join(basedir, img)) } if img != "" { m.layers[i].imgid = rt_image_load(rt_image_st, rt_inflate_st, tiled_join(basedir, img)) }
} }
i += 1 i += 1
} }
@ -964,12 +964,12 @@ function tmap_add_layer(m: Tmap, lv: Val) -> void {
# load a Tiled map file into the runtime model: read + parse (TMX or TMJ), build # load a Tiled map file into the runtime model: read + parse (TMX or TMJ), build
# the model (resolving external tilesets + images), and project the collision # the model (resolving external tilesets + images), and project the collision
# layer (a tile layer named collision/solids/walls) down to the legacy tilemap. # layer (a tile layer named collision/solids/walls) down to the legacy tilemap.
function tiled_load(path: pointer) -> Tmap { function tiled_load(rt_core_st: mut RtCoreState, rt_image_st: mut RtImageState, rt_inflate_st: mut RtInflateState, rt_xml_st: mut RtXmlState, rt_zstd_st: mut RtZstdState, path: pointer) -> Tmap {
let tree = tiled_read(path) let tree = tiled_read(rt_inflate_st, rt_xml_st, rt_zstd_st, path)
let m = tmap_build(tree, Path.dir(path)) let m = tmap_build(rt_image_st, rt_inflate_st, rt_xml_st, tree, Path.dir(path))
tmap_resolve_templates(m, Path.dir(path)) # #72: fill template-instance objects tmap_resolve_templates(rt_xml_st, m, Path.dir(path)) # #72: fill template-instance objects
let c = tmap_find_collision(m) let c = tmap_find_collision(m)
if c >= 0 { tmap_project(m, c) } if c >= 0 { tmap_project(rt_core_st, m, c) }
return m return m
} }
@ -1032,7 +1032,7 @@ function tmap_is_animated(m: Tmap, gid: int) -> int {
# draw every visible tile layer (animated tiles resolved for `frame`) plus every # draw every visible tile layer (animated tiles resolved for `frame`) plus every
# tile object on the object layers, in file order, offset by the camera. # tile object on the object layers, in file order, offset by the camera.
function tmap_draw_full(m: Tmap, camx: int, camy: int, frame: int, animate: int) -> void { function tmap_draw_full(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, m: Tmap, camx: int, camy: int, frame: int, animate: int) -> void {
var li = 0 var li = 0
while li < len(m.layers) { while li < len(m.layers) {
let l = m.layers[li] let l = m.layers[li]
@ -1047,7 +1047,7 @@ function tmap_draw_full(m: Tmap, camx: int, camy: int, frame: int, animate: int)
if gid != 0 { if gid != 0 {
if animate != 0 { gid = tmap_frame_gid(m, gid, frame) } if animate != 0 { gid = tmap_frame_gid(m, gid, frame) }
# orientation transform places the cell (orthogonal / iso / hex / staggered) # orientation transform places the cell (orthogonal / iso / hex / staggered)
tmap_draw_gid(m, gid, tmap_cell_sx(m, x, y) + lox - camx, tmap_cell_sy(m, x, y) + loy - camy) tmap_draw_gid(rt_core_st, rt_image_st, m, gid, tmap_cell_sx(m, x, y) + lox - camx, tmap_cell_sy(m, x, y) + loy - camy)
} }
x += 1 x += 1
} }
@ -1055,7 +1055,7 @@ function tmap_draw_full(m: Tmap, camx: int, camy: int, frame: int, animate: int)
} }
} }
if l.kind == 2 and l.visible != 0 { # image layer (parallax / repeat) if l.kind == 2 and l.visible != 0 { # image layer (parallax / repeat)
tmap_draw_imagelayer(m, l, camx, camy) tmap_draw_imagelayer(rt_core_st, rt_image_st, m, l, camx, camy)
} }
if l.kind == 1 and l.visible != 0 { # object layer: draw tile objects if l.kind == 1 and l.visible != 0 { # object layer: draw tile objects
let objs = value_get(l.data, "objects") let objs = value_get(l.data, "objects")
@ -1068,7 +1068,7 @@ function tmap_draw_full(m: Tmap, camx: int, camy: int, frame: int, animate: int)
let ox = value_as_int(value_get(ob, "x")) let ox = value_as_int(value_get(ob, "x"))
let oy = value_as_int(value_get(ob, "y")) let oy = value_as_int(value_get(ob, "y"))
# Tiled anchors a tile object by its bottom-left corner # Tiled anchors a tile object by its bottom-left corner
tmap_draw_gid(m, g, ox - camx, oy - m.tileh - camy) tmap_draw_gid(rt_core_st, rt_image_st, m, g, ox - camx, oy - m.tileh - camy)
} }
oi += 1 oi += 1
} }
@ -1078,11 +1078,11 @@ function tmap_draw_full(m: Tmap, camx: int, camy: int, frame: int, animate: int)
} }
# static draw (no animation) — the P1 entry point. # static draw (no animation) — the P1 entry point.
function tmap_draw(m: Tmap, camx: int, camy: int) -> void { tmap_draw_full(m, camx, camy, 0, 0) } function tmap_draw(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, m: Tmap, camx: int, camy: int) -> void { tmap_draw_full(rt_core_st, rt_image_st, m, camx, camy, 0, 0) }
# animated draw at engine `frame` (pass Time.frame): animated tiles advance, # animated draw at engine `frame` (pass Time.frame): animated tiles advance,
# deterministically and frame-identically across runs. # deterministically and frame-identically across runs.
function tmap_draw_anim(m: Tmap, camx: int, camy: int, frame: int) -> void { tmap_draw_full(m, camx, camy, frame, 1) } function tmap_draw_anim(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, m: Tmap, camx: int, camy: int, frame: int) -> void { tmap_draw_full(rt_core_st, rt_image_st, m, camx, camy, frame, 1) }
# ============================================================================ # ============================================================================
# P4 (#72) — object layers (shapes + text), custom properties/types, templates, # P4 (#72) — object layers (shapes + text), custom properties/types, templates,
@ -1132,13 +1132,13 @@ function tiled_prop_node(container: Val, name: pointer) -> Val {
# a property's raw string value, with a fallback to the object's custom-type # a property's raw string value, with a fallback to the object's custom-type
# default (objecttypes.xml, via the type table). "" when absent everywhere. # default (objecttypes.xml, via the type table). "" when absent everywhere.
function tiled_prop_str(container: Val, name: pointer) -> string { function tiled_prop_str(rt_tiled_st: mut RtTiledState, container: Val, name: pointer) -> string {
let p = tiled_prop_node(container, name) let p = tiled_prop_node(container, name)
if value_kind(p) == 6 { return value_as_str(value_get(p, "value")) } if value_kind(p) == 6 { return value_as_str(value_get(p, "value")) }
# fall back to the container's class default # fall back to the container's class default
let cls = value_as_str(value_get(container, "type")) let cls = value_as_str(value_get(container, "type"))
if cls != "" { if cls != "" {
let d = tiled_type_default(cls, name) let d = tiled_type_default(rt_tiled_st, cls, name)
if value_kind(d) == 6 { return value_as_str(value_get(d, "value")) } if value_kind(d) == 6 { return value_as_str(value_get(d, "value")) }
} }
return "" return ""
@ -1146,19 +1146,19 @@ function tiled_prop_str(container: Val, name: pointer) -> string {
# a property parsed as an integer ("true"/"false" -> 1/0), with the same default # a property parsed as an integer ("true"/"false" -> 1/0), with the same default
# fallback. # fallback.
function tiled_prop_int(container: Val, name: pointer) -> int { function tiled_prop_int(rt_tiled_st: mut RtTiledState, container: Val, name: pointer) -> int {
let s = tiled_prop_str(container, name) let s = tiled_prop_str(rt_tiled_st, container, name)
if s == "true" { return 1 } if s == "true" { return 1 }
if s == "false" { return 0 } if s == "false" { return 0 }
return xml_atoi(s) return xml_atoi(s)
} }
function tiled_prop_type(container: Val, name: pointer) -> string { function tiled_prop_type(rt_tiled_st: mut RtTiledState, container: Val, name: pointer) -> string {
let p = tiled_prop_node(container, name) let p = tiled_prop_node(container, name)
if value_kind(p) == 6 { return value_as_str(value_get(p, "type")) } if value_kind(p) == 6 { return value_as_str(value_get(p, "type")) }
let cls = value_as_str(value_get(container, "type")) let cls = value_as_str(value_get(container, "type"))
if cls != "" { if cls != "" {
let d = tiled_type_default(cls, name) let d = tiled_type_default(rt_tiled_st, cls, name)
if value_kind(d) == 6 { return value_as_str(value_get(d, "type")) } if value_kind(d) == 6 { return value_as_str(value_get(d, "type")) }
} }
return "" return ""
@ -1168,21 +1168,23 @@ function tiled_prop_type(container: Val, name: pointer) -> string {
# The project custom-type table: an object mapping a type/class name to its list # The project custom-type table: an object mapping a type/class name to its list
# of {name,type,value(default)} property definitions. class/enum properties then # of {name,type,value(default)} property definitions. class/enum properties then
# resolve their defaults against it. # resolve their defaults against it.
var tiled_type_table: Val = null export state RtTiledState {
tiled_type_table: Val = null
}
function tiled_types() -> Val { function tiled_types(rt_tiled_st: mut RtTiledState) -> Val {
if tiled_type_table == null { tiled_type_table = value_object() } if rt_tiled_st.tiled_type_table == null { rt_tiled_st.tiled_type_table = value_object() }
return tiled_type_table return rt_tiled_st.tiled_type_table
} }
# load an objecttypes.xml file into the type table. Each <objecttype name=..> maps # load an objecttypes.xml file into the type table. Each <objecttype name=..> maps
# to its <property name= type= default=> list. # to its <property name= type= default=> list.
function tiled_load_types(path: pointer) -> int { function tiled_load_types(rt_tiled_st: mut RtTiledState, rt_xml_st: mut RtXmlState, path: pointer) -> int {
let text = Fs.read_text(path) let text = Fs.read_text(path)
if text == null { return 0 } if text == null { return 0 }
if text == "" { return 0 } if text == "" { return 0 }
let root = xml_parse(text) let root = xml_parse(rt_xml_st, text)
let tbl = tiled_types() let tbl = tiled_types(rt_tiled_st)
var n = 0 var n = 0
var i = 0 var i = 0
while i < xml_child_count(root) { while i < xml_child_count(root) {
@ -1212,8 +1214,8 @@ function tiled_load_types(path: pointer) -> int {
} }
# the default {name,type,value} property node for a custom type, or a null node. # the default {name,type,value} property node for a custom type, or a null node.
function tiled_type_default(typename: pointer, propname: pointer) -> Val { function tiled_type_default(rt_tiled_st: mut RtTiledState, typename: pointer, propname: pointer) -> Val {
let tbl = tiled_types() let tbl = tiled_types(rt_tiled_st)
let props = value_get(tbl, typename) let props = value_get(tbl, typename)
if value_kind(props) != 5 { return value_null() } if value_kind(props) != 5 { return value_null() }
var i = 0 var i = 0
@ -1228,12 +1230,12 @@ function tiled_type_default(typename: pointer, propname: pointer) -> Val {
# ---- templates (.tx / .tj) ------------------------------------------------- # ---- templates (.tx / .tj) -------------------------------------------------
# read a template file -> its object Value (the <object> a .tx wraps, or the # read a template file -> its object Value (the <object> a .tx wraps, or the
# "object" of a .tj). External reusable object definitions. # "object" of a .tj). External reusable object definitions.
function tiled_read_template(path: pointer) -> Val { function tiled_read_template(rt_xml_st: mut RtXmlState, path: pointer) -> Val {
let text = Fs.read_text(path) let text = Fs.read_text(path)
if text == null { return value_null() } if text == null { return value_null() }
if text == "" { return value_null() } if text == "" { return value_null() }
if tiled_first_byte(text) == 60 { # XML .tx if tiled_first_byte(text) == 60 { # XML .tx
let root = xml_parse(text) # <template> let root = xml_parse(rt_xml_st, text) # <template>
let ob = xml_find(root, "object") let ob = xml_find(root, "object")
if xml_tag(ob) == "object" { return tmx_object_to_value(ob) } if xml_tag(ob) == "object" { return tmx_object_to_value(ob) }
return value_null() return value_null()
@ -1257,7 +1259,7 @@ function tiled_merge_template(inst: Val, tmpl: Val) -> void {
# resolve every object that references a `template`, loading + merging it. Paths # resolve every object that references a `template`, loading + merging it. Paths
# are relative to the map file (`basedir`). # are relative to the map file (`basedir`).
function tmap_resolve_templates(m: Tmap, basedir: pointer) -> void { function tmap_resolve_templates(rt_xml_st: mut RtXmlState, m: Tmap, basedir: pointer) -> void {
var li = 0 var li = 0
while li < len(m.layers) { while li < len(m.layers) {
let l = m.layers[li] let l = m.layers[li]
@ -1267,7 +1269,7 @@ function tmap_resolve_templates(m: Tmap, basedir: pointer) -> void {
while oi < value_count(objs) { while oi < value_count(objs) {
let ob = value_at(objs, oi) let ob = value_at(objs, oi)
let tp = value_as_str(value_get(ob, "template")) let tp = value_as_str(value_get(ob, "template"))
if tp != "" { tiled_merge_template(ob, tiled_read_template(tiled_join(basedir, tp))) } if tp != "" { tiled_merge_template(ob, tiled_read_template(rt_xml_st, tiled_join(basedir, tp))) }
oi += 1 oi += 1
} }
} }
@ -1317,15 +1319,15 @@ function tmap_cell_sy(m: Tmap, x: int, y: int) -> int {
# ---- image-layer render ---------------------------------------------------- # ---- image-layer render ----------------------------------------------------
# draw an <imagelayer> with parallax + optional repeat. `parallax` is 1.0 by # draw an <imagelayer> with parallax + optional repeat. `parallax` is 1.0 by
# default (moves with the camera); repeatx/repeaty tile the image across the view. # default (moves with the camera); repeatx/repeaty tile the image across the view.
function tmap_draw_imagelayer(m: Tmap, l: TmLayer, camx: int, camy: int) -> void { function tmap_draw_imagelayer(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, m: Tmap, l: TmLayer, camx: int, camy: int) -> void {
if l.imgid < 0 { return } if l.imgid < 0 { return }
let ox = value_as_int(value_get(l.data, "offsetx")) let ox = value_as_int(value_get(l.data, "offsetx"))
let oy = value_as_int(value_get(l.data, "offsety")) let oy = value_as_int(value_get(l.data, "offsety"))
# parallax factor (fixed, default 1.0); px/py = the drawn origin # parallax factor (fixed, default 1.0); px/py = the drawn origin
var px = ox - camx var px = ox - camx
var py = oy - camy var py = oy - camy
let iw = img_w[l.imgid] let iw = rt_image_st.img_w[l.imgid]
let ih = img_h[l.imgid] let ih = rt_image_st.img_h[l.imgid]
let repx = value_as_int(value_get(l.data, "repeatx")) let repx = value_as_int(value_get(l.data, "repeatx"))
let repy = value_as_int(value_get(l.data, "repeaty")) let repy = value_as_int(value_get(l.data, "repeaty"))
# starting origin: for a repeating axis, back up to before the screen # starting origin: for a repeating axis, back up to before the screen
@ -1339,7 +1341,7 @@ function tmap_draw_imagelayer(m: Tmap, l: TmLayer, camx: int, camy: int) -> void
var xx = sx0 var xx = sx0
var first_x = 1 var first_x = 1
while (xx < rt_screen_w()) and (first_x == 1 or repx != 0) { while (xx < rt_screen_w()) and (first_x == 1 or repx != 0) {
rt_draw_image(l.imgid, xx, yy) rt_draw_image(rt_core_st, rt_image_st, l.imgid, xx, yy)
first_x = 0 first_x = 0
if repx == 0 { xx = rt_screen_w() } else { xx += iw } if repx == 0 { xx = rt_screen_w() } else { xx += iw }
} }

View file

@ -24,7 +24,7 @@ function tiled_apply_field(e: int, fname: pointer, v: int) -> void {
# has one) takes the object's x/y, and each object custom property sets a like- # has one) takes the object's x/y, and each object custom property sets a like-
# named int/bool component field. Returns the entity, or -1 if the model is # named int/bool component field. Returns the entity, or -1 if the model is
# unknown. Opt-in — the core load never calls this. # unknown. Opt-in — the core load never calls this.
function tiled_spawn_object(m: Tmap, obj: Val, model_name: pointer) -> int { function tiled_spawn_object(rt_tiled_st: mut RtTiledState, m: Tmap, obj: Val, model_name: pointer) -> int {
let mid = world_model_id(model_name) let mid = world_model_id(model_name)
if mid < 0 { return -1 } if mid < 0 { return -1 }
let e = world_spawn(mid) let e = world_spawn(mid)
@ -39,17 +39,17 @@ function tiled_spawn_object(m: Tmap, obj: Val, model_name: pointer) -> int {
var i = 0 var i = 0
while i < value_count(props) { while i < value_count(props) {
let p = value_at(props, i) let p = value_at(props, i)
tiled_apply_field(e, value_as_str(value_get(p, "name")), tiled_prop_int(obj, value_as_str(value_get(p, "name")))) tiled_apply_field(e, value_as_str(value_get(p, "name")), tiled_prop_int(rt_tiled_st, obj, value_as_str(value_get(p, "name"))))
i += 1 i += 1
} }
return e return e
} }
# spawn every object on `layer` as `model_name` (opt-in). Returns the count. # spawn every object on `layer` as `model_name` (opt-in). Returns the count.
function tiled_spawn_layer(m: Tmap, layer: int, model_name: pointer) -> int { function tiled_spawn_layer(rt_tiled_st: mut RtTiledState, m: Tmap, layer: int, model_name: pointer) -> int {
var n = 0 var n = 0
let c = tmap_object_count(m, layer) let c = tmap_object_count(m, layer)
var i = 0 var i = 0
while i < c { if tiled_spawn_object(m, tmap_object(m, layer, i), model_name) >= 0 { n += 1 }; i += 1 } while i < c { if tiled_spawn_object(rt_tiled_st, m, tmap_object(m, layer, i), model_name) >= 0 { n += 1 }; i += 1 }
return n return n
} }

View file

@ -18,90 +18,92 @@ const TT_EDGES: int = 16384 # line segments in one glyph
const TT_GC: int = 512 # glyph cache entries const TT_GC: int = 512 # glyph cache entries
# ---- per-font tables (parallel arrays; index = font id) ------------------- # ---- per-font tables (parallel arrays; index = font id) -------------------
var tt_data: pointers = null export state RtTruetypeState {
var tt_size: words = null tt_data: pointers = null
var tt_upem: words = null tt_size: words = null
var tt_nglyf: words = null tt_upem: words = null
var tt_locfm: words = null tt_nglyf: words = null
var tt_nhm: words = null tt_locfm: words = null
var tt_asc: words = null tt_nhm: words = null
var tt_desc: words = null tt_asc: words = null
var tt_gap: words = null tt_desc: words = null
var tt_glyf: words = null tt_gap: words = null
var tt_loca: words = null tt_glyf: words = null
var tt_hmtx: words = null tt_loca: words = null
var tt_cmap: words = null tt_hmtx: words = null
var tt_cfmt: words = null tt_cmap: words = null
var tt_n: int = 0 tt_cfmt: words = null
tt_n: int = 0
ol_x: fixeds = null
ol_y: fixeds = null
ol_on: words = null
ol_ends: words = null
ol_n: int = 0
ol_ne: int = 0
ed_x0: fixeds = null
ed_y0: fixeds = null
ed_x1: fixeds = null
ed_y1: fixeds = null
ed_n: int = 0
sc_x: fixeds = null # scanline crossings
sc_d: words = null
gc_used: words = null
gc_font: words = null
gc_cp: words = null
gc_px: words = null
gc_w: words = null
gc_h: words = null
gc_ox: words = null
gc_oy: words = null
gc_adv: words = null
gc_bmp: pointers = null
}
# ---- outline + edge scratch (reused for every glyph) ---------------------- # ---- outline + edge scratch (reused for every glyph) ----------------------
var ol_x: fixeds = null
var ol_y: fixeds = null
var ol_on: words = null
var ol_ends: words = null
var ol_n: int = 0
var ol_ne: int = 0
var ed_x0: fixeds = null
var ed_y0: fixeds = null
var ed_x1: fixeds = null
var ed_y1: fixeds = null
var ed_n: int = 0
var sc_x: fixeds = null # scanline crossings
var sc_d: words = null
# ---- glyph cache ---------------------------------------------------------- # ---- glyph cache ----------------------------------------------------------
var gc_used: words = null
var gc_font: words = null
var gc_cp: words = null
var gc_px: words = null
var gc_w: words = null
var gc_h: words = null
var gc_ox: words = null
var gc_oy: words = null
var gc_adv: words = null
var gc_bmp: pointers = null
function rt_tt_init() -> void { function rt_tt_init(rt_truetype_st: mut RtTruetypeState) -> void {
tt_data = pointers(TT_MAX) rt_truetype_st.tt_data = pointers(TT_MAX)
tt_size = words(TT_MAX) rt_truetype_st.tt_size = words(TT_MAX)
tt_upem = words(TT_MAX) rt_truetype_st.tt_upem = words(TT_MAX)
tt_nglyf = words(TT_MAX) rt_truetype_st.tt_nglyf = words(TT_MAX)
tt_locfm = words(TT_MAX) rt_truetype_st.tt_locfm = words(TT_MAX)
tt_nhm = words(TT_MAX) rt_truetype_st.tt_nhm = words(TT_MAX)
tt_asc = words(TT_MAX) rt_truetype_st.tt_asc = words(TT_MAX)
tt_desc = words(TT_MAX) rt_truetype_st.tt_desc = words(TT_MAX)
tt_gap = words(TT_MAX) rt_truetype_st.tt_gap = words(TT_MAX)
tt_glyf = words(TT_MAX) rt_truetype_st.tt_glyf = words(TT_MAX)
tt_loca = words(TT_MAX) rt_truetype_st.tt_loca = words(TT_MAX)
tt_hmtx = words(TT_MAX) rt_truetype_st.tt_hmtx = words(TT_MAX)
tt_cmap = words(TT_MAX) rt_truetype_st.tt_cmap = words(TT_MAX)
tt_cfmt = words(TT_MAX) rt_truetype_st.tt_cfmt = words(TT_MAX)
ol_x = fixeds(TT_PTS) rt_truetype_st.ol_x = fixeds(TT_PTS)
ol_y = fixeds(TT_PTS) rt_truetype_st.ol_y = fixeds(TT_PTS)
ol_on = words(TT_PTS) rt_truetype_st.ol_on = words(TT_PTS)
ol_ends = words(256) rt_truetype_st.ol_ends = words(256)
ed_x0 = fixeds(TT_EDGES) rt_truetype_st.ed_x0 = fixeds(TT_EDGES)
ed_y0 = fixeds(TT_EDGES) rt_truetype_st.ed_y0 = fixeds(TT_EDGES)
ed_x1 = fixeds(TT_EDGES) rt_truetype_st.ed_x1 = fixeds(TT_EDGES)
ed_y1 = fixeds(TT_EDGES) rt_truetype_st.ed_y1 = fixeds(TT_EDGES)
sc_x = fixeds(TT_EDGES) rt_truetype_st.sc_x = fixeds(TT_EDGES)
sc_d = words(TT_EDGES) rt_truetype_st.sc_d = words(TT_EDGES)
gc_used = words(TT_GC) rt_truetype_st.gc_used = words(TT_GC)
gc_font = words(TT_GC) rt_truetype_st.gc_font = words(TT_GC)
gc_cp = words(TT_GC) rt_truetype_st.gc_cp = words(TT_GC)
gc_px = words(TT_GC) rt_truetype_st.gc_px = words(TT_GC)
gc_w = words(TT_GC) rt_truetype_st.gc_w = words(TT_GC)
gc_h = words(TT_GC) rt_truetype_st.gc_h = words(TT_GC)
gc_ox = words(TT_GC) rt_truetype_st.gc_ox = words(TT_GC)
gc_oy = words(TT_GC) rt_truetype_st.gc_oy = words(TT_GC)
gc_adv = words(TT_GC) rt_truetype_st.gc_adv = words(TT_GC)
gc_bmp = pointers(TT_GC) rt_truetype_st.gc_bmp = pointers(TT_GC)
fill(gc_used, 0, TT_GC * 4) fill(rt_truetype_st.gc_used, 0, TT_GC * 4)
} }
# ---- big-endian readers --------------------------------------------------- # ---- big-endian readers ---------------------------------------------------
@ -138,9 +140,9 @@ function tt_find_table(d: pointer, base: int, a: int, b: int, c: int, e: int) ->
} }
# pick the most capable Unicode subtable, as the C loader did # pick the most capable Unicode subtable, as the C loader did
function tt_pick_cmap(id: int, d: pointer, co: int) -> void { function tt_pick_cmap(rt_truetype_st: mut RtTruetypeState, id: int, d: pointer, co: int) -> void {
tt_cmap[id] = -1 rt_truetype_st.tt_cmap[id] = -1
tt_cfmt[id] = 0 rt_truetype_st.tt_cfmt[id] = 0
if co < 0 { return } if co < 0 { return }
let n = tt_u16(d, co + 2) let n = tt_u16(d, co + 2)
var best = -1 var best = -1
@ -162,15 +164,15 @@ function tt_pick_cmap(id: int, d: pointer, co: int) -> void {
} }
} }
if best < 0 { return } if best < 0 { return }
tt_cmap[id] = best rt_truetype_st.tt_cmap[id] = best
tt_cfmt[id] = tt_u16(d, best) rt_truetype_st.tt_cfmt[id] = tt_u16(d, best)
} }
function rt_font_load(path: string) -> int { function rt_font_load(rt_image_st: mut RtImageState, rt_truetype_st: mut RtTruetypeState, path: string) -> int {
if tt_n >= TT_MAX { return -1 } if rt_truetype_st.tt_n >= TT_MAX { return -1 }
let d = rt_read_file(path) let d = rt_read_file(rt_image_st, path)
if (d == null) { return -1 } if (d == null) { return -1 }
let size = rt_file_len let size = rt_image_st.rt_file_len
var base = 0 var base = 0
# a .ttc collection points at its first font # a .ttc collection points at its first font
if size >= 16 { if size >= 16 {
@ -191,32 +193,32 @@ function rt_font_load(path: string) -> int {
if glyf < 0 { return -1 } if glyf < 0 { return -1 }
if hmtx < 0 { return -1 } if hmtx < 0 { return -1 }
let id = tt_n let id = rt_truetype_st.tt_n
tt_n += 1 rt_truetype_st.tt_n += 1
tt_data[id] = d rt_truetype_st.tt_data[id] = d
tt_size[id] = size rt_truetype_st.tt_size[id] = size
var upem = tt_u16(d, head + 18) var upem = tt_u16(d, head + 18)
if upem <= 0 { upem = 1000 } if upem <= 0 { upem = 1000 }
tt_upem[id] = upem rt_truetype_st.tt_upem[id] = upem
tt_locfm[id] = tt_i16(d, head + 50) rt_truetype_st.tt_locfm[id] = tt_i16(d, head + 50)
tt_nglyf[id] = tt_u16(d, maxp + 4) rt_truetype_st.tt_nglyf[id] = tt_u16(d, maxp + 4)
tt_asc[id] = tt_i16(d, hhea + 4) rt_truetype_st.tt_asc[id] = tt_i16(d, hhea + 4)
tt_desc[id] = tt_i16(d, hhea + 6) rt_truetype_st.tt_desc[id] = tt_i16(d, hhea + 6)
tt_gap[id] = tt_i16(d, hhea + 8) rt_truetype_st.tt_gap[id] = tt_i16(d, hhea + 8)
tt_nhm[id] = tt_u16(d, hhea + 34) rt_truetype_st.tt_nhm[id] = tt_u16(d, hhea + 34)
tt_glyf[id] = glyf rt_truetype_st.tt_glyf[id] = glyf
tt_loca[id] = loca rt_truetype_st.tt_loca[id] = loca
tt_hmtx[id] = hmtx rt_truetype_st.tt_hmtx[id] = hmtx
tt_pick_cmap(id, d, tt_find_table(d, base, 99, 109, 97, 112)) tt_pick_cmap(rt_truetype_st, id, d, tt_find_table(d, base, 99, 109, 97, 112))
return id return id
} }
# ---- codepoint -> glyph id ------------------------------------------------ # ---- codepoint -> glyph id ------------------------------------------------
function tt_glyph_index(id: int, cp: int) -> int { function tt_glyph_index(rt_truetype_st: RtTruetypeState, id: int, cp: int) -> int {
let d = tt_data[id] let d = rt_truetype_st.tt_data[id]
let s = tt_cmap[id] let s = rt_truetype_st.tt_cmap[id]
if s < 0 { return 0 } if s < 0 { return 0 }
let fmt = tt_cfmt[id] let fmt = rt_truetype_st.tt_cfmt[id]
if fmt == 4 { if fmt == 4 {
if cp > 65535 { return 0 } if cp > 65535 { return 0 }
@ -261,46 +263,46 @@ function tt_glyph_index(id: int, cp: int) -> int {
return 0 return 0
} }
function tt_advance(id: int, gid: int) -> int { function tt_advance(rt_truetype_st: RtTruetypeState, id: int, gid: int) -> int {
let d = tt_data[id] let d = rt_truetype_st.tt_data[id]
let hmtx = tt_hmtx[id] let hmtx = rt_truetype_st.tt_hmtx[id]
let n = tt_nhm[id] let n = rt_truetype_st.tt_nhm[id]
if gid < n { return tt_u16(d, hmtx + gid * 4) } if gid < n { return tt_u16(d, hmtx + gid * 4) }
return tt_u16(d, hmtx + (n - 1) * 4) return tt_u16(d, hmtx + (n - 1) * 4)
} }
# ---- outline extraction --------------------------------------------------- # ---- outline extraction ---------------------------------------------------
function ol_pt(x: fixed, y: fixed, on: int) -> void { function ol_pt(rt_truetype_st: mut RtTruetypeState, x: fixed, y: fixed, on: int) -> void {
if ol_n >= TT_PTS { return } if rt_truetype_st.ol_n >= TT_PTS { return }
ol_x[ol_n] = x rt_truetype_st.ol_x[rt_truetype_st.ol_n] = x
ol_y[ol_n] = y rt_truetype_st.ol_y[rt_truetype_st.ol_n] = y
ol_on[ol_n] = on rt_truetype_st.ol_on[rt_truetype_st.ol_n] = on
ol_n += 1 rt_truetype_st.ol_n += 1
} }
function tt_glyph_start(id: int, gid: int) -> int { function tt_glyph_start(rt_truetype_st: RtTruetypeState, id: int, gid: int) -> int {
let d = tt_data[id] let d = rt_truetype_st.tt_data[id]
let loca = tt_loca[id] let loca = rt_truetype_st.tt_loca[id]
if tt_locfm[id] == 0 { return tt_u16(d, loca + gid * 2) * 2 } if rt_truetype_st.tt_locfm[id] == 0 { return tt_u16(d, loca + gid * 2) * 2 }
return tt_u32(d, loca + gid * 4) return tt_u32(d, loca + gid * 4)
} }
function tt_glyph_end(id: int, gid: int) -> int { function tt_glyph_end(rt_truetype_st: RtTruetypeState, id: int, gid: int) -> int {
let d = tt_data[id] let d = rt_truetype_st.tt_data[id]
let loca = tt_loca[id] let loca = rt_truetype_st.tt_loca[id]
if tt_locfm[id] == 0 { return tt_u16(d, loca + (gid + 1) * 2) * 2 } if rt_truetype_st.tt_locfm[id] == 0 { return tt_u16(d, loca + (gid + 1) * 2) * 2 }
return tt_u32(d, loca + (gid + 1) * 4) return tt_u32(d, loca + (gid + 1) * 4)
} }
# Append glyph `gid`, transformed by [a b c e] + (dx,dy), to the outline. # Append glyph `gid`, transformed by [a b c e] + (dx,dy), to the outline.
function tt_load_outline(id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fixed, dx: fixed, dy: fixed, depth: int) -> void { function tt_load_outline(rt_truetype_st: mut RtTruetypeState, id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fixed, dx: fixed, dy: fixed, depth: int) -> void {
if gid < 0 { return } if gid < 0 { return }
if gid >= tt_nglyf[id] { return } if gid >= rt_truetype_st.tt_nglyf[id] { return }
if depth > 5 { return } if depth > 5 { return }
let goff = tt_glyph_start(id, gid) let goff = tt_glyph_start(rt_truetype_st, id, gid)
let gend = tt_glyph_end(id, gid) let gend = tt_glyph_end(rt_truetype_st, id, gid)
if goff >= gend { return } if goff >= gend { return }
let d = tt_data[id] let d = rt_truetype_st.tt_data[id]
let g = tt_glyf[id] + goff let g = rt_truetype_st.tt_glyf[id] + goff
let nc = tt_i16(d, g) let nc = tt_i16(d, g)
if nc >= 0 { if nc >= 0 {
@ -360,11 +362,11 @@ function tt_load_outline(id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fix
for k in start .. last + 1 { for k in start .. last + 1 {
let X = fixed(xs[k]) let X = fixed(xs[k])
let Y = fixed(ys[k]) let Y = fixed(ys[k])
ol_pt(a * X + c * Y + dx, b * X + e * Y + dy, (flags[k] & 1)) ol_pt(rt_truetype_st, a * X + c * Y + dx, b * X + e * Y + dy, (flags[k] & 1))
} }
if ol_ne < 256 { if rt_truetype_st.ol_ne < 256 {
ol_ends[ol_ne] = ol_n rt_truetype_st.ol_ends[rt_truetype_st.ol_ne] = rt_truetype_st.ol_n
ol_ne += 1 rt_truetype_st.ol_ne += 1
} }
start = last + 1 start = last + 1
} }
@ -417,7 +419,7 @@ function tt_load_outline(id: int, gid: int, a: fixed, b: fixed, c: fixed, e: fix
odx = a * fixed(arg1) + c * fixed(arg2) + dx odx = a * fixed(arg1) + c * fixed(arg2) + dx
ody = b * fixed(arg1) + e * fixed(arg2) + dy ody = b * fixed(arg1) + e * fixed(arg2) + dy
} }
tt_load_outline(id, cgid, a * ca + c * cb, b * ca + e * cb, a * cc + c * ce, b * cc + e * ce, odx, ody, depth + 1) tt_load_outline(rt_truetype_st, id, cgid, a * ca + c * cb, b * ca + e * cb, a * cc + c * ce, b * cc + e * ce, odx, ody, depth + 1)
more = 0 more = 0
if (flags & 32) != 0 { more = 1 } if (flags & 32) != 0 { more = 1 }
} }

View file

@ -2,13 +2,13 @@
# and text drawing. Split out of truetype.ludic (the sfnt/cmap/outline half). # and text drawing. Split out of truetype.ludic (the sfnt/cmap/outline half).
# ---- rasterization -------------------------------------------------------- # ---- rasterization --------------------------------------------------------
function ed_add(x0: fixed, y0: fixed, x1: fixed, y1: fixed) -> void { function ed_add(rt_truetype_st: mut RtTruetypeState, x0: fixed, y0: fixed, x1: fixed, y1: fixed) -> void {
if ed_n >= TT_EDGES { return } if rt_truetype_st.ed_n >= TT_EDGES { return }
ed_x0[ed_n] = x0 rt_truetype_st.ed_x0[rt_truetype_st.ed_n] = x0
ed_y0[ed_n] = y0 rt_truetype_st.ed_y0[rt_truetype_st.ed_n] = y0
ed_x1[ed_n] = x1 rt_truetype_st.ed_x1[rt_truetype_st.ed_n] = x1
ed_y1[ed_n] = y1 rt_truetype_st.ed_y1[rt_truetype_st.ed_n] = y1
ed_n += 1 rt_truetype_st.ed_n += 1
} }
function tt_isqrt(v: int) -> int { function tt_isqrt(v: int) -> int {
@ -24,7 +24,7 @@ function tt_isqrt(v: int) -> int {
} }
# flatten one quadratic Bézier into line segments, subdivided by chord length # flatten one quadratic Bézier into line segments, subdivided by chord length
function ed_quad(x0: fixed, y0: fixed, cx: fixed, cy: fixed, x1: fixed, y1: fixed) -> void { function ed_quad(rt_truetype_st: mut RtTruetypeState, x0: fixed, y0: fixed, cx: fixed, cy: fixed, x1: fixed, y1: fixed) -> void {
let dx = floor(x1) - floor(x0) let dx = floor(x1) - floor(x0)
let dy = floor(y1) - floor(y0) let dy = floor(y1) - floor(y0)
var n = tt_isqrt(dx * dx + dy * dy) / 3 var n = tt_isqrt(dx * dx + dy * dy) / 3
@ -37,7 +37,7 @@ function ed_quad(x0: fixed, y0: fixed, cx: fixed, cy: fixed, x1: fixed, y1: fixe
let u = 1.0 - t let u = 1.0 - t
let qx = u * u * x0 + 2.0 * u * t * cx + t * t * x1 let qx = u * u * x0 + 2.0 * u * t * cx + t * t * x1
let qy = u * u * y0 + 2.0 * u * t * cy + t * t * y1 let qy = u * u * y0 + 2.0 * u * t * cy + t * t * y1
ed_add(px, py, qx, qy) ed_add(rt_truetype_st, px, py, qx, qy)
px = qx px = qx
py = qy py = qy
} }
@ -45,31 +45,34 @@ function ed_quad(x0: fixed, y0: fixed, cx: fixed, cy: fixed, x1: fixed, y1: fixe
# Rasterize a glyph at `px` pixels. Coverage lands in a fresh buffer; the # Rasterize a glyph at `px` pixels. Coverage lands in a fresh buffer; the
# geometry is left in gr_* for the caller. # geometry is left in gr_* for the caller.
var gr_w: int = 0 export state RtTruetypeRasterState {
var gr_h: int = 0 gr_w: int = 0
var gr_ox: int = 0 gr_h: int = 0
var gr_oy: int = 0 gr_ox: int = 0
var gr_adv: int = 0 gr_oy: int = 0
gr_adv: int = 0
u8_next: int = 0 # byte index just past the codepoint last decoded
}
function tt_raster(id: int, gid: int, px: int) -> pointer { function tt_raster(rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, id: int, gid: int, px: int) -> pointer {
let upem = tt_upem[id] let upem = rt_truetype_st.tt_upem[id]
let scale = fixed(px) / upem let scale = fixed(px) / upem
gr_adv = floor(fixed(tt_advance(id, gid)) * scale + 0.5) rt_truetype_raster_st.gr_adv = floor(fixed(tt_advance(rt_truetype_st, id, gid)) * scale + 0.5)
gr_w = 0 rt_truetype_raster_st.gr_w = 0
gr_h = 0 rt_truetype_raster_st.gr_h = 0
ol_n = 0 rt_truetype_st.ol_n = 0
ol_ne = 0 rt_truetype_st.ol_ne = 0
ed_n = 0 rt_truetype_st.ed_n = 0
tt_load_outline(id, gid, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0) tt_load_outline(rt_truetype_st, id, gid, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0)
if ol_n == 0 { return null } if rt_truetype_st.ol_n == 0 { return null }
var minx = 999999.0 var minx = 999999.0
var miny = 999999.0 var miny = 999999.0
var maxx = 0.0 - 999999.0 var maxx = 0.0 - 999999.0
var maxy = 0.0 - 999999.0 var maxy = 0.0 - 999999.0
for i in 0 .. ol_n { for i in 0 .. rt_truetype_st.ol_n {
let X = ol_x[i] * scale let X = rt_truetype_st.ol_x[i] * scale
let Y = ol_y[i] * scale let Y = rt_truetype_st.ol_y[i] * scale
if X < minx { minx = X } if X < minx { minx = X }
if X > maxx { maxx = X } if X > maxx { maxx = X }
if Y < miny { miny = Y } if Y < miny { miny = Y }
@ -87,64 +90,64 @@ function tt_raster(id: int, gid: int, px: int) -> pointer {
if H <= 0 { return null } if H <= 0 { return null }
if W > 1024 { return null } if W > 1024 { return null }
if H > 1024 { return null } if H > 1024 { return null }
gr_w = W rt_truetype_raster_st.gr_w = W
gr_h = H rt_truetype_raster_st.gr_h = H
gr_ox = x0 rt_truetype_raster_st.gr_ox = x0
gr_oy = y1 rt_truetype_raster_st.gr_oy = y1
# build edges in supersample space (y grows downward) # build edges in supersample space (y grows downward)
var start = 0 var start = 0
for ci in 0 .. ol_ne { for ci in 0 .. rt_truetype_st.ol_ne {
let end = ol_ends[ci] let end = rt_truetype_st.ol_ends[ci]
let cnt = end - start let cnt = end - start
if cnt >= 2 { if cnt >= 2 {
var first_on = -1 var first_on = -1
for i in 0 .. cnt { for i in 0 .. cnt {
if first_on < 0 { if ol_on[start + i] == 1 { first_on = i } } if first_on < 0 { if rt_truetype_st.ol_on[start + i] == 1 { first_on = i } }
} }
var sx = 0.0 var sx = 0.0
var sy = 0.0 var sy = 0.0
if first_on < 0 { if first_on < 0 {
# all off-curve: start at the midpoint of the first and last point # all off-curve: start at the midpoint of the first and last point
let mx = (ol_x[start] + ol_x[start + cnt - 1]) / 2 let mx = (rt_truetype_st.ol_x[start] + rt_truetype_st.ol_x[start + cnt - 1]) / 2
let my = (ol_y[start] + ol_y[start + cnt - 1]) / 2 let my = (rt_truetype_st.ol_y[start] + rt_truetype_st.ol_y[start + cnt - 1]) / 2
sx = (mx * scale - fixed(x0)) * TT_SS sx = (mx * scale - fixed(x0)) * TT_SS
sy = (fixed(y1) - my * scale) * TT_SS sy = (fixed(y1) - my * scale) * TT_SS
first_on = 0 first_on = 0
} else { } else {
sx = (ol_x[start + first_on] * scale - fixed(x0)) * TT_SS sx = (rt_truetype_st.ol_x[start + first_on] * scale - fixed(x0)) * TT_SS
sy = (fixed(y1) - ol_y[start + first_on] * scale) * TT_SS sy = (fixed(y1) - rt_truetype_st.ol_y[start + first_on] * scale) * TT_SS
} }
var curx = sx var curx = sx
var cury = sy var cury = sy
var step = 0 var step = 0
while step < cnt { while step < cnt {
let i = (first_on + 1 + step) % cnt let i = (first_on + 1 + step) % cnt
let ix = (ol_x[start + i] * scale - fixed(x0)) * TT_SS let ix = (rt_truetype_st.ol_x[start + i] * scale - fixed(x0)) * TT_SS
let iy = (fixed(y1) - ol_y[start + i] * scale) * TT_SS let iy = (fixed(y1) - rt_truetype_st.ol_y[start + i] * scale) * TT_SS
if ol_on[start + i] == 1 { if rt_truetype_st.ol_on[start + i] == 1 {
ed_add(curx, cury, ix, iy) ed_add(rt_truetype_st, curx, cury, ix, iy)
curx = ix curx = ix
cury = iy cury = iy
} else { } else {
var j = (i + 1) % cnt var j = (i + 1) % cnt
let jx = (ol_x[start + j] * scale - fixed(x0)) * TT_SS let jx = (rt_truetype_st.ol_x[start + j] * scale - fixed(x0)) * TT_SS
let jy = (fixed(y1) - ol_y[start + j] * scale) * TT_SS let jy = (fixed(y1) - rt_truetype_st.ol_y[start + j] * scale) * TT_SS
var ex = jx var ex = jx
var ey = jy var ey = jy
if ol_on[start + j] == 1 { if rt_truetype_st.ol_on[start + j] == 1 {
step += 1 step += 1
} else { } else {
ex = (ix + jx) / 2 # implied on-curve midpoint ex = (ix + jx) / 2 # implied on-curve midpoint
ey = (iy + jy) / 2 ey = (iy + jy) / 2
} }
ed_quad(curx, cury, ix, iy, ex, ey) ed_quad(rt_truetype_st, curx, cury, ix, iy, ex, ey)
curx = ex curx = ex
cury = ey cury = ey
} }
step += 1 step += 1
} }
ed_add(curx, cury, sx, sy) ed_add(rt_truetype_st, curx, cury, sx, sy)
} }
start = end start = end
} }
@ -158,49 +161,49 @@ function tt_raster(id: int, gid: int, px: int) -> pointer {
for sy in 0 .. SH { for sy in 0 .. SH {
let yc = fixed(sy) + 0.5 let yc = fixed(sy) + 0.5
var m = 0 var m = 0
for i in 0 .. ed_n { for i in 0 .. rt_truetype_st.ed_n {
let ya = ed_y0[i] let ya = rt_truetype_st.ed_y0[i]
let yb = ed_y1[i] let yb = rt_truetype_st.ed_y1[i]
var hit = 0 var hit = 0
if ya <= yc { if yb > yc { hit = 1 } } if ya <= yc { if yb > yc { hit = 1 } }
if yb <= yc { if ya > yc { hit = 1 } } if yb <= yc { if ya > yc { hit = 1 } }
if hit == 1 { if hit == 1 {
let t = (yc - ya) / (yb - ya) let t = (yc - ya) / (yb - ya)
sc_x[m] = ed_x0[i] + t * (ed_x1[i] - ed_x0[i]) rt_truetype_st.sc_x[m] = rt_truetype_st.ed_x0[i] + t * (rt_truetype_st.ed_x1[i] - rt_truetype_st.ed_x0[i])
if yb > ya { sc_d[m] = 1 } else { sc_d[m] = -1 } if yb > ya { rt_truetype_st.sc_d[m] = 1 } else { rt_truetype_st.sc_d[m] = -1 }
m += 1 m += 1
} }
} }
# insertion sort the crossings by x # insertion sort the crossings by x
for i in 1 .. m { for i in 1 .. m {
let kx = sc_x[i] let kx = rt_truetype_st.sc_x[i]
let kd = sc_d[i] let kd = rt_truetype_st.sc_d[i]
var j = i - 1 var j = i - 1
var placed = 0 var placed = 0
while j >= 0 { while j >= 0 {
if sc_x[j] > kx { if rt_truetype_st.sc_x[j] > kx {
sc_x[j + 1] = sc_x[j] rt_truetype_st.sc_x[j + 1] = rt_truetype_st.sc_x[j]
sc_d[j + 1] = sc_d[j] rt_truetype_st.sc_d[j + 1] = rt_truetype_st.sc_d[j]
j -= 1 j -= 1
} else { } else {
sc_x[j + 1] = kx rt_truetype_st.sc_x[j + 1] = kx
sc_d[j + 1] = kd rt_truetype_st.sc_d[j + 1] = kd
placed = 1 placed = 1
j = -1 j = -1
} }
} }
if placed == 0 { if placed == 0 {
sc_x[0] = kx rt_truetype_st.sc_x[0] = kx
sc_d[0] = kd rt_truetype_st.sc_d[0] = kd
} }
} }
var wind = 0 var wind = 0
let oy = sy / TT_SS let oy = sy / TT_SS
for i in 0 .. m - 1 { for i in 0 .. m - 1 {
wind += sc_d[i] wind += rt_truetype_st.sc_d[i]
if wind != 0 { if wind != 0 {
var xa = sc_x[i] var xa = rt_truetype_st.sc_x[i]
var xb = sc_x[i + 1] var xb = rt_truetype_st.sc_x[i + 1]
if xa < 0.0 { xa = 0.0 } if xa < 0.0 { xa = 0.0 }
if xb > fixed(SW) { xb = fixed(SW) } if xb > fixed(SW) { xb = fixed(SW) }
if xb > xa { if xb > xa {
@ -235,41 +238,40 @@ function gc_hash(font: int, cp: int, px: int) -> int {
return ((((cp * 2654435761) ^ (font << 20)) ^ (px << 8)) & (TT_GC - 1)) return ((((cp * 2654435761) ^ (font << 20)) ^ (px << 8)) & (TT_GC - 1))
} }
function tt_glyph_get(font: int, cp: int, px: int) -> int { function tt_glyph_get(rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, font: int, cp: int, px: int) -> int {
let h = gc_hash(font, cp, px) let h = gc_hash(font, cp, px)
for k in 0 .. 8 { for k in 0 .. 8 {
let s = ((h + k) & (TT_GC - 1)) let s = ((h + k) & (TT_GC - 1))
if gc_used[s] == 1 { if rt_truetype_st.gc_used[s] == 1 {
if gc_font[s] == font { if rt_truetype_st.gc_font[s] == font {
if gc_cp[s] == cp { if rt_truetype_st.gc_cp[s] == cp {
if gc_px[s] == px { return s } if rt_truetype_st.gc_px[s] == px { return s }
} }
} }
} }
} }
let s = h let s = h
if gc_used[s] == 1 { if rt_truetype_st.gc_used[s] == 1 {
let old = gc_bmp[s] let old = rt_truetype_st.gc_bmp[s]
if (old == null) == false { free(old) } if (old == null) == false { free(old) }
} }
let bmp = tt_raster(font, tt_glyph_index(font, cp), px) let bmp = tt_raster(rt_truetype_raster_st, rt_truetype_st, font, tt_glyph_index(rt_truetype_st, font, cp), px)
gc_bmp[s] = bmp rt_truetype_st.gc_bmp[s] = bmp
gc_used[s] = 1 rt_truetype_st.gc_used[s] = 1
gc_font[s] = font rt_truetype_st.gc_font[s] = font
gc_cp[s] = cp rt_truetype_st.gc_cp[s] = cp
gc_px[s] = px rt_truetype_st.gc_px[s] = px
gc_w[s] = gr_w rt_truetype_st.gc_w[s] = rt_truetype_raster_st.gr_w
gc_h[s] = gr_h rt_truetype_st.gc_h[s] = rt_truetype_raster_st.gr_h
gc_ox[s] = gr_ox rt_truetype_st.gc_ox[s] = rt_truetype_raster_st.gr_ox
gc_oy[s] = gr_oy rt_truetype_st.gc_oy[s] = rt_truetype_raster_st.gr_oy
gc_adv[s] = gr_adv rt_truetype_st.gc_adv[s] = rt_truetype_raster_st.gr_adv
return s return s
} }
# ---- UTF-8 ---------------------------------------------------------------- # ---- UTF-8 ----------------------------------------------------------------
var u8_next: int = 0 # byte index just past the codepoint last decoded
function tt_utf8(s: string, at: int) -> int { function tt_utf8(rt_truetype_raster_st: mut RtTruetypeRasterState, s: string, at: int) -> int {
let c = s[at] let c = s[at]
var extra = -1 var extra = -1
if c < 128 { extra = 0 } if c < 128 { extra = 0 }
@ -277,28 +279,28 @@ function tt_utf8(s: string, at: int) -> int {
if (c >> 4) == 14 { extra = 2 } if (c >> 4) == 14 { extra = 2 }
if (c >> 3) == 30 { extra = 3 } if (c >> 3) == 30 { extra = 3 }
if extra < 0 { if extra < 0 {
u8_next = at + 1 rt_truetype_raster_st.u8_next = at + 1
return 65533 return 65533
} }
if extra == 0 { if extra == 0 {
u8_next = at + 1 rt_truetype_raster_st.u8_next = at + 1
return c return c
} }
var cp = (c & (63 >> extra)) var cp = (c & (63 >> extra))
for i in 0 .. extra { for i in 0 .. extra {
let b = s[at + 1 + i] let b = s[at + 1 + i]
if (b & 192) != 128 { if (b & 192) != 128 {
u8_next = at + 1 + i rt_truetype_raster_st.u8_next = at + 1 + i
return 65533 return 65533
} }
cp = ((cp << 6) | (b & 63)) cp = ((cp << 6) | (b & 63))
} }
u8_next = at + 1 + extra rt_truetype_raster_st.u8_next = at + 1 + extra
return cp return cp
} }
# ---- drawing -------------------------------------------------------------- # ---- drawing --------------------------------------------------------------
function tt_blit(bmp: pointer, w: int, h: int, dx: int, dy: int, colour: int) -> void { function tt_blit(rt_core_st: mut RtCoreState, bmp: pointer, w: int, h: int, dx: int, dy: int, colour: int) -> void {
if (bmp == null) { return } if (bmp == null) { return }
let cr = ((colour >> 16) & 255) let cr = ((colour >> 16) & 255)
let cg = ((colour >> 8) & 255) let cg = ((colour >> 8) & 255)
@ -313,11 +315,11 @@ function tt_blit(bmp: pointer, w: int, h: int, dx: int, dy: int, colour: int) ->
let pxx = dx + x let pxx = dx + x
if pxx >= 0 { if pxx >= 0 {
if pxx < rt_fbw { if pxx < rt_fbw {
let d = rt_fb[py * rt_fbw + pxx] let d = rt_core_st.rt_fb[py * rt_fbw + pxx]
let rr = (cr * a + ((d >> 16) & 255) * (255 - a)) / 255 let rr = (cr * a + ((d >> 16) & 255) * (255 - a)) / 255
let rg = (cg * a + ((d >> 8) & 255) * (255 - a)) / 255 let rg = (cg * a + ((d >> 8) & 255) * (255 - a)) / 255
let rb = (cb * a + (d & 255) * (255 - a)) / 255 let rb = (cb * a + (d & 255) * (255 - a)) / 255
rt_fb[py * rt_fbw + pxx] = (((rr << 16) | (rg << 8)) | rb) rt_core_st.rt_fb[py * rt_fbw + pxx] = (((rr << 16) | (rg << 8)) | rb)
} }
} }
} }
@ -327,54 +329,54 @@ function tt_blit(bmp: pointer, w: int, h: int, dx: int, dy: int, colour: int) ->
} }
} }
function rt_text_ttf(font: int, x: int, y: int, s: string, colour: int, px: int) -> void { function rt_text_ttf(rt_core_st: mut RtCoreState, rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, font: int, x: int, y: int, s: string, colour: int, px: int) -> void {
if font < 0 { return } if font < 0 { return }
if font >= tt_n { return } if font >= rt_truetype_st.tt_n { return }
if px <= 0 { return } if px <= 0 { return }
let upem = tt_upem[font] let upem = rt_truetype_st.tt_upem[font]
let scale = fixed(px) / upem let scale = fixed(px) / upem
var baseline = y + floor(fixed(tt_asc[font]) * scale + 0.5) var baseline = y + floor(fixed(rt_truetype_st.tt_asc[font]) * scale + 0.5)
let lineh = floor(fixed(tt_asc[font] - tt_desc[font] + tt_gap[font]) * scale + 0.5) let lineh = floor(fixed(rt_truetype_st.tt_asc[font] - rt_truetype_st.tt_desc[font] + rt_truetype_st.tt_gap[font]) * scale + 0.5)
var penx = x var penx = x
var i = 0 var i = 0
while s[i] != 0 { while s[i] != 0 {
let cp = tt_utf8(s, i) let cp = tt_utf8(rt_truetype_raster_st, s, i)
i = u8_next i = rt_truetype_raster_st.u8_next
if cp == 10 { if cp == 10 {
penx = x penx = x
baseline += lineh baseline += lineh
} else { } else {
let g = tt_glyph_get(font, cp, px) let g = tt_glyph_get(rt_truetype_raster_st, rt_truetype_st, font, cp, px)
tt_blit(gc_bmp[g], gc_w[g], gc_h[g], penx + gc_ox[g], baseline - gc_oy[g], colour) tt_blit(rt_core_st, rt_truetype_st.gc_bmp[g], rt_truetype_st.gc_w[g], rt_truetype_st.gc_h[g], penx + rt_truetype_st.gc_ox[g], baseline - rt_truetype_st.gc_oy[g], colour)
penx += gc_adv[g] penx += rt_truetype_st.gc_adv[g]
} }
} }
} }
function rt_text_w(font: int, s: string, px: int) -> int { function rt_text_w(rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, font: int, s: string, px: int) -> int {
if font < 0 { return 0 } if font < 0 { return 0 }
if font >= tt_n { return 0 } if font >= rt_truetype_st.tt_n { return 0 }
if px <= 0 { return 0 } if px <= 0 { return 0 }
var w = 0 var w = 0
var best = 0 var best = 0
var i = 0 var i = 0
while s[i] != 0 { while s[i] != 0 {
let cp = tt_utf8(s, i) let cp = tt_utf8(rt_truetype_raster_st, s, i)
i = u8_next i = rt_truetype_raster_st.u8_next
if cp == 10 { if cp == 10 {
if w > best { best = w } if w > best { best = w }
w = 0 w = 0
} else { } else {
w += gc_adv[tt_glyph_get(font, cp, px)] w += rt_truetype_st.gc_adv[tt_glyph_get(rt_truetype_raster_st, rt_truetype_st, font, cp, px)]
} }
} }
if w > best { best = w } if w > best { best = w }
return best return best
} }
function rt_text_h(font: int, px: int) -> int { function rt_text_h(rt_truetype_st: RtTruetypeState, font: int, px: int) -> int {
if font < 0 { return 0 } if font < 0 { return 0 }
if font >= tt_n { return 0 } if font >= rt_truetype_st.tt_n { return 0 }
let scale = fixed(px) / tt_upem[font] let scale = fixed(px) / rt_truetype_st.tt_upem[font]
return floor(fixed(tt_asc[font] - tt_desc[font] + tt_gap[font]) * scale + 0.5) return floor(fixed(rt_truetype_st.tt_asc[font] - rt_truetype_st.tt_desc[font] + rt_truetype_st.tt_gap[font]) * scale + 0.5)
} }

View file

@ -24,127 +24,129 @@ const TW_MAX: int = 64 # concurrent handles
const TW_SEGS: int = 8 # segments per handle (chain depth) const TW_SEGS: int = 8 # segments per handle (chain depth)
# per-handle state # per-handle state
var tw_used: words = null # slot in use (1) or free (0) export state RtTweenState {
var tw_seg: words = null # index of the segment currently playing tw_used: words = null # slot in use (1) or free (0)
var tw_nseg: words = null # number of segments queued tw_seg: words = null # index of the segment currently playing
var tw_tick: words = null # ticks elapsed inside the current segment tw_nseg: words = null # number of segments queued
var tw_value: words = null # OUTPUT: the value this frame tw_tick: words = null # ticks elapsed inside the current segment
var tw_done: words = null # OUTPUT: 1 once every segment has finished tw_value: words = null # OUTPUT: the value this frame
tw_done: words = null # OUTPUT: 1 once every segment has finished
tw_kind: words = null # 0 = tween (from->to), 1 = delay (hold)
tw_from: words = null
tw_to: words = null
tw_dur: words = null # segment length in ticks
tw_ease: words = null # 0 linear, 1 in, 2 out, 3 in-out (Motion's curves)
}
# per-segment state (flat: handle * TW_SEGS + seg) # per-segment state (flat: handle * TW_SEGS + seg)
var tw_kind: words = null # 0 = tween (from->to), 1 = delay (hold)
var tw_from: words = null
var tw_to: words = null
var tw_dur: words = null # segment length in ticks
var tw_ease: words = null # 0 linear, 1 in, 2 out, 3 in-out (Motion's curves)
function tw_init() -> void { function tw_init(rt_tween_st: mut RtTweenState) -> void {
if tw_used == null { if rt_tween_st.tw_used == null {
tw_used = words(TW_MAX) rt_tween_st.tw_used = words(TW_MAX)
tw_seg = words(TW_MAX) rt_tween_st.tw_seg = words(TW_MAX)
tw_nseg = words(TW_MAX) rt_tween_st.tw_nseg = words(TW_MAX)
tw_tick = words(TW_MAX) rt_tween_st.tw_tick = words(TW_MAX)
tw_value = words(TW_MAX) rt_tween_st.tw_value = words(TW_MAX)
tw_done = words(TW_MAX) rt_tween_st.tw_done = words(TW_MAX)
tw_kind = words(TW_MAX * TW_SEGS) rt_tween_st.tw_kind = words(TW_MAX * TW_SEGS)
tw_from = words(TW_MAX * TW_SEGS) rt_tween_st.tw_from = words(TW_MAX * TW_SEGS)
tw_to = words(TW_MAX * TW_SEGS) rt_tween_st.tw_to = words(TW_MAX * TW_SEGS)
tw_dur = words(TW_MAX * TW_SEGS) rt_tween_st.tw_dur = words(TW_MAX * TW_SEGS)
tw_ease = words(TW_MAX * TW_SEGS) rt_tween_st.tw_ease = words(TW_MAX * TW_SEGS)
} }
} }
# claim a free handle (a finished or never-used slot). -1 if the pool is full. # claim a free handle (a finished or never-used slot). -1 if the pool is full.
function tw_alloc() -> int { function tw_alloc(rt_tween_st: mut RtTweenState) -> int {
tw_init() tw_init(rt_tween_st)
var i = 0 var i = 0
while i < TW_MAX { while i < TW_MAX {
if tw_used[i] == 0 { return i } if rt_tween_st.tw_used[i] == 0 { return i }
i += 1 i += 1
} }
# none free: reuse the first finished handle so long-lived games don't leak. # none free: reuse the first finished handle so long-lived games don't leak.
i = 0 i = 0
while i < TW_MAX { while i < TW_MAX {
if tw_done[i] != 0 { return i } if rt_tween_st.tw_done[i] != 0 { return i }
i += 1 i += 1
} }
return -1 return -1
} }
# append one segment to a handle (internal). Silently ignored past TW_SEGS. # append one segment to a handle (internal). Silently ignored past TW_SEGS.
function tw_push(h: int, kind: int, from: int, to: int, dur: int, ease: int) -> void { function tw_push(rt_tween_st: mut RtTweenState, h: int, kind: int, from: int, to: int, dur: int, ease: int) -> void {
if (h < 0) or (h >= TW_MAX) { return } if (h < 0) or (h >= TW_MAX) { return }
let n = tw_nseg[h] let n = rt_tween_st.tw_nseg[h]
if n >= TW_SEGS { return } if n >= TW_SEGS { return }
let s = h * TW_SEGS + n let s = h * TW_SEGS + n
tw_kind[s] = kind rt_tween_st.tw_kind[s] = kind
tw_from[s] = from rt_tween_st.tw_from[s] = from
tw_to[s] = to rt_tween_st.tw_to[s] = to
tw_dur[s] = dur rt_tween_st.tw_dur[s] = dur
tw_ease[s] = ease rt_tween_st.tw_ease[s] = ease
tw_nseg[h] = n + 1 rt_tween_st.tw_nseg[h] = n + 1
} }
# start a new tween handle: from -> to over `dur` ticks with easing `ease`. # start a new tween handle: from -> to over `dur` ticks with easing `ease`.
function tween_to(from: int, to: int, dur: int, ease: int) -> int { function tween_to(rt_tween_st: mut RtTweenState, from: int, to: int, dur: int, ease: int) -> int {
let h = tw_alloc() let h = tw_alloc(rt_tween_st)
if h < 0 { return -1 } if h < 0 { return -1 }
tw_used[h] = 1 rt_tween_st.tw_used[h] = 1
tw_seg[h] = 0 rt_tween_st.tw_seg[h] = 0
tw_nseg[h] = 0 rt_tween_st.tw_nseg[h] = 0
tw_tick[h] = 0 rt_tween_st.tw_tick[h] = 0
tw_value[h] = from rt_tween_st.tw_value[h] = from
tw_done[h] = 0 rt_tween_st.tw_done[h] = 0
tw_push(h, 0, from, to, dur, ease) tw_push(rt_tween_st, h, 0, from, to, dur, ease)
return h return h
} }
# chain a tween segment after the handle's current queue: continues from where the # chain a tween segment after the handle's current queue: continues from where the
# previous segment ends, so the game only names the new target. Returns the handle # previous segment ends, so the game only names the new target. Returns the handle
# for fluent chaining. # for fluent chaining.
function tween_chain(h: int, to: int, dur: int, ease: int) -> int { function tween_chain(rt_tween_st: mut RtTweenState, h: int, to: int, dur: int, ease: int) -> int {
if (h < 0) or (h >= TW_MAX) { return h } if (h < 0) or (h >= TW_MAX) { return h }
var from = tw_value[h] var from = rt_tween_st.tw_value[h]
let n = tw_nseg[h] let n = rt_tween_st.tw_nseg[h]
if n > 0 { if n > 0 {
let last = h * TW_SEGS + (n - 1) let last = h * TW_SEGS + (n - 1)
if tw_kind[last] == 0 { from = tw_to[last] } # continue from the previous tween's end if rt_tween_st.tw_kind[last] == 0 { from = rt_tween_st.tw_to[last] } # continue from the previous tween's end
} }
tw_push(h, 0, from, from + (to - from), dur, ease) # `to` is the absolute target tw_push(rt_tween_st, h, 0, from, from + (to - from), dur, ease) # `to` is the absolute target
return h return h
} }
# chain a pause of `ticks` ticks (the value holds). Returns the handle. # chain a pause of `ticks` ticks (the value holds). Returns the handle.
function tween_delay(h: int, ticks: int) -> int { function tween_delay(rt_tween_st: mut RtTweenState, h: int, ticks: int) -> int {
if (h < 0) or (h >= TW_MAX) { return h } if (h < 0) or (h >= TW_MAX) { return h }
tw_push(h, 1, 0, 0, ticks, 0) tw_push(rt_tween_st, h, 1, 0, 0, ticks, 0)
return h return h
} }
# the value of a handle this frame. # the value of a handle this frame.
function tween_value(h: int) -> int { function tween_value(rt_tween_st: RtTweenState, h: int) -> int {
if (h < 0) or (h >= TW_MAX) { return 0 } if (h < 0) or (h >= TW_MAX) { return 0 }
return tw_value[h] return rt_tween_st.tw_value[h]
} }
# has every segment of the handle finished? # has every segment of the handle finished?
function tween_done(h: int) -> bool { function tween_done(rt_tween_st: RtTweenState, h: int) -> bool {
if (h < 0) or (h >= TW_MAX) { return true } if (h < 0) or (h >= TW_MAX) { return true }
return tw_done[h] != 0 return rt_tween_st.tw_done[h] != 0
} }
# are two handles both finished? — the completion of a parallel pair. Independent # are two handles both finished? — the completion of a parallel pair. Independent
# handles advance together each frame, so running several at once *is* parallel; # handles advance together each frame, so running several at once *is* parallel;
# this is the "all done" query over a pair. # this is the "all done" query over a pair.
function tween_parallel(a: int, b: int) -> bool { function tween_parallel(rt_tween_st: RtTweenState, a: int, b: int) -> bool {
return tween_done(a) and tween_done(b) return tween_done(rt_tween_st, a) and tween_done(rt_tween_st, b)
} }
# free a handle immediately (stop and dispose). Its value is frozen where it was. # free a handle immediately (stop and dispose). Its value is frozen where it was.
function tween_stop(h: int) -> void { function tween_stop(rt_tween_st: mut RtTweenState, h: int) -> void {
if (h < 0) or (h >= TW_MAX) { return } if (h < 0) or (h >= TW_MAX) { return }
tw_used[h] = 0 rt_tween_st.tw_used[h] = 0
tw_done[h] = 1 rt_tween_st.tw_done[h] = 1
} }
# floor of a/b for non-negative a (the tick counter only ever rises). # floor of a/b for non-negative a (the tick counter only ever rises).
@ -167,42 +169,42 @@ function tween_ease(t: int, ease: int) -> int {
# advance one active handle by a tick: interpolate within the current segment, # advance one active handle by a tick: interpolate within the current segment,
# roll over to the next when it ends, latch done past the last. # roll over to the next when it ends, latch done past the last.
function tw_advance_one(h: int) -> void { function tw_advance_one(rt_tween_st: mut RtTweenState, h: int) -> void {
if tw_done[h] != 0 { return } if rt_tween_st.tw_done[h] != 0 { return }
let n = tw_nseg[h] let n = rt_tween_st.tw_nseg[h]
if n == 0 { tw_done[h] = 1; return } if n == 0 { rt_tween_st.tw_done[h] = 1; return }
var seg = tw_seg[h] var seg = rt_tween_st.tw_seg[h]
if seg >= n { tw_done[h] = 1; return } if seg >= n { rt_tween_st.tw_done[h] = 1; return }
var tick = tw_tick[h] + 1 var tick = rt_tween_st.tw_tick[h] + 1
let s = h * TW_SEGS + seg let s = h * TW_SEGS + seg
let dur = tw_dur[s] let dur = rt_tween_st.tw_dur[s]
let kind = tw_kind[s] let kind = rt_tween_st.tw_kind[s]
if kind == 0 { # a tween segment if kind == 0 { # a tween segment
var t = 1024 var t = 1024
if dur > 0 { t = tw_div(tick * 1024, dur) } if dur > 0 { t = tw_div(tick * 1024, dur) }
if t > 1024 { t = 1024 } if t > 1024 { t = 1024 }
let te = tween_ease(t, tw_ease[s]) let te = tween_ease(t, rt_tween_st.tw_ease[s])
tw_value[h] = tw_from[s] + (tw_to[s] - tw_from[s]) * te / 1024 rt_tween_st.tw_value[h] = rt_tween_st.tw_from[s] + (rt_tween_st.tw_to[s] - rt_tween_st.tw_from[s]) * te / 1024
} }
# (a delay segment holds tw_value unchanged.) # (a delay segment holds tw_value unchanged.)
if tick >= dur { # this segment finished: advance if tick >= dur { # this segment finished: advance
if kind == 0 { tw_value[h] = tw_to[s] } # rest exactly on the target if kind == 0 { rt_tween_st.tw_value[h] = rt_tween_st.tw_to[s] } # rest exactly on the target
seg += 1 seg += 1
tw_seg[h] = seg rt_tween_st.tw_seg[h] = seg
tw_tick[h] = 0 rt_tween_st.tw_tick[h] = 0
if seg >= n { tw_done[h] = 1 } if seg >= n { rt_tween_st.tw_done[h] = 1 }
} else { } else {
tw_tick[h] = tick rt_tween_st.tw_tick[h] = tick
} }
} }
# the engine-owned system: advance every active tween handle one tick. Inserted # the engine-owned system: advance every active tween handle one tick. Inserted
# into the Update phase when a game uses Tween.to (emit_game.ludic). # into the Update phase when a game uses Tween.to (emit_game.ludic).
function esys_tween() -> void { function esys_tween(rt_tween_st: mut RtTweenState) -> void {
if tw_used == null { return } if rt_tween_st.tw_used == null { return }
var i = 0 var i = 0
while i < TW_MAX { while i < TW_MAX {
if (tw_used[i] != 0) and (tw_done[i] == 0) { tw_advance_one(i) } if (rt_tween_st.tw_used[i] != 0) and (rt_tween_st.tw_done[i] == 0) { tw_advance_one(rt_tween_st, i) }
i += 1 i += 1
} }
} }

View file

@ -31,24 +31,26 @@ extern function lu_udp_local_ip() -> int = "lu_udp_local_ip"
const UDP_HANDLES: int = 16 const UDP_HANDLES: int = 16
var u_ready: bool = false export state RtUdpState {
var u_ipport: pointer = null # the native side writes the sender here (two i32s) u_ready: bool = false
var u_from_ip: words = null # per handle: the sender of the last datagram read u_ipport: pointer = null # the native side writes the sender here (two i32s)
var u_from_port: words = null u_from_ip: words = null # per handle: the sender of the last datagram read
u_from_port: words = null
}
function udp_init() -> void { function udp_init(rt_udp_st: mut RtUdpState) -> void {
if u_ready { return } if rt_udp_st.u_ready { return }
u_ipport = bytes(8) rt_udp_st.u_ipport = bytes(8)
u_from_ip = words(UDP_HANDLES + 1) rt_udp_st.u_from_ip = words(UDP_HANDLES + 1)
u_from_port = words(UDP_HANDLES + 1) rt_udp_st.u_from_port = words(UDP_HANDLES + 1)
var i = 0 var i = 0
while i <= UDP_HANDLES { u_from_ip[i] = 0; u_from_port[i] = 0; i += 1 } while i <= UDP_HANDLES { rt_udp_st.u_from_ip[i] = 0; rt_udp_st.u_from_port[i] = 0; i += 1 }
u_ready = true rt_udp_st.u_ready = true
} }
# a socket bound to `port` on every interface (0 = any free port): a handle, or 0 # a socket bound to `port` on every interface (0 = any free port): a handle, or 0
function udp_open(port: int) -> int { function udp_open(rt_udp_st: mut RtUdpState, port: int) -> int {
udp_init() udp_init(rt_udp_st)
if (port < 0) or (port > 65535) { return 0 } if (port < 0) or (port > 65535) { return 0 }
return lu_udp_open(port) return lu_udp_open(port)
} }
@ -60,27 +62,27 @@ function udp_send(h: int, ip: int, port: int, buf: []byte, n: int) -> int {
return lu_udp_send(h, ip, port, buf, n) return lu_udp_send(h, ip, port, buf, n)
} }
# the next waiting datagram, up to `cap` bytes, into `buf`: its length, or 0 when none waits # the next waiting datagram, up to `cap` bytes, into `buf`: its length, or 0 when none waits
function udp_recv(h: int, buf: []byte, cap0: int) -> int { function udp_recv(rt_udp_st: mut RtUdpState, h: int, buf: []byte, cap0: int) -> int {
udp_init() udp_init(rt_udp_st)
var cap = cap0 var cap = cap0
if buf != null and cap > len(buf) { cap = len(buf) } if buf != null and cap > len(buf) { cap = len(buf) }
if (buf == null) or (cap <= 0) { return 0 } if (buf == null) or (cap <= 0) { return 0 }
let n = lu_udp_recv(h, buf, cap, u_ipport) let n = lu_udp_recv(h, buf, cap, rt_udp_st.u_ipport)
if (n > 0) and (h >= 1) and (h <= UDP_HANDLES) { if (n > 0) and (h >= 1) and (h <= UDP_HANDLES) {
u_from_ip[h] = udp_le32(u_ipport, 0) rt_udp_st.u_from_ip[h] = udp_le32(rt_udp_st.u_ipport, 0)
u_from_port[h] = udp_le32(u_ipport, 4) rt_udp_st.u_from_port[h] = udp_le32(rt_udp_st.u_ipport, 4)
} }
return n return n
} }
function udp_from_ip(h: int) -> int { function udp_from_ip(rt_udp_st: mut RtUdpState, h: int) -> int {
udp_init() udp_init(rt_udp_st)
if (h < 1) or (h > UDP_HANDLES) { return 0 } if (h < 1) or (h > UDP_HANDLES) { return 0 }
return u_from_ip[h] return rt_udp_st.u_from_ip[h]
} }
function udp_from_port(h: int) -> int { function udp_from_port(rt_udp_st: mut RtUdpState, h: int) -> int {
udp_init() udp_init(rt_udp_st)
if (h < 1) or (h > UDP_HANDLES) { return 0 } if (h < 1) or (h > UDP_HANDLES) { return 0 }
return u_from_port[h] return rt_udp_st.u_from_port[h]
} }
function udp_close(h: int) -> void { lu_udp_close(h) } function udp_close(h: int) -> void { lu_udp_close(h) }
function udp_resolve(name: pointer) -> int { function udp_resolve(name: pointer) -> int {

View file

@ -45,165 +45,167 @@ const K_VISIBLE: int = 18
const K_IMG: int = 19 const K_IMG: int = 19
const K_SKIN: int = 20 const K_SKIN: int = 20
var ui_type: words = null export state RtUiState {
var ui_parent: words = null ui_type: words = null
var ui_w: words = null ui_parent: words = null
var ui_h: words = null ui_w: words = null
var ui_x: words = null ui_h: words = null
var ui_y: words = null ui_x: words = null
var ui_haspos: words = null ui_y: words = null
var ui_pad: words = null ui_haspos: words = null
var ui_gap: words = null ui_pad: words = null
var ui_bg: words = null ui_gap: words = null
var ui_fg: words = null ui_bg: words = null
var ui_border: words = null ui_fg: words = null
var ui_align: words = null ui_border: words = null
var ui_grow: words = null ui_align: words = null
var ui_font: words = null ui_grow: words = null
var ui_size: words = null ui_font: words = null
var ui_skin: words = null ui_size: words = null
var ui_inset: words = null ui_skin: words = null
var ui_img: words = null ui_inset: words = null
var ui_focusable: words = null ui_img: words = null
var ui_rx: words = null ui_focusable: words = null
var ui_ry: words = null ui_rx: words = null
var ui_rw: words = null ui_ry: words = null
var ui_rh: words = null ui_rw: words = null
var ui_visible: words = null ui_rh: words = null
var ui_fired: words = null ui_visible: words = null
var ui_hasdyn: words = null ui_fired: words = null
var ui_dyn: pointer = null # UI_MAX * 96 bytes ui_hasdyn: words = null
var ui_text: pointers = null # UI_MAX static string pointers ui_dyn: pointer = null # UI_MAX * 96 bytes
ui_text: pointers = null # UI_MAX static string pointers
ui_n: int = 0
ui_active: int = -1
ui_focus: int = -1
}
var ui_n: int = 0
var ui_active: int = -1
var ui_focus: int = -1
function rt_ui_init() -> void { function rt_ui_init(rt_ui_st: mut RtUiState) -> void {
ui_type = words(UI_MAX) rt_ui_st.ui_type = words(UI_MAX)
ui_parent = words(UI_MAX) rt_ui_st.ui_parent = words(UI_MAX)
ui_w = words(UI_MAX) rt_ui_st.ui_w = words(UI_MAX)
ui_h = words(UI_MAX) rt_ui_st.ui_h = words(UI_MAX)
ui_x = words(UI_MAX) rt_ui_st.ui_x = words(UI_MAX)
ui_y = words(UI_MAX) rt_ui_st.ui_y = words(UI_MAX)
ui_haspos = words(UI_MAX) rt_ui_st.ui_haspos = words(UI_MAX)
ui_pad = words(UI_MAX) rt_ui_st.ui_pad = words(UI_MAX)
ui_gap = words(UI_MAX) rt_ui_st.ui_gap = words(UI_MAX)
ui_bg = words(UI_MAX) rt_ui_st.ui_bg = words(UI_MAX)
ui_fg = words(UI_MAX) rt_ui_st.ui_fg = words(UI_MAX)
ui_border = words(UI_MAX) rt_ui_st.ui_border = words(UI_MAX)
ui_align = words(UI_MAX) rt_ui_st.ui_align = words(UI_MAX)
ui_grow = words(UI_MAX) rt_ui_st.ui_grow = words(UI_MAX)
ui_font = words(UI_MAX) rt_ui_st.ui_font = words(UI_MAX)
ui_size = words(UI_MAX) rt_ui_st.ui_size = words(UI_MAX)
ui_skin = words(UI_MAX) rt_ui_st.ui_skin = words(UI_MAX)
ui_inset = words(UI_MAX) rt_ui_st.ui_inset = words(UI_MAX)
ui_img = words(UI_MAX) rt_ui_st.ui_img = words(UI_MAX)
ui_focusable = words(UI_MAX) rt_ui_st.ui_focusable = words(UI_MAX)
ui_rx = words(UI_MAX) rt_ui_st.ui_rx = words(UI_MAX)
ui_ry = words(UI_MAX) rt_ui_st.ui_ry = words(UI_MAX)
ui_rw = words(UI_MAX) rt_ui_st.ui_rw = words(UI_MAX)
ui_rh = words(UI_MAX) rt_ui_st.ui_rh = words(UI_MAX)
ui_visible = words(UI_MAX) rt_ui_st.ui_visible = words(UI_MAX)
ui_fired = words(UI_MAX) rt_ui_st.ui_fired = words(UI_MAX)
ui_hasdyn = words(UI_MAX) rt_ui_st.ui_hasdyn = words(UI_MAX)
ui_dyn = bytes(UI_MAX * 96) rt_ui_st.ui_dyn = bytes(UI_MAX * 96)
ui_text = pointers(UI_MAX) rt_ui_st.ui_text = pointers(UI_MAX)
} }
# ---- build-time interface (called by compiler-emitted code) --------------- # ---- build-time interface (called by compiler-emitted code) ---------------
function rt_ui_reset(n: int) -> void { function rt_ui_reset(rt_ui_st: mut RtUiState, n: int) -> void {
ui_n = n rt_ui_st.ui_n = n
for i in 0 .. n { for i in 0 .. n {
ui_type[i] = 0 rt_ui_st.ui_type[i] = 0
ui_parent[i] = -1 rt_ui_st.ui_parent[i] = -1
ui_w[i] = 0 rt_ui_st.ui_w[i] = 0
ui_h[i] = 0 rt_ui_st.ui_h[i] = 0
ui_x[i] = 0 rt_ui_st.ui_x[i] = 0
ui_y[i] = 0 rt_ui_st.ui_y[i] = 0
ui_haspos[i] = 0 rt_ui_st.ui_haspos[i] = 0
ui_pad[i] = 0 rt_ui_st.ui_pad[i] = 0
ui_gap[i] = 0 rt_ui_st.ui_gap[i] = 0
ui_bg[i] = -1 rt_ui_st.ui_bg[i] = -1
ui_fg[i] = -1 rt_ui_st.ui_fg[i] = -1
ui_border[i] = -1 rt_ui_st.ui_border[i] = -1
ui_align[i] = -1 rt_ui_st.ui_align[i] = -1
ui_grow[i] = 0 rt_ui_st.ui_grow[i] = 0
ui_font[i] = -1 rt_ui_st.ui_font[i] = -1
ui_size[i] = 8 rt_ui_st.ui_size[i] = 8
ui_skin[i] = -1 rt_ui_st.ui_skin[i] = -1
ui_inset[i] = 6 rt_ui_st.ui_inset[i] = 6
ui_img[i] = -1 rt_ui_st.ui_img[i] = -1
ui_focusable[i] = 0 rt_ui_st.ui_focusable[i] = 0
ui_visible[i] = 1 rt_ui_st.ui_visible[i] = 1
ui_fired[i] = 0 rt_ui_st.ui_fired[i] = 0
ui_hasdyn[i] = 0 rt_ui_st.ui_hasdyn[i] = 0
ui_text[i] = null rt_ui_st.ui_text[i] = null
} }
} }
function rt_ui_set(i: int, k: int, v: int) -> void { function rt_ui_set(rt_ui_st: mut RtUiState, i: int, k: int, v: int) -> void {
if i < 0 { return } if i < 0 { return }
if i >= UI_MAX { return } if i >= UI_MAX { return }
if k == K_TYPE { ui_type[i] = v } if k == K_TYPE { rt_ui_st.ui_type[i] = v }
if k == K_PARENT { ui_parent[i] = v } if k == K_PARENT { rt_ui_st.ui_parent[i] = v }
if k == K_W { ui_w[i] = v } if k == K_W { rt_ui_st.ui_w[i] = v }
if k == K_H { ui_h[i] = v } if k == K_H { rt_ui_st.ui_h[i] = v }
if k == K_X { ui_x[i] = v } if k == K_X { rt_ui_st.ui_x[i] = v }
if k == K_Y { ui_y[i] = v } if k == K_Y { rt_ui_st.ui_y[i] = v }
if k == K_HASPOS { ui_haspos[i] = v } if k == K_HASPOS { rt_ui_st.ui_haspos[i] = v }
if k == K_PAD { ui_pad[i] = v } if k == K_PAD { rt_ui_st.ui_pad[i] = v }
if k == K_GAP { ui_gap[i] = v } if k == K_GAP { rt_ui_st.ui_gap[i] = v }
if k == K_BG { ui_bg[i] = v } if k == K_BG { rt_ui_st.ui_bg[i] = v }
if k == K_FG { ui_fg[i] = v } if k == K_FG { rt_ui_st.ui_fg[i] = v }
if k == K_BORDER { ui_border[i] = v } if k == K_BORDER { rt_ui_st.ui_border[i] = v }
if k == K_ALIGN { ui_align[i] = v } if k == K_ALIGN { rt_ui_st.ui_align[i] = v }
if k == K_GROW { ui_grow[i] = v } if k == K_GROW { rt_ui_st.ui_grow[i] = v }
if k == K_FONT { ui_font[i] = v } if k == K_FONT { rt_ui_st.ui_font[i] = v }
if k == K_SIZE { ui_size[i] = v } if k == K_SIZE { rt_ui_st.ui_size[i] = v }
if k == K_SKININSET { ui_inset[i] = v } if k == K_SKININSET { rt_ui_st.ui_inset[i] = v }
if k == K_FOCUSABLE { ui_focusable[i] = v } if k == K_FOCUSABLE { rt_ui_st.ui_focusable[i] = v }
if k == K_VISIBLE { ui_visible[i] = v } if k == K_VISIBLE { rt_ui_st.ui_visible[i] = v }
if k == K_IMG { ui_img[i] = v } if k == K_IMG { rt_ui_st.ui_img[i] = v }
if k == K_SKIN { ui_skin[i] = v } if k == K_SKIN { rt_ui_st.ui_skin[i] = v }
} }
function rt_ui_static_text(i: int, s: string) -> void { function rt_ui_static_text(rt_ui_st: mut RtUiState, i: int, s: string) -> void {
if i < 0 { return } if i < 0 { return }
if i >= UI_MAX { return } if i >= UI_MAX { return }
ui_text[i] = s rt_ui_st.ui_text[i] = s
} }
# ---- text helpers: TrueType when a font is loaded, bitmap otherwise ------- # ---- text helpers: TrueType when a font is loaded, bitmap otherwise -------
function ui_str(i: int) -> pointer { function ui_str(rt_ui_st: RtUiState, i: int) -> pointer {
if ui_hasdyn[i] == 1 { return offset(ui_dyn, i * 96) } if rt_ui_st.ui_hasdyn[i] == 1 { return offset(rt_ui_st.ui_dyn, i * 96) }
let t = ui_text[i] let t = rt_ui_st.ui_text[i]
if (t == null) { return offset(ui_dyn, i * 96) } if (t == null) { return offset(rt_ui_st.ui_dyn, i * 96) }
return t return t
} }
function ui_draw_text(font: int, x: int, y: int, s: pointer, colour: int, size: int) -> void { function ui_draw_text(rt_core_st: mut RtCoreState, rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, font: int, x: int, y: int, s: pointer, colour: int, size: int) -> void {
if font >= 0 { if font >= 0 {
if font < tt_n { if font < rt_truetype_st.tt_n {
rt_text_ttf(font, x, y, s, colour, size) rt_text_ttf(rt_core_st, rt_truetype_raster_st, rt_truetype_st, font, x, y, s, colour, size)
return return
} }
} }
rt_text(x, y, s, colour, max(size / 8, 1)) rt_text(rt_core_st, x, y, s, colour, max(size / 8, 1))
} }
function ui_tw(font: int, s: pointer, size: int) -> int { function ui_tw(rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, font: int, s: pointer, size: int) -> int {
if font >= 0 { if font >= 0 {
if font < tt_n { return rt_text_w(font, s, size) } if font < rt_truetype_st.tt_n { return rt_text_w(rt_truetype_raster_st, rt_truetype_st, font, s, size) }
} }
var n = 0 var n = 0
while s[n] != 0 { n += 1 } while s[n] != 0 { n += 1 }
return n * 6 * max(size / 8, 1) return n * 6 * max(size / 8, 1)
} }
function ui_th(font: int, size: int) -> int { function ui_th(rt_truetype_st: RtTruetypeState, font: int, size: int) -> int {
if font >= 0 { if font >= 0 {
if font < tt_n { return rt_text_h(font, size) } if font < rt_truetype_st.tt_n { return rt_text_h(rt_truetype_st, font, size) }
} }
return 7 * max(size / 8, 1) return 7 * max(size / 8, 1)
} }
@ -218,68 +220,68 @@ function ui_dir(t: int) -> int {
if t == WT_ROW { return 0 } if t == WT_ROW { return 0 }
return 1 return 1
} }
function ui_under(i: int, root: int) -> bool { function ui_under(rt_ui_st: RtUiState, i: int, root: int) -> bool {
var k = i var k = i
while k >= 0 { while k >= 0 {
if k == root { return true } if k == root { return true }
k = ui_parent[k] k = rt_ui_st.ui_parent[k]
} }
return false return false
} }
# ---- measure: content sizes, children before parents ---------------------- # ---- measure: content sizes, children before parents ----------------------
function ui_measure() -> void { function ui_measure(rt_image_st: RtImageState, rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, rt_ui_st: mut RtUiState) -> void {
var i = ui_n - 1 var i = rt_ui_st.ui_n - 1
while i >= 0 { while i >= 0 {
if ui_visible[i] == 0 { if rt_ui_st.ui_visible[i] == 0 {
ui_rw[i] = 0 rt_ui_st.ui_rw[i] = 0
ui_rh[i] = 0 rt_ui_st.ui_rh[i] = 0
} else { } else {
let t = ui_type[i] let t = rt_ui_st.ui_type[i]
let pad = ui_pad[i] let pad = rt_ui_st.ui_pad[i]
var cw = 0 var cw = 0
var ch = 0 var ch = 0
if t == WT_LABEL { cw = -1 } if t == WT_LABEL { cw = -1 }
if t == WT_BUTTON { cw = -1 } if t == WT_BUTTON { cw = -1 }
if cw == -1 { if cw == -1 {
let s = ui_str(i) let s = ui_str(rt_ui_st, i)
cw = ui_tw(ui_font[i], s, ui_size[i]) + 2 * pad cw = ui_tw(rt_truetype_raster_st, rt_truetype_st, rt_ui_st.ui_font[i], s, rt_ui_st.ui_size[i]) + 2 * pad
ch = ui_th(ui_font[i], ui_size[i]) + 2 * pad ch = ui_th(rt_truetype_st, rt_ui_st.ui_font[i], rt_ui_st.ui_size[i]) + 2 * pad
if t == WT_BUTTON { if t == WT_BUTTON {
cw += 10 cw += 10
ch += 6 ch += 6
} }
} else { } else {
if t == WT_IMAGE { if t == WT_IMAGE {
let im = ui_img[i] let im = rt_ui_st.ui_img[i]
if im >= 0 { if im >= 0 {
cw = img_w[im] cw = rt_image_st.img_w[im]
ch = img_h[im] ch = rt_image_st.img_h[im]
} }
} else { } else {
if t == WT_SPACER { if t == WT_SPACER {
cw = ui_w[i] cw = rt_ui_st.ui_w[i]
ch = ui_h[i] ch = rt_ui_st.ui_h[i]
} else { } else {
let dir = ui_dir(t) let dir = ui_dir(t)
var mainsz = 0 var mainsz = 0
var cross = 0 var cross = 0
var nc = 0 var nc = 0
for c in 0 .. ui_n { for c in 0 .. rt_ui_st.ui_n {
if ui_parent[c] == i { if rt_ui_st.ui_parent[c] == i {
if ui_visible[c] == 1 { if rt_ui_st.ui_visible[c] == 1 {
if dir == 1 { if dir == 1 {
mainsz += ui_rh[c] mainsz += rt_ui_st.ui_rh[c]
cross = max(cross, ui_rw[c]) cross = max(cross, rt_ui_st.ui_rw[c])
} else { } else {
mainsz += ui_rw[c] mainsz += rt_ui_st.ui_rw[c]
cross = max(cross, ui_rh[c]) cross = max(cross, rt_ui_st.ui_rh[c])
} }
nc += 1 nc += 1
} }
} }
} }
if nc > 1 { mainsz = mainsz + ui_gap[i] * (nc - 1) } if nc > 1 { mainsz = mainsz + rt_ui_st.ui_gap[i] * (nc - 1) }
mainsz = mainsz + 2 * pad mainsz = mainsz + 2 * pad
cross = cross + 2 * pad cross = cross + 2 * pad
if dir == 1 { if dir == 1 {
@ -292,20 +294,20 @@ function ui_measure() -> void {
} }
} }
} }
var rw = ui_w[i] var rw = rt_ui_st.ui_w[i]
var rh = ui_h[i] var rh = rt_ui_st.ui_h[i]
if rw == 0 { rw = cw } if rw == 0 { rw = cw }
if rh == 0 { rh = ch } if rh == 0 { rh = ch }
ui_rw[i] = rw rt_ui_st.ui_rw[i] = rw
ui_rh[i] = rh rt_ui_st.ui_rh[i] = rh
} }
i -= 1 i -= 1
} }
} }
function ui_eff_align(child: int, parent: int) -> int { function ui_eff_align(rt_ui_st: RtUiState, child: int, parent: int) -> int {
if ui_align[child] >= 0 { return ui_align[child] } if rt_ui_st.ui_align[child] >= 0 { return rt_ui_st.ui_align[child] }
if ui_align[parent] >= 0 { return ui_align[parent] } if rt_ui_st.ui_align[parent] >= 0 { return rt_ui_st.ui_align[parent] }
return 0 return 0
} }

View file

@ -2,25 +2,25 @@
# accessors. Split out of ui.ludic (the storage/build/measure half). # accessors. Split out of ui.ludic (the storage/build/measure half).
# ---- arrange: rects, parents before children ------------------------------ # ---- arrange: rects, parents before children ------------------------------
function ui_arrange(i: int, x: int, y: int) -> void { function ui_arrange(rt_ui_st: mut RtUiState, i: int, x: int, y: int) -> void {
ui_rx[i] = x rt_ui_st.ui_rx[i] = x
ui_ry[i] = y rt_ui_st.ui_ry[i] = y
let t = ui_type[i] let t = rt_ui_st.ui_type[i]
if ui_is_container(t) == false { return } if ui_is_container(t) == false { return }
let dir = ui_dir(t) let dir = ui_dir(t)
let pad = ui_pad[i] let pad = rt_ui_st.ui_pad[i]
let gap = ui_gap[i] let gap = rt_ui_st.ui_gap[i]
let innerw = ui_rw[i] - 2 * pad let innerw = rt_ui_st.ui_rw[i] - 2 * pad
let innerh = ui_rh[i] - 2 * pad let innerh = rt_ui_st.ui_rh[i] - 2 * pad
var total = 0 var total = 0
var ng = 0 var ng = 0
var grows = 0 var grows = 0
for c in 0 .. ui_n { for c in 0 .. rt_ui_st.ui_n {
if ui_parent[c] == i { if rt_ui_st.ui_parent[c] == i {
if ui_visible[c] == 1 { if rt_ui_st.ui_visible[c] == 1 {
if dir == 1 { total += ui_rh[c] } else { total += ui_rw[c] } if dir == 1 { total += rt_ui_st.ui_rh[c] } else { total += rt_ui_st.ui_rw[c] }
ng += 1 ng += 1
grows += ui_grow[c] grows += rt_ui_st.ui_grow[c]
} }
} }
} }
@ -30,30 +30,30 @@ function ui_arrange(i: int, x: int, y: int) -> void {
let extra = max(avail - total, 0) let extra = max(avail - total, 0)
var cx = x + pad var cx = x + pad
var cy = y + pad var cy = y + pad
for c in 0 .. ui_n { for c in 0 .. rt_ui_st.ui_n {
if ui_parent[c] == i { if rt_ui_st.ui_parent[c] == i {
if ui_visible[c] == 1 { if rt_ui_st.ui_visible[c] == 1 {
var kw = ui_rw[c] var kw = rt_ui_st.ui_rw[c]
var kh = ui_rh[c] var kh = rt_ui_st.ui_rh[c]
if ui_grow[c] != 0 { if rt_ui_st.ui_grow[c] != 0 {
if grows > 0 { if grows > 0 {
if dir == 1 { kh = kh + extra / grows } else { kw = kw + extra / grows } if dir == 1 { kh = kh + extra / grows } else { kw = kw + extra / grows }
} }
} }
let al = ui_eff_align(c, i) let al = ui_eff_align(rt_ui_st, c, i)
ui_rw[c] = kw rt_ui_st.ui_rw[c] = kw
ui_rh[c] = kh rt_ui_st.ui_rh[c] = kh
if dir == 1 { if dir == 1 {
var ax = x + pad var ax = x + pad
if al == 1 { ax = x + pad + (innerw - kw) / 2 } if al == 1 { ax = x + pad + (innerw - kw) / 2 }
if al == 2 { ax = x + pad + (innerw - kw) } if al == 2 { ax = x + pad + (innerw - kw) }
ui_arrange(c, ax, cy) ui_arrange(rt_ui_st, c, ax, cy)
cy = cy + kh + gap cy = cy + kh + gap
} else { } else {
var ay = y + pad var ay = y + pad
if al == 1 { ay = y + pad + (innerh - kh) / 2 } if al == 1 { ay = y + pad + (innerh - kh) / 2 }
if al == 2 { ay = y + pad + (innerh - kh) } if al == 2 { ay = y + pad + (innerh - kh) }
ui_arrange(c, cx, ay) ui_arrange(rt_ui_st, c, cx, ay)
cx = cx + kw + gap cx = cx + kw + gap
} }
} }
@ -61,16 +61,16 @@ function ui_arrange(i: int, x: int, y: int) -> void {
} }
} }
function ui_layout() -> void { function ui_layout(rt_image_st: RtImageState, rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, rt_ui_st: mut RtUiState) -> void {
if ui_active < 0 { return } if rt_ui_st.ui_active < 0 { return }
ui_measure() ui_measure(rt_image_st, rt_truetype_raster_st, rt_truetype_st, rt_ui_st)
var px = (rt_fbw - ui_rw[ui_active]) / 2 var px = (rt_fbw - rt_ui_st.ui_rw[rt_ui_st.ui_active]) / 2
var py = (rt_fbh - ui_rh[ui_active]) / 2 var py = (rt_fbh - rt_ui_st.ui_rh[rt_ui_st.ui_active]) / 2
if ui_haspos[ui_active] == 1 { if rt_ui_st.ui_haspos[rt_ui_st.ui_active] == 1 {
px = ui_x[ui_active] px = rt_ui_st.ui_x[rt_ui_st.ui_active]
py = ui_y[ui_active] py = rt_ui_st.ui_y[rt_ui_st.ui_active]
} }
ui_arrange(ui_active, px, py) ui_arrange(rt_ui_st, rt_ui_st.ui_active, px, py)
} }
# ---- drawing -------------------------------------------------------------- # ---- drawing --------------------------------------------------------------
@ -84,78 +84,80 @@ function ui_lighten(c: int) -> int {
return (((r << 16) | (g << 8)) | b) return (((r << 16) | (g << 8)) | b)
} }
function ui_draw_node(i: int) -> void { function ui_draw_node(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, rt_ui_st: RtUiState, i: int) -> void {
if ui_visible[i] == 0 { return } if rt_ui_st.ui_visible[i] == 0 { return }
let t = ui_type[i] let t = rt_ui_st.ui_type[i]
let rx = ui_rx[i] let rx = rt_ui_st.ui_rx[i]
let ry = ui_ry[i] let ry = rt_ui_st.ui_ry[i]
let rw = ui_rw[i] let rw = rt_ui_st.ui_rw[i]
let rh = ui_rh[i] let rh = rt_ui_st.ui_rh[i]
let skin = ui_skin[i] let skin = rt_ui_st.ui_skin[i]
var focused = 0 var focused = 0
if i == ui_focus { focused = 1 } if i == rt_ui_st.ui_focus { focused = 1 }
if t == WT_PANEL { if t == WT_PANEL {
if skin >= 0 { if skin >= 0 {
rt_draw_9slice(skin, rx, ry, rw, rh, ui_inset[i]) rt_draw_9slice(rt_core_st, rt_image_st, skin, rx, ry, rw, rh, rt_ui_st.ui_inset[i])
} else { } else {
if ui_bg[i] >= 0 { rt_fill_rect(rx, ry, rw, rh, ui_bg[i]) } if rt_ui_st.ui_bg[i] >= 0 { rt_fill_rect(rt_core_st, rx, ry, rw, rh, rt_ui_st.ui_bg[i]) }
if ui_border[i] >= 0 { rt_frame_rect(rx, ry, rw, rh, ui_border[i]) } if rt_ui_st.ui_border[i] >= 0 { rt_frame_rect(rt_core_st, rx, ry, rw, rh, rt_ui_st.ui_border[i]) }
} }
} }
if t == WT_BUTTON { if t == WT_BUTTON {
var bg = ui_bg[i] var bg = rt_ui_st.ui_bg[i]
if bg < 0 { bg = 0x2a2a3a } if bg < 0 { bg = 0x2a2a3a }
if focused == 1 { bg = ui_lighten(bg) } if focused == 1 { bg = ui_lighten(bg) }
if skin >= 0 { if skin >= 0 {
rt_draw_9slice(skin, rx, ry, rw, rh, ui_inset[i]) rt_draw_9slice(rt_core_st, rt_image_st, skin, rx, ry, rw, rh, rt_ui_st.ui_inset[i])
} else { } else {
rt_fill_rect(rx, ry, rw, rh, bg) rt_fill_rect(rt_core_st, rx, ry, rw, rh, bg)
} }
var bc = ui_border[i] var bc = rt_ui_st.ui_border[i]
if bc < 0 { bc = 0x555577 } if bc < 0 { bc = 0x555577 }
if focused == 1 { bc = 0xffff00 } if focused == 1 { bc = 0xffff00 }
rt_frame_rect(rx, ry, rw, rh, bc) rt_frame_rect(rt_core_st, rx, ry, rw, rh, bc)
let s = ui_str(i) let s = ui_str(rt_ui_st, i)
var fg = ui_fg[i] var fg = rt_ui_st.ui_fg[i]
if fg < 0 { fg = 0xffffff } if fg < 0 { fg = 0xffffff }
let font = ui_font[i] let font = rt_ui_st.ui_font[i]
let size = ui_size[i] let size = rt_ui_st.ui_size[i]
ui_draw_text(font, rx + (rw - ui_tw(font, s, size)) / 2, ry + (rh - ui_th(font, size)) / 2, s, fg, size) ui_draw_text(rt_core_st, rt_truetype_raster_st, rt_truetype_st, font, rx + (rw - ui_tw(rt_truetype_raster_st, rt_truetype_st, font, s, size)) / 2, ry + (rh - ui_th(rt_truetype_st, font, size)) / 2, s, fg, size)
} }
if t == WT_LABEL { if t == WT_LABEL {
let s = ui_str(i) let s = ui_str(rt_ui_st, i)
var fg = ui_fg[i] var fg = rt_ui_st.ui_fg[i]
if fg < 0 { fg = 0xffffff } if fg < 0 { fg = 0xffffff }
let font = ui_font[i] let font = rt_ui_st.ui_font[i]
let size = ui_size[i] let size = rt_ui_st.ui_size[i]
let pad = ui_pad[i] let pad = rt_ui_st.ui_pad[i]
let tw = ui_tw(font, s, size) let tw = ui_tw(rt_truetype_raster_st, rt_truetype_st, font, s, size)
var tx = rx + pad var tx = rx + pad
let al = ui_align[i] let al = rt_ui_st.ui_align[i]
if al == 1 { tx = rx + (rw - tw) / 2 } if al == 1 { tx = rx + (rw - tw) / 2 }
if al == 2 { tx = rx + rw - pad - tw } if al == 2 { tx = rx + rw - pad - tw }
ui_draw_text(font, tx, ry + pad, s, fg, size) ui_draw_text(rt_core_st, rt_truetype_raster_st, rt_truetype_st, font, tx, ry + pad, s, fg, size)
} }
if t == WT_IMAGE { if t == WT_IMAGE {
if ui_img[i] >= 0 { rt_draw_image_scaled(ui_img[i], rx, ry, rw, rh) } if rt_ui_st.ui_img[i] >= 0 { rt_draw_image_scaled(rt_core_st, rt_image_st, rt_ui_st.ui_img[i], rx, ry, rw, rh) }
} }
for c in 0 .. ui_n { for c in 0 .. rt_ui_st.ui_n {
if ui_parent[c] == i { ui_draw_node(c) } if rt_ui_st.ui_parent[c] == i { ui_draw_node(rt_core_st, rt_image_st, rt_truetype_raster_st, rt_truetype_st, rt_ui_st, c) }
} }
} }
# ---- focus and activation ------------------------------------------------- # ---- focus and activation -------------------------------------------------
var ui_fl: words = null export state RtUiDrawState {
ui_fl: words = null
}
function ui_focusables() -> int { function ui_focusables(rt_ui_draw_st: mut RtUiDrawState, rt_ui_st: RtUiState) -> int {
if (ui_fl == null) { ui_fl = words(UI_MAX) } if (rt_ui_draw_st.ui_fl == null) { rt_ui_draw_st.ui_fl = words(UI_MAX) }
var n = 0 var n = 0
for i in 0 .. ui_n { for i in 0 .. rt_ui_st.ui_n {
if ui_focusable[i] == 1 { if rt_ui_st.ui_focusable[i] == 1 {
if ui_visible[i] == 1 { if rt_ui_st.ui_visible[i] == 1 {
if ui_under(i, ui_active) { if ui_under(rt_ui_st, i, rt_ui_st.ui_active) {
ui_fl[n] = i rt_ui_draw_st.ui_fl[n] = i
n += 1 n += 1
} }
} }
@ -169,91 +171,91 @@ function ui_focusables() -> int {
event UiClicked { id: int = 0 } event UiClicked { id: int = 0 }
# Ui.close(): no menu is active (the same as opening root -1) # Ui.close(): no menu is active (the same as opening root -1)
function rt_ui_close() -> void { rt_ui_open(-1) } function rt_ui_close(rt_ui_draw_st: mut RtUiDrawState, rt_ui_st: mut RtUiState) -> void { rt_ui_open(rt_ui_draw_st, rt_ui_st, -1) }
function rt_ui_open(root: int) -> void { function rt_ui_open(rt_ui_draw_st: mut RtUiDrawState, rt_ui_st: mut RtUiState, root: int) -> void {
ui_active = root rt_ui_st.ui_active = root
let n = ui_focusables() let n = ui_focusables(rt_ui_draw_st, rt_ui_st)
ui_focus = -1 rt_ui_st.ui_focus = -1
if n > 0 { ui_focus = ui_fl[0] } if n > 0 { rt_ui_st.ui_focus = rt_ui_draw_st.ui_fl[0] }
} }
function rt_ui_tick(k: int) -> void { function rt_ui_tick(rt_image_st: RtImageState, rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, rt_ui_draw_st: mut RtUiDrawState, rt_ui_st: mut RtUiState, k: int) -> void {
for i in 0 .. ui_n { ui_fired[i] = 0 } # a closed UI clears clicks too, so a stale for i in 0 .. rt_ui_st.ui_n { rt_ui_st.ui_fired[i] = 0 } # a closed UI clears clicks too, so a stale
if ui_active < 0 { return } # activation never leaks to the next frame/scene if rt_ui_st.ui_active < 0 { return } # activation never leaks to the next frame/scene
ui_layout() ui_layout(rt_image_st, rt_truetype_raster_st, rt_truetype_st, rt_ui_st)
let n = ui_focusables() let n = ui_focusables(rt_ui_draw_st, rt_ui_st)
if n == 0 { if n == 0 {
ui_focus = -1 rt_ui_st.ui_focus = -1
return return
} }
var cur = -1 var cur = -1
for i in 0 .. n { for i in 0 .. n {
if ui_fl[i] == ui_focus { cur = i } if rt_ui_draw_st.ui_fl[i] == rt_ui_st.ui_focus { cur = i }
} }
if cur < 0 { if cur < 0 {
cur = 0 cur = 0
ui_focus = ui_fl[0] rt_ui_st.ui_focus = rt_ui_draw_st.ui_fl[0]
} }
if k == 'w' { # 'w' if k == 'w' { # 'w'
cur = (cur - 1 + n) % n cur = (cur - 1 + n) % n
ui_focus = ui_fl[cur] rt_ui_st.ui_focus = rt_ui_draw_st.ui_fl[cur]
} }
if k == 's' { # 's' if k == 's' { # 's'
cur = (cur + 1) % n cur = (cur + 1) % n
ui_focus = ui_fl[cur] rt_ui_st.ui_focus = rt_ui_draw_st.ui_fl[cur]
} }
var fire = 0 var fire = 0
if k == ' ' { fire = 1 } if k == ' ' { fire = 1 }
if k == '\n' { fire = 1 } if k == '\n' { fire = 1 }
if k == '\r' { fire = 1 } if k == '\r' { fire = 1 }
if fire == 1 { ui_fired[ui_focus] = 1; emit UiClicked(id: ui_focus) } if fire == 1 { rt_ui_st.ui_fired[rt_ui_st.ui_focus] = 1; emit UiClicked(id: rt_ui_st.ui_focus) }
} }
function rt_ui_render() -> void { function rt_ui_render(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, rt_ui_st: mut RtUiState) -> void {
if ui_active < 0 { return } if rt_ui_st.ui_active < 0 { return }
ui_layout() ui_layout(rt_image_st, rt_truetype_raster_st, rt_truetype_st, rt_ui_st)
ui_draw_node(ui_active) ui_draw_node(rt_core_st, rt_image_st, rt_truetype_raster_st, rt_truetype_st, rt_ui_st, rt_ui_st.ui_active)
} }
# ---- game-facing accessors ------------------------------------------------ # ---- game-facing accessors ------------------------------------------------
function rt_ui_clicked(id: int) -> bool { function rt_ui_clicked(rt_ui_st: RtUiState, id: int) -> bool {
if id < 0 { return false } if id < 0 { return false }
if id >= ui_n { return false } if id >= rt_ui_st.ui_n { return false }
return ui_fired[id] == 1 return rt_ui_st.ui_fired[id] == 1
} }
function rt_ui_set_text(id: int, s: string) -> void { function rt_ui_set_text(rt_ui_st: mut RtUiState, id: int, s: string) -> void {
if id < 0 { return } if id < 0 { return }
if id >= ui_n { return } if id >= rt_ui_st.ui_n { return }
let base = id * 96 let base = id * 96
var i = 0 var i = 0
var ch = s[0] var ch = s[0]
while ch != 0 { while ch != 0 {
if i >= 95 { ch = 0 } if i >= 95 { ch = 0 }
if ch != 0 { if ch != 0 {
ui_dyn[base + i] = ch rt_ui_st.ui_dyn[base + i] = ch
i += 1 i += 1
ch = s[i] ch = s[i]
} }
} }
ui_dyn[base + i] = 0 rt_ui_st.ui_dyn[base + i] = 0
ui_hasdyn[id] = 1 rt_ui_st.ui_hasdyn[id] = 1
} }
function rt_ui_set_int(id: int, n: int) -> void { function rt_ui_set_int(rt_ui_st: mut RtUiState, id: int, n: int) -> void {
if id < 0 { return } if id < 0 { return }
if id >= ui_n { return } if id >= rt_ui_st.ui_n { return }
let base = id * 96 let base = id * 96
var at = 0 var at = 0
var v = n var v = n
if v < 0 { if v < 0 {
ui_dyn[base] = '-' rt_ui_st.ui_dyn[base] = '-'
at = 1 at = 1
v = -v v = -v
} }
if v == 0 { if v == 0 {
ui_dyn[base + at] = '0' rt_ui_st.ui_dyn[base + at] = '0'
at += 1 at += 1
} else { } else {
var digits = 0 var digits = 0
@ -266,27 +268,27 @@ function rt_ui_set_int(id: int, n: int) -> void {
while p > 0 { while p > 0 {
var div = 1 var div = 1
for k in 1 .. p { div *= 10 } for k in 1 .. p { div *= 10 }
ui_dyn[base + at] = 48 + (v / div) % 10 rt_ui_st.ui_dyn[base + at] = 48 + (v / div) % 10
at += 1 at += 1
p -= 1 p -= 1
} }
} }
ui_dyn[base + at] = 0 rt_ui_st.ui_dyn[base + at] = 0
ui_hasdyn[id] = 1 rt_ui_st.ui_hasdyn[id] = 1
} }
function rt_ui_focus(id: int) -> void { function rt_ui_focus(rt_ui_st: mut RtUiState, id: int) -> void {
if id < 0 { return } if id < 0 { return }
if id >= ui_n { return } if id >= rt_ui_st.ui_n { return }
ui_focus = id rt_ui_st.ui_focus = id
} }
function rt_ui_focused() -> int { function rt_ui_focused(rt_ui_st: RtUiState) -> int {
return ui_focus return rt_ui_st.ui_focus
} }
function rt_ui_visible(id: int, v: int) -> void { function rt_ui_visible(rt_ui_st: mut RtUiState, id: int, v: int) -> void {
if id < 0 { return } if id < 0 { return }
if id >= ui_n { return } if id >= rt_ui_st.ui_n { return }
ui_visible[id] = v rt_ui_st.ui_visible[id] = v
} }

View file

@ -95,7 +95,7 @@ function xml_atoi(s: pointer) -> int {
# raw run. Only bytes 0..255 of a character reference are emitted (Ludic strings # raw run. Only bytes 0..255 of a character reference are emitted (Ludic strings
# are byte strings); a code point above that is written as its low byte, which is # are byte strings); a code point above that is written as its low byte, which is
# ample for the ASCII/Latin-1 text Tiled attributes carry. # ample for the ASCII/Latin-1 text Tiled attributes carry.
function xml_unescape(s: pointer) -> string { function xml_unescape(rt_xml_st: mut RtXmlState, s: pointer) -> string {
# fast path: no '&' means nothing to expand # fast path: no '&' means nothing to expand
var k = 0 var k = 0
let m = len(s) let m = len(s)
@ -134,7 +134,7 @@ function xml_unescape(s: pointer) -> string {
var d = 1 var d = 1
while d < len(ent) { code = code * 10 + (ent[d] - 48); d += 1 } while d < len(ent) { code = code * 10 + (ent[d] - 48); d += 1 }
} }
out += xml_byte(code & 255) out += xml_byte(rt_xml_st, code & 255)
} else { } else {
out = out + "&" + ent + ";" # unknown entity, keep literal out = out + "&" + ent + ";" # unknown entity, keep literal
} }
@ -155,17 +155,19 @@ function xml_hexval(c: int) -> int {
# a one-byte string holding byte value `b` (1..255); "" for 0 (a NUL can't sit in # a one-byte string holding byte value `b` (1..255); "" for 0 (a NUL can't sit in
# a Ludic string). Built by slicing a 256-byte table of every byte value. # a Ludic string). Built by slicing a 256-byte table of every byte value.
var xml_bytetab: pointer = null export state RtXmlState {
function xml_byte(b: int) -> string { xml_bytetab: pointer = null
}
function xml_byte(rt_xml_st: mut RtXmlState, b: int) -> string {
if b <= 0 { return "" } if b <= 0 { return "" }
if xml_bytetab == null { if rt_xml_st.xml_bytetab == null {
let t = bytes(257) let t = bytes(257)
var i = 0 var i = 0
while i < 256 { t[i] = i + 1; i += 1 } # table[i] = byte (i+1), so 0 never appears while i < 256 { t[i] = i + 1; i += 1 } # table[i] = byte (i+1), so 0 never appears
t[256] = 0 t[256] = 0
xml_bytetab = t rt_xml_st.xml_bytetab = t
} }
return xml_bytetab[b - 1..b] return rt_xml_st.xml_bytetab[b - 1..b]
} }
# --- parser ----------------------------------------------------------------- # --- parser -----------------------------------------------------------------
@ -217,7 +219,7 @@ function xp_name(p: XP) -> string {
} }
# parse `key="value"` / `key='value'` attributes into the element node. # parse `key="value"` / `key='value'` attributes into the element node.
function xp_attrs(p: XP, node: Xml) -> void { function xp_attrs(rt_xml_st: mut RtXmlState, p: XP, node: Xml) -> void {
while true { while true {
xp_skip_ws(p) xp_skip_ws(p)
if p.i >= p.n { return } if p.i >= p.n { return }
@ -235,7 +237,7 @@ function xp_attrs(p: XP, node: Xml) -> void {
p.i += 1 p.i += 1
let start = p.i let start = p.i
while p.i < p.n and p.s[p.i] != q { p.i += 1 } while p.i < p.n and p.s[p.i] != q { p.i += 1 }
val = xml_unescape(p.s[start..p.i]) val = xml_unescape(rt_xml_st, p.s[start..p.i])
p.i += 1 # skip closing quote p.i += 1 # skip closing quote
} }
} }
@ -245,13 +247,13 @@ function xp_attrs(p: XP, node: Xml) -> void {
} }
# parse one element (cursor on its opening '<'). Recurses for children. # parse one element (cursor on its opening '<'). Recurses for children.
function xp_element(p: XP) -> Xml { function xp_element(rt_xml_st: mut RtXmlState, p: XP) -> Xml {
let at = p.i let at = p.i
p.i += 1 # skip '<' p.i += 1 # skip '<'
let name = xp_name(p) let name = xp_name(p)
let node = xml_new(name) let node = xml_new(name)
node.at = at node.at = at
xp_attrs(p, node) xp_attrs(rt_xml_st, p, node)
# self-closing '/>' # self-closing '/>'
if p.i < p.n and p.s[p.i] == '/' { # '/' if p.i < p.n and p.s[p.i] == '/' { # '/'
p.i += 1 p.i += 1
@ -279,7 +281,7 @@ function xp_element(p: XP) -> Xml {
} else { } else {
if xp_skip_misc(p) { } # comment / PI inside content if xp_skip_misc(p) { } # comment / PI inside content
else { else {
let kid = xp_element(p) let kid = xp_element(rt_xml_st, p)
push(node.kids, kid) push(node.kids, kid)
push(node.mixed, kid) push(node.mixed, kid)
} }
@ -288,7 +290,7 @@ function xp_element(p: XP) -> Xml {
# character data run up to the next '<' # character data run up to the next '<'
let start = p.i let start = p.i
while p.i < p.n and p.s[p.i] != '<' { p.i += 1 } while p.i < p.n and p.s[p.i] != '<' { p.i += 1 }
let run = xml_unescape(p.s[start..p.i]) let run = xml_unescape(rt_xml_st, p.s[start..p.i])
node.text += run node.text += run
xp_text_run(node, run) xp_text_run(node, run)
} }
@ -312,7 +314,7 @@ function xp_text_run(node: Xml, run: string) -> void {
} }
# parse a whole document -> its root element (or the synthetic empty node). # parse a whole document -> its root element (or the synthetic empty node).
function xml_parse(s: pointer) -> Xml { function xml_parse(rt_xml_st: mut RtXmlState, s: pointer) -> Xml {
let p = new XP let p = new XP
p.s = s; p.i = 0; p.n = len(s) p.s = s; p.i = 0; p.n = len(s)
while p.i < p.n { while p.i < p.n {
@ -320,7 +322,7 @@ function xml_parse(s: pointer) -> Xml {
if p.i >= p.n { break } if p.i >= p.n { break }
if p.s[p.i] == '<' { # '<' if p.s[p.i] == '<' { # '<'
if xp_skip_misc(p) { } # prolog / comment / doctype if xp_skip_misc(p) { } # prolog / comment / doctype
else { return xp_element(p) } else { return xp_element(rt_xml_st, p) }
} else { p.i += 1 } } else { p.i += 1 }
} }
return xml_new("") return xml_new("")

View file

@ -17,32 +17,50 @@
# ============================================================================ # ============================================================================
# ---- forward bit reader (LSB-first) — for FSE table descriptions ----------- # ---- forward bit reader (LSB-first) — for FSE table descriptions -----------
var zf_src: pointer = null export state RtZstdState {
var zf_pos: int = 0 # byte cursor zf_src: pointer = null
var zf_bit: int = 0 # bit within the current byte (0 = LSB) zf_pos: int = 0 # byte cursor
zf_bit: int = 0 # bit within the current byte (0 = LSB)
zb_src: pointer = null
zb_s: int = 0 # first byte of the stream
zb_L: int = 0 # stream length
zb_skip: int = 0 # padding bits above the sentinel in the last byte
zb_cur: int = 0 # data-bit index consumed so far
fse_ncount_n: int = 0
zstd_pll: SeqDT = null
zstd_pof: SeqDT = null
zstd_pml: SeqDT = null
zstd_ll_base: words = null
zstd_ll_bits: words = null
zstd_ml_base: words = null
zstd_ml_bits: words = null
zstd_lit: pointer = null
zstd_litn: int = 0
zstd_huf_prev: HufDT = null
zstd_rep0: int = 1
zstd_rep1: int = 4
zstd_rep2: int = 8
zstd_seq_sp: int = 0
zstd_err: int = 0
}
function zf_init(src: pointer, at: int) -> void { zf_src = src; zf_pos = at; zf_bit = 0 } function zf_init(rt_zstd_st: mut RtZstdState, src: pointer, at: int) -> void { rt_zstd_st.zf_src = src; rt_zstd_st.zf_pos = at; rt_zstd_st.zf_bit = 0 }
function zf_read(n: int) -> int { function zf_read(rt_zstd_st: mut RtZstdState, n: int) -> int {
var v = 0 var v = 0
var k = 0 var k = 0
while k < n { while k < n {
let b = (zf_src[zf_pos] >> zf_bit) & 1 let b = (rt_zstd_st.zf_src[rt_zstd_st.zf_pos] >> rt_zstd_st.zf_bit) & 1
v = v | (b << k) v = v | (b << k)
zf_bit += 1 rt_zstd_st.zf_bit += 1
if zf_bit == 8 { zf_bit = 0; zf_pos += 1 } if rt_zstd_st.zf_bit == 8 { rt_zstd_st.zf_bit = 0; rt_zstd_st.zf_pos += 1 }
k += 1 k += 1
} }
return v return v
} }
# byte position just past the bits consumed (rounding up a partial byte). # byte position just past the bits consumed (rounding up a partial byte).
function zf_bytepos() -> int { if zf_bit == 0 { return zf_pos }; return zf_pos + 1 } function zf_bytepos(rt_zstd_st: RtZstdState) -> int { if rt_zstd_st.zf_bit == 0 { return rt_zstd_st.zf_pos }; return rt_zstd_st.zf_pos + 1 }
# ---- backward bit reader (MSB-first) — for FSE / Huffman streams ------------ # ---- backward bit reader (MSB-first) — for FSE / Huffman streams ------------
var zb_src: pointer = null
var zb_s: int = 0 # first byte of the stream
var zb_L: int = 0 # stream length
var zb_skip: int = 0 # padding bits above the sentinel in the last byte
var zb_cur: int = 0 # data-bit index consumed so far
function zstd_highbit(v: int) -> int { function zstd_highbit(v: int) -> int {
var r = -1 var r = -1
@ -52,32 +70,32 @@ function zstd_highbit(v: int) -> int {
} }
# init over stream bytes [s, s+L); the sentinel is the top set bit of the last. # init over stream bytes [s, s+L); the sentinel is the top set bit of the last.
function zb_init(src: pointer, s: int, L: int) -> int { function zb_init(rt_zstd_st: mut RtZstdState, src: pointer, s: int, L: int) -> int {
zb_src = src; zb_s = s; zb_L = L; zb_cur = 0 rt_zstd_st.zb_src = src; rt_zstd_st.zb_s = s; rt_zstd_st.zb_L = L; rt_zstd_st.zb_cur = 0
if L <= 0 { return -1 } if L <= 0 { return -1 }
let hb = zstd_highbit(src[s + L - 1]) let hb = zstd_highbit(src[s + L - 1])
if hb < 0 { return -1 } # a zero last byte is invalid if hb < 0 { return -1 } # a zero last byte is invalid
zb_skip = 8 - hb # the sentinel bit + anything above it is padding rt_zstd_st.zb_skip = 8 - hb # the sentinel bit + anything above it is padding
return 0 return 0
} }
# one data bit (0/1), MSB-first from the end of the stream. # one data bit (0/1), MSB-first from the end of the stream.
function zb_bit() -> int { function zb_bit(rt_zstd_st: mut RtZstdState) -> int {
let j = zb_skip + zb_cur let j = rt_zstd_st.zb_skip + rt_zstd_st.zb_cur
let byteidx = (zb_L - 1) - (j >> 3) let byteidx = (rt_zstd_st.zb_L - 1) - (j >> 3)
let bit = 7 - (j & 7) let bit = 7 - (j & 7)
zb_cur += 1 rt_zstd_st.zb_cur += 1
if byteidx < 0 { return 0 } if byteidx < 0 { return 0 }
return (zb_src[zb_s + byteidx] >> bit) & 1 return (rt_zstd_st.zb_src[rt_zstd_st.zb_s + byteidx] >> bit) & 1
} }
# read n bits, first bit read = most-significant of the result. # read n bits, first bit read = most-significant of the result.
function zb_read(n: int) -> int { function zb_read(rt_zstd_st: mut RtZstdState, n: int) -> int {
var v = 0 var v = 0
var k = 0 var k = 0
while k < n { v = (v << 1) | zb_bit(); k += 1 } while k < n { v = (v << 1) | zb_bit(rt_zstd_st); k += 1 }
return v return v
} }
function zb_total() -> int { return 8 * (zb_L - 1) + (8 - zb_skip) } # usable data bits function zb_total(rt_zstd_st: RtZstdState) -> int { return 8 * (rt_zstd_st.zb_L - 1) + (8 - rt_zstd_st.zb_skip) } # usable data bits
function zb_done() -> int { if zb_cur >= zb_total() { return 1 }; return 0 } function zb_done(rt_zstd_st: RtZstdState) -> int { if rt_zstd_st.zb_cur >= zb_total(rt_zstd_st) { return 1 }; return 0 }
# ---- FSE decode table ------------------------------------------------------ # ---- FSE decode table ------------------------------------------------------
property FseDT { property FseDT {
@ -152,9 +170,8 @@ function zstd_highbit32(v: int) -> int {
# read normalized counts (FSE_readNCount) forward from the current zf position. # read normalized counts (FSE_readNCount) forward from the current zf position.
# fills `norm[0..*nsym)`, returns the tableLog; `outn` receives the symbol count. # fills `norm[0..*nsym)`, returns the tableLog; `outn` receives the symbol count.
var fse_ncount_n: int = 0 function fse_read_ncount(rt_zstd_st: mut RtZstdState, norm: words, maxsym: int) -> int {
function fse_read_ncount(norm: words, maxsym: int) -> int { let accLog = zf_read(rt_zstd_st, 4) + 5
let accLog = zf_read(4) + 5
var remaining = (1 << accLog) + 1 var remaining = (1 << accLog) + 1
var threshold = 1 << accLog var threshold = 1 << accLog
var bitsLeft = accLog + 1 var bitsLeft = accLog + 1
@ -165,10 +182,10 @@ function fse_read_ncount(norm: words, maxsym: int) -> int {
# a run of zero-probability symbols, encoded in groups of 2 bits (0..3), # a run of zero-probability symbols, encoded in groups of 2 bits (0..3),
# 3 meaning "continue" # 3 meaning "continue"
var n0 = sym var n0 = sym
var rep = zf_read(2) var rep = zf_read(rt_zstd_st, 2)
while rep == 3 { while rep == 3 {
n0 += 3 n0 += 3
rep = zf_read(2) rep = zf_read(rt_zstd_st, 2)
} }
n0 += rep n0 += rep
while sym < n0 { norm[sym] = 0; sym += 1 } while sym < n0 { norm[sym] = 0; sym += 1 }
@ -177,12 +194,12 @@ function fse_read_ncount(norm: words, maxsym: int) -> int {
let maxv = (2 * threshold - 1) - remaining let maxv = (2 * threshold - 1) - remaining
var count = 0 var count = 0
# low bitsLeft-1 bits, then maybe one more (the "large" range) # low bitsLeft-1 bits, then maybe one more (the "large" range)
let low = zf_peek(bitsLeft - 1) let low = zf_peek(rt_zstd_st, bitsLeft - 1)
if low < maxv { if low < maxv {
count = low count = low
zf_skip(bitsLeft - 1) zf_skip(rt_zstd_st, bitsLeft - 1)
} else { } else {
count = zf_read(bitsLeft) count = zf_read(rt_zstd_st, bitsLeft)
if count >= threshold { count -= maxv } if count >= threshold { count -= maxv }
} }
let val = count - 1 # -1 means low-prob (stored as -1) let val = count - 1 # -1 means low-prob (stored as -1)
@ -199,36 +216,36 @@ function fse_read_ncount(norm: words, maxsym: int) -> int {
} }
} }
while sym <= maxsym { norm[sym] = 0; sym += 1 } while sym <= maxsym { norm[sym] = 0; sym += 1 }
fse_ncount_n = sym rt_zstd_st.fse_ncount_n = sym
return accLog return accLog
} }
# peek/skip helpers for the forward reader (the ncount "large range" needs a peek) # peek/skip helpers for the forward reader (the ncount "large range" needs a peek)
function zf_peek(n: int) -> int { function zf_peek(rt_zstd_st: mut RtZstdState, n: int) -> int {
let sp = zf_pos; let sb = zf_bit let sp = rt_zstd_st.zf_pos; let sb = rt_zstd_st.zf_bit
let v = zf_read(n) let v = zf_read(rt_zstd_st, n)
zf_pos = sp; zf_bit = sb rt_zstd_st.zf_pos = sp; rt_zstd_st.zf_bit = sb
return v return v
} }
function zf_skip(n: int) -> void { zf_read(n) } function zf_skip(rt_zstd_st: mut RtZstdState, n: int) -> void { zf_read(rt_zstd_st, n) }
# ---- FSE decompress (2 interleaved states) — Huffman weight stream ---------- # ---- FSE decompress (2 interleaved states) — Huffman weight stream ----------
# decode symbols from a backward stream already init'd, using DTable `dt`, into # decode symbols from a backward stream already init'd, using DTable `dt`, into
# out[0..cap). Returns count. Two states advance alternately (FSE_decompress). # out[0..cap). Returns count. Two states advance alternately (FSE_decompress).
function fse_decompress(dt: FseDT, out: words, cap: int) -> int { function fse_decompress(rt_zstd_st: mut RtZstdState, dt: FseDT, out: words, cap: int) -> int {
var s1 = zb_read(dt.log) var s1 = zb_read(rt_zstd_st, dt.log)
var s2 = zb_read(dt.log) var s2 = zb_read(rt_zstd_st, dt.log)
var n = 0 var n = 0
# two states alternate; when a state-advance read overruns the stream, the # two states alternate; when a state-advance read overruns the stream, the
# other state's residual symbol is the final one (FSE_decompress tail). # other state's residual symbol is the final one (FSE_decompress tail).
while true { while true {
out[n] = dt.sym[s1]; n += 1 out[n] = dt.sym[s1]; n += 1
s1 = dt.ns[s1] + zb_read(dt.nb[s1]) s1 = dt.ns[s1] + zb_read(rt_zstd_st, dt.nb[s1])
if zb_cur > zb_total() { out[n] = dt.sym[s2]; n += 1; return n } if rt_zstd_st.zb_cur > zb_total(rt_zstd_st) { out[n] = dt.sym[s2]; n += 1; return n }
if n >= cap { return n } if n >= cap { return n }
out[n] = dt.sym[s2]; n += 1 out[n] = dt.sym[s2]; n += 1
s2 = dt.ns[s2] + zb_read(dt.nb[s2]) s2 = dt.ns[s2] + zb_read(rt_zstd_st, dt.nb[s2])
if zb_cur > zb_total() { out[n] = dt.sym[s1]; n += 1; return n } if rt_zstd_st.zb_cur > zb_total(rt_zstd_st) { out[n] = dt.sym[s1]; n += 1; return n }
if n >= cap { return n } if n >= cap { return n }
} }
return n return n
@ -262,51 +279,44 @@ function zstd_lits(s: pointer) -> words {
return a return a
} }
var zstd_pll: SeqDT = null function zstd_predef_ll(rt_zstd_st: mut RtZstdState) -> SeqDT {
function zstd_predef_ll() -> SeqDT { if rt_zstd_st.zstd_pll != null { return rt_zstd_st.zstd_pll }
if zstd_pll != null { return zstd_pll }
let dt = new SeqDT let dt = new SeqDT
dt.log = 6 dt.log = 6
dt.base = zstd_lits("0;0;1;3;4;6;7;9;10;12;14;16;20;22;28;32;48;64;128;256;1024;4096;0;1;2;4;5;7;8;10;11;13;16;18;22;24;32;40;64;64;128;512;2048;0;1;2;3;5;6;8;9;11;12;15;18;20;24;28;40;48;65536;32768;16384;8192") dt.base = zstd_lits("0;0;1;3;4;6;7;9;10;12;14;16;20;22;28;32;48;64;128;256;1024;4096;0;1;2;4;5;7;8;10;11;13;16;18;22;24;32;40;64;64;128;512;2048;0;1;2;3;5;6;8;9;11;12;15;18;20;24;28;40;48;65536;32768;16384;8192")
dt.addbits = zstd_lits("0;0;0;0;0;0;0;0;0;0;0;1;1;1;2;3;4;6;7;8;10;12;0;0;0;0;0;0;0;0;0;0;1;1;1;2;3;3;6;6;7;9;11;0;0;0;0;0;0;0;0;0;0;0;1;1;2;2;3;4;16;15;14;13") dt.addbits = zstd_lits("0;0;0;0;0;0;0;0;0;0;0;1;1;1;2;3;4;6;7;8;10;12;0;0;0;0;0;0;0;0;0;0;1;1;1;2;3;3;6;6;7;9;11;0;0;0;0;0;0;0;0;0;0;0;1;1;2;2;3;4;16;15;14;13")
dt.nb = zstd_lits("4;4;5;5;5;5;5;5;5;5;6;5;5;5;5;5;5;5;5;6;6;6;4;4;5;5;5;5;5;5;5;6;5;5;5;5;5;5;4;4;5;6;6;4;4;5;5;5;5;5;5;5;5;6;5;5;5;5;5;5;6;6;6;6") dt.nb = zstd_lits("4;4;5;5;5;5;5;5;5;5;6;5;5;5;5;5;5;5;5;6;6;6;4;4;5;5;5;5;5;5;5;6;5;5;5;5;5;5;4;4;5;6;6;4;4;5;5;5;5;5;5;5;5;6;5;5;5;5;5;5;6;6;6;6")
dt.ns = zstd_lits("0;16;32;0;0;0;0;0;0;0;0;0;0;0;0;0;0;32;0;0;0;0;32;0;0;32;0;32;0;32;0;0;32;0;32;0;32;0;0;16;32;0;0;48;16;32;32;32;32;32;32;32;32;0;32;32;32;32;32;32;0;0;0;0") dt.ns = zstd_lits("0;16;32;0;0;0;0;0;0;0;0;0;0;0;0;0;0;32;0;0;0;0;32;0;0;32;0;32;0;32;0;0;32;0;32;0;32;0;0;16;32;0;0;48;16;32;32;32;32;32;32;32;32;0;32;32;32;32;32;32;0;0;0;0")
zstd_pll = dt rt_zstd_st.zstd_pll = dt
return dt return dt
} }
var zstd_pof: SeqDT = null function zstd_predef_of(rt_zstd_st: mut RtZstdState) -> SeqDT {
function zstd_predef_of() -> SeqDT { if rt_zstd_st.zstd_pof != null { return rt_zstd_st.zstd_pof }
if zstd_pof != null { return zstd_pof }
let dt = new SeqDT let dt = new SeqDT
dt.log = 5 dt.log = 5
dt.base = zstd_lits("0;61;509;32765;2097149;5;125;4093;262141;8388605;29;253;16381;1048573;1;125;2045;131069;4194301;13;253;8189;524285;1;61;1021;65533;268435453;134217725;67108861;33554429;16777213") dt.base = zstd_lits("0;61;509;32765;2097149;5;125;4093;262141;8388605;29;253;16381;1048573;1;125;2045;131069;4194301;13;253;8189;524285;1;61;1021;65533;268435453;134217725;67108861;33554429;16777213")
dt.addbits = zstd_lits("0;6;9;15;21;3;7;12;18;23;5;8;14;20;2;7;11;17;22;4;8;13;19;1;6;10;16;28;27;26;25;24") dt.addbits = zstd_lits("0;6;9;15;21;3;7;12;18;23;5;8;14;20;2;7;11;17;22;4;8;13;19;1;6;10;16;28;27;26;25;24")
dt.nb = zstd_lits("5;4;5;5;5;5;4;5;5;5;5;4;5;5;5;4;5;5;5;5;4;5;5;5;4;5;5;5;5;5;5;5") dt.nb = zstd_lits("5;4;5;5;5;5;4;5;5;5;5;4;5;5;5;4;5;5;5;5;4;5;5;5;4;5;5;5;5;5;5;5")
dt.ns = zstd_lits("0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;16;0;0;0;0;16;0;0;0;16;0;0;0;0;0;0;0") dt.ns = zstd_lits("0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;16;0;0;0;0;16;0;0;0;16;0;0;0;0;0;0;0")
zstd_pof = dt rt_zstd_st.zstd_pof = dt
return dt return dt
} }
var zstd_pml: SeqDT = null function zstd_predef_ml(rt_zstd_st: mut RtZstdState) -> SeqDT {
function zstd_predef_ml() -> SeqDT { if rt_zstd_st.zstd_pml != null { return rt_zstd_st.zstd_pml }
if zstd_pml != null { return zstd_pml }
let dt = new SeqDT let dt = new SeqDT
dt.log = 6 dt.log = 6
dt.base = zstd_lits("3;4;5;6;8;9;11;13;16;19;22;25;28;31;34;37;41;47;59;83;131;515;4;5;6;7;9;10;12;15;18;21;24;27;30;33;35;39;43;51;67;99;259;4;4;5;7;8;10;11;14;17;20;23;26;29;32;65539;32771;16387;8195;4099;2051;1027") dt.base = zstd_lits("3;4;5;6;8;9;11;13;16;19;22;25;28;31;34;37;41;47;59;83;131;515;4;5;6;7;9;10;12;15;18;21;24;27;30;33;35;39;43;51;67;99;259;4;4;5;7;8;10;11;14;17;20;23;26;29;32;65539;32771;16387;8195;4099;2051;1027")
dt.addbits = zstd_lits("0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;1;2;3;4;7;9;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;1;2;3;4;5;8;0;0;0;0;0;0;0;0;0;0;0;0;0;0;16;15;14;13;12;11;10") dt.addbits = zstd_lits("0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;1;2;3;4;7;9;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;1;2;3;4;5;8;0;0;0;0;0;0;0;0;0;0;0;0;0;0;16;15;14;13;12;11;10")
dt.nb = zstd_lits("6;4;5;5;5;5;5;6;6;6;6;6;6;6;6;6;6;6;6;6;6;6;4;4;5;5;5;5;6;6;6;6;6;6;6;6;6;6;6;6;6;6;6;4;4;4;5;5;5;5;6;6;6;6;6;6;6;6;6;6;6;6;6;6") dt.nb = zstd_lits("6;4;5;5;5;5;5;6;6;6;6;6;6;6;6;6;6;6;6;6;6;6;4;4;5;5;5;5;6;6;6;6;6;6;6;6;6;6;6;6;6;6;6;4;4;4;5;5;5;5;6;6;6;6;6;6;6;6;6;6;6;6;6;6")
dt.ns = zstd_lits("0;0;32;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;16;0;32;0;32;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;32;48;16;32;32;32;32;0;0;0;0;0;0;0;0;0;0;0;0;0;0") dt.ns = zstd_lits("0;0;32;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;16;0;32;0;32;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;32;48;16;32;32;32;32;0;0;0;0;0;0;0;0;0;0;0;0;0;0")
zstd_pml = dt rt_zstd_st.zstd_pml = dt
return dt return dt
} }
var zstd_ll_base: words = null function zstd_seq_tables_init(rt_zstd_st: mut RtZstdState) -> void {
var zstd_ll_bits: words = null if rt_zstd_st.zstd_ll_base != null { return }
var zstd_ml_base: words = null
var zstd_ml_bits: words = null
function zstd_seq_tables_init() -> void {
if zstd_ll_base != null { return }
let llb = words(36) let llb = words(36)
var i = 0 var i = 0
while i < 16 { llb[i] = i; i += 1 } while i < 16 { llb[i] = i; i += 1 }
@ -335,7 +345,7 @@ function zstd_seq_tables_init() -> void {
mlx[38] = 3; mlx[39] = 3; mlx[40] = 4; mlx[41] = 4; mlx[42] = 5; mlx[43] = 7 mlx[38] = 3; mlx[39] = 3; mlx[40] = 4; mlx[41] = 4; mlx[42] = 5; mlx[43] = 7
mlx[44] = 8; mlx[45] = 9; mlx[46] = 10; mlx[47] = 11; mlx[48] = 12; mlx[49] = 13 mlx[44] = 8; mlx[45] = 9; mlx[46] = 10; mlx[47] = 11; mlx[48] = 12; mlx[49] = 13
mlx[50] = 14; mlx[51] = 15; mlx[52] = 16 mlx[50] = 14; mlx[51] = 15; mlx[52] = 16
zstd_ll_base = llb; zstd_ll_bits = llx; zstd_ml_base = mlb; zstd_ml_bits = mlx rt_zstd_st.zstd_ll_base = llb; rt_zstd_st.zstd_ll_bits = llx; rt_zstd_st.zstd_ml_base = mlb; rt_zstd_st.zstd_ml_bits = mlx
} }
# ---- Huffman literal decode ------------------------------------------------ # ---- Huffman literal decode ------------------------------------------------
@ -384,63 +394,61 @@ function huf_build(weight: words, nweights: int) -> HufDT {
# parse a Huffman tree description at zf_pos (forward); returns the HufDT and # parse a Huffman tree description at zf_pos (forward); returns the HufDT and
# leaves zf_pos just past the description. # leaves zf_pos just past the description.
function huf_read_tree() -> HufDT { function huf_read_tree(rt_zstd_st: mut RtZstdState) -> HufDT {
let header = zf_src[zf_pos] let header = rt_zstd_st.zf_src[rt_zstd_st.zf_pos]
zf_pos += 1 rt_zstd_st.zf_pos += 1
let weight = words(256) let weight = words(256)
if header >= 128 { # direct: (header-127) 4-bit weights if header >= 128 { # direct: (header-127) 4-bit weights
let n = header - 127 let n = header - 127
var i = 0 var i = 0
while i < n { while i < n {
let bpos = zf_pos + (i >> 1) let bpos = rt_zstd_st.zf_pos + (i >> 1)
var w = 0 var w = 0
if (i & 1) == 0 { w = zf_src[bpos] >> 4 } else { w = zf_src[bpos] & 15 } if (i & 1) == 0 { w = rt_zstd_st.zf_src[bpos] >> 4 } else { w = rt_zstd_st.zf_src[bpos] & 15 }
weight[i] = w weight[i] = w
i += 1 i += 1
} }
zf_pos = zf_pos + ((n + 1) >> 1) rt_zstd_st.zf_pos = rt_zstd_st.zf_pos + ((n + 1) >> 1)
return huf_build(weight, n) return huf_build(weight, n)
} }
# FSE-compressed weights: header = compressed size; decode via a fresh FSE. # FSE-compressed weights: header = compressed size; decode via a fresh FSE.
# This path is not yet bit-exact, so flag it and let z_zstd bail with -1 rather # This path is not yet bit-exact, so flag it and let z_zstd bail with -1 rather
# than emit wrong literals (a low-entropy tilemap uses raw literals, not this). # than emit wrong literals (a low-entropy tilemap uses raw literals, not this).
zstd_err = 1 rt_zstd_st.zstd_err = 1
let cstart = zf_pos let cstart = rt_zstd_st.zf_pos
let norm = words(256) let norm = words(256)
let log = fse_read_ncount(norm, 255) # forward table description let log = fse_read_ncount(rt_zstd_st, norm, 255) # forward table description
let wt = fse_build(norm, fse_ncount_n, log) let wt = fse_build(norm, rt_zstd_st.fse_ncount_n, log)
# the weight bitstream runs from the byte after the ncount to cstart+header # the weight bitstream runs from the byte after the ncount to cstart+header
zb_init(zf_src, zf_bytepos(), cstart + header - zf_bytepos()) zb_init(rt_zstd_st, rt_zstd_st.zf_src, zf_bytepos(rt_zstd_st), cstart + header - zf_bytepos(rt_zstd_st))
let n = fse_decompress(wt, weight, 256) let n = fse_decompress(rt_zstd_st, wt, weight, 256)
zf_pos = cstart + header rt_zstd_st.zf_pos = cstart + header
return huf_build(weight, n) return huf_build(weight, n)
} }
# decode `n` Huffman symbols from a backward stream already init'd into out. # decode `n` Huffman symbols from a backward stream already init'd into out.
function huf_decode_stream(dt: HufDT, out: pointer, at: int, n: int) -> void { function huf_decode_stream(rt_zstd_st: mut RtZstdState, dt: HufDT, out: pointer, at: int, n: int) -> void {
var i = 0 var i = 0
while i < n { while i < n {
let bits = zstd_peek_rev(dt.maxbits) let bits = zstd_peek_rev(rt_zstd_st, dt.maxbits)
out[at + i] = dt.sym[bits] out[at + i] = dt.sym[bits]
zb_cur += dt.nb[bits] rt_zstd_st.zb_cur += dt.nb[bits]
i += 1 i += 1
} }
} }
# peek maxbits bits (MSB-first) without consuming — for the Huffman table index. # peek maxbits bits (MSB-first) without consuming — for the Huffman table index.
function zstd_peek_rev(n: int) -> int { function zstd_peek_rev(rt_zstd_st: mut RtZstdState, n: int) -> int {
let save = zb_cur let save = rt_zstd_st.zb_cur
let v = zb_read(n) let v = zb_read(rt_zstd_st, n)
zb_cur = save rt_zstd_st.zb_cur = save
return v return v
} }
# ---- literals section ------------------------------------------------------ # ---- literals section ------------------------------------------------------
# decoded literals live here; zstd_lit_decode fills them and returns the byte # decoded literals live here; zstd_lit_decode fills them and returns the byte
# offset where the sequences section begins. # offset where the sequences section begins.
var zstd_lit: pointer = null
var zstd_litn: int = 0
function zstd_lit_decode(src: pointer, pos: int, blockend: int) -> int { function zstd_lit_decode(rt_zstd_st: mut RtZstdState, src: pointer, pos: int, blockend: int) -> int {
let lb = src[pos] let lb = src[pos]
let ltype = lb & 3 let ltype = lb & 3
let sf = (lb >> 2) & 3 let sf = (lb >> 2) & 3
@ -457,8 +465,8 @@ function zstd_lit_decode(src: pointer, pos: int, blockend: int) -> int {
hdr = 3 hdr = 3
} } } }
if ltype == 0 { # raw literals: copy regen bytes if ltype == 0 { # raw literals: copy regen bytes
zstd_lit = offset(src, pos + hdr) rt_zstd_st.zstd_lit = offset(src, pos + hdr)
zstd_litn = regen rt_zstd_st.zstd_litn = regen
return pos + hdr + regen return pos + hdr + regen
} }
if ltype == 1 { # RLE literals: one byte * regen if ltype == 1 { # RLE literals: one byte * regen
@ -466,17 +474,16 @@ function zstd_lit_decode(src: pointer, pos: int, blockend: int) -> int {
let b = src[pos + hdr] let b = src[pos + hdr]
var i = 0 var i = 0
while i < regen { buf[i] = b; i += 1 } while i < regen { buf[i] = b; i += 1 }
zstd_lit = buf rt_zstd_st.zstd_lit = buf
zstd_litn = regen rt_zstd_st.zstd_litn = regen
return pos + hdr + 1 return pos + hdr + 1
} }
# Huffman literals (ltype 2 compressed, 3 treeless) — a 3/4/5-byte header with # Huffman literals (ltype 2 compressed, 3 treeless) — a 3/4/5-byte header with
# regenerated + compressed sizes and 1 or 4 streams. # regenerated + compressed sizes and 1 or 4 streams.
return zstd_lit_huff(src, pos, ltype, sf) return zstd_lit_huff(rt_zstd_st, src, pos, ltype, sf)
} }
var zstd_huf_prev: HufDT = null function zstd_lit_huff(rt_zstd_st: mut RtZstdState, src: pointer, pos: int, ltype: int, sf: int) -> int {
function zstd_lit_huff(src: pointer, pos: int, ltype: int, sf: int) -> int {
var regen = 0 var regen = 0
var comp = 0 var comp = 0
var hdr = 0 var hdr = 0
@ -497,22 +504,22 @@ function zstd_lit_huff(src: pointer, pos: int, ltype: int, sf: int) -> int {
hdr = 5 hdr = 5
} } } } } }
let buf = bytes(regen + 4) let buf = bytes(regen + 4)
zstd_lit = buf rt_zstd_st.zstd_lit = buf
zstd_litn = regen rt_zstd_st.zstd_litn = regen
var hs = pos + hdr # start of the Huffman payload var hs = pos + hdr # start of the Huffman payload
var payn = comp var payn = comp
var dt = zstd_huf_prev var dt = rt_zstd_st.zstd_huf_prev
if ltype == 2 { # compressed: read the tree first if ltype == 2 { # compressed: read the tree first
zf_init(src, hs) zf_init(rt_zstd_st, src, hs)
dt = huf_read_tree() dt = huf_read_tree(rt_zstd_st)
let treesize = zf_bytepos() - hs let treesize = zf_bytepos(rt_zstd_st) - hs
hs += treesize hs += treesize
payn = comp - treesize payn = comp - treesize
zstd_huf_prev = dt rt_zstd_st.zstd_huf_prev = dt
} }
if streams == 1 { if streams == 1 {
zb_init(src, hs, payn) zb_init(rt_zstd_st, src, hs, payn)
huf_decode_stream(dt, buf, 0, regen) huf_decode_stream(rt_zstd_st, dt, buf, 0, regen)
} else { } else {
# 4 streams with a 6-byte jump table (three 16-bit sizes; the 4th is derived) # 4 streams with a 6-byte jump table (three 16-bit sizes; the 4th is derived)
let s1 = src[hs] | (src[hs + 1] << 8) let s1 = src[hs] | (src[hs + 1] << 8)
@ -521,78 +528,74 @@ function zstd_lit_huff(src: pointer, pos: int, ltype: int, sf: int) -> int {
let s4 = payn - 6 - s1 - s2 - s3 let s4 = payn - 6 - s1 - s2 - s3
let seg = (regen + 3) / 4 let seg = (regen + 3) / 4
var o = hs + 6 var o = hs + 6
zb_init(src, o, s1); huf_decode_stream(dt, buf, 0, seg) zb_init(rt_zstd_st, src, o, s1); huf_decode_stream(rt_zstd_st, dt, buf, 0, seg)
o += s1 o += s1
zb_init(src, o, s2); huf_decode_stream(dt, buf, seg, seg) zb_init(rt_zstd_st, src, o, s2); huf_decode_stream(rt_zstd_st, dt, buf, seg, seg)
o += s2 o += s2
zb_init(src, o, s3); huf_decode_stream(dt, buf, 2 * seg, seg) zb_init(rt_zstd_st, src, o, s3); huf_decode_stream(rt_zstd_st, dt, buf, 2 * seg, seg)
o += s3 o += s3
zb_init(src, o, s4); huf_decode_stream(dt, buf, 3 * seg, regen - 3 * seg) zb_init(rt_zstd_st, src, o, s4); huf_decode_stream(rt_zstd_st, dt, buf, 3 * seg, regen - 3 * seg)
} }
return pos + hdr + comp return pos + hdr + comp
} }
# ---- sequences + execution (exact port of ZSTD_decodeSequence) ------------- # ---- sequences + execution (exact port of ZSTD_decodeSequence) -------------
var zstd_rep0: int = 1
var zstd_rep1: int = 4
var zstd_rep2: int = 8
var zstd_seq_sp: int = 0
# build a sequence SeqDT for a symbol type from its compression mode: # build a sequence SeqDT for a symbol type from its compression mode:
# 0 predefined, 1 RLE, 2 FSE-described (forward table desc), 3 repeat previous. # 0 predefined, 1 RLE, 2 FSE-described (forward table desc), 3 repeat previous.
# `kind` selects the baseline tables for RLE / mode-2: 0 = LL, 1 = OF, 2 = ML. # `kind` selects the baseline tables for RLE / mode-2: 0 = LL, 1 = OF, 2 = ML.
function zstd_seq_table(src: pointer, sp: int, mode: int, kind: int, prev: SeqDT) -> SeqDT { function zstd_seq_table(rt_zstd_st: mut RtZstdState, src: pointer, sp: int, mode: int, kind: int, prev: SeqDT) -> SeqDT {
if mode == 0 { if mode == 0 {
zstd_seq_sp = sp rt_zstd_st.zstd_seq_sp = sp
if kind == 0 { return zstd_predef_ll() } if kind == 0 { return zstd_predef_ll(rt_zstd_st) }
if kind == 1 { return zstd_predef_of() } if kind == 1 { return zstd_predef_of(rt_zstd_st) }
return zstd_predef_ml() return zstd_predef_ml(rt_zstd_st)
} }
if mode == 3 { zstd_seq_sp = sp; return prev } if mode == 3 { rt_zstd_st.zstd_seq_sp = sp; return prev }
if mode == 1 { # RLE: a single symbol byte if mode == 1 { # RLE: a single symbol byte
let sym = src[sp] let sym = src[sp]
zstd_seq_sp = sp + 1 rt_zstd_st.zstd_seq_sp = sp + 1
let dt = new SeqDT let dt = new SeqDT
dt.log = 0; dt.base = words(1); dt.addbits = words(1); dt.nb = words(1); dt.ns = words(1) dt.log = 0; dt.base = words(1); dt.addbits = words(1); dt.nb = words(1); dt.ns = words(1)
dt.base[0] = zstd_seq_base(kind, sym); dt.addbits[0] = zstd_seq_bits(kind, sym) dt.base[0] = zstd_seq_base(rt_zstd_st, kind, sym); dt.addbits[0] = zstd_seq_bits(rt_zstd_st, kind, sym)
dt.nb[0] = 0; dt.ns[0] = 0 dt.nb[0] = 0; dt.ns[0] = 0
return dt return dt
} }
# mode 2: FSE table description (forward), build, then map base/addbits per symbol # mode 2: FSE table description (forward), build, then map base/addbits per symbol
let norm = words(256) let norm = words(256)
zf_init(src, sp) zf_init(rt_zstd_st, src, sp)
let log = fse_read_ncount(norm, 255) let log = fse_read_ncount(rt_zstd_st, norm, 255)
zstd_seq_sp = zf_bytepos() rt_zstd_st.zstd_seq_sp = zf_bytepos(rt_zstd_st)
let ft = fse_build(norm, fse_ncount_n, log) let ft = fse_build(norm, rt_zstd_st.fse_ncount_n, log)
let size = 1 << log let size = 1 << log
let dt = new SeqDT let dt = new SeqDT
dt.log = log; dt.base = words(size); dt.addbits = words(size); dt.nb = ft.nb; dt.ns = ft.ns dt.log = log; dt.base = words(size); dt.addbits = words(size); dt.nb = ft.nb; dt.ns = ft.ns
var u = 0 var u = 0
while u < size { while u < size {
dt.base[u] = zstd_seq_base(kind, ft.sym[u]) dt.base[u] = zstd_seq_base(rt_zstd_st, kind, ft.sym[u])
dt.addbits[u] = zstd_seq_bits(kind, ft.sym[u]) dt.addbits[u] = zstd_seq_bits(rt_zstd_st, kind, ft.sym[u])
u += 1 u += 1
} }
return dt return dt
} }
# baseline / extra-bit tables per symbol type for RLE + FSE-described tables. # baseline / extra-bit tables per symbol type for RLE + FSE-described tables.
function zstd_seq_base(kind: int, sym: int) -> int { function zstd_seq_base(rt_zstd_st: RtZstdState, kind: int, sym: int) -> int {
if kind == 0 { return zstd_ll_base[sym] } if kind == 0 { return rt_zstd_st.zstd_ll_base[sym] }
if kind == 2 { return zstd_ml_base[sym] } if kind == 2 { return rt_zstd_st.zstd_ml_base[sym] }
return 1 << sym # OF: offset base = 1<<code return 1 << sym # OF: offset base = 1<<code
} }
function zstd_seq_bits(kind: int, sym: int) -> int { function zstd_seq_bits(rt_zstd_st: RtZstdState, kind: int, sym: int) -> int {
if kind == 0 { return zstd_ll_bits[sym] } if kind == 0 { return rt_zstd_st.zstd_ll_bits[sym] }
if kind == 2 { return zstd_ml_bits[sym] } if kind == 2 { return rt_zstd_st.zstd_ml_bits[sym] }
return sym # OF: extra bits = code return sym # OF: extra bits = code
} }
# decode one compressed block's literals + sequences into out[opos..]. # decode one compressed block's literals + sequences into out[opos..].
function zstd_decomp_block(src: pointer, bstart: int, bsize: int, out: pointer, opos: int, cap: int) -> int { function zstd_decomp_block(rt_zstd_st: mut RtZstdState, src: pointer, bstart: int, bsize: int, out: pointer, opos: int, cap: int) -> int {
zstd_seq_tables_init() zstd_seq_tables_init(rt_zstd_st)
let blockend = bstart + bsize let blockend = bstart + bsize
var sp = zstd_lit_decode(src, bstart, blockend) var sp = zstd_lit_decode(rt_zstd_st, src, bstart, blockend)
var nseq = src[sp] var nseq = src[sp]
if nseq < 128 { sp += 1 } if nseq < 128 { sp += 1 }
else { if nseq < 255 { nseq = ((nseq - 128) << 8) + src[sp + 1]; sp += 2 } else { if nseq < 255 { nseq = ((nseq - 128) << 8) + src[sp + 1]; sp += 2 }
@ -601,21 +604,21 @@ function zstd_decomp_block(src: pointer, bstart: int, bsize: int, out: pointer,
var litpos = 0 var litpos = 0
if nseq == 0 { if nseq == 0 {
var i = 0 var i = 0
while i < zstd_litn { out[op] = zstd_lit[i]; op += 1; i += 1 } while i < rt_zstd_st.zstd_litn { out[op] = rt_zstd_st.zstd_lit[i]; op += 1; i += 1 }
return op return op
} }
let modes = src[sp]; sp += 1 let modes = src[sp]; sp += 1
let llmode = (modes >> 6) & 3 let llmode = (modes >> 6) & 3
let ofmode = (modes >> 4) & 3 let ofmode = (modes >> 4) & 3
let mlmode = (modes >> 2) & 3 let mlmode = (modes >> 2) & 3
let llt = zstd_seq_table(src, sp, llmode, 0, null); sp = zstd_seq_sp let llt = zstd_seq_table(rt_zstd_st, src, sp, llmode, 0, null); sp = rt_zstd_st.zstd_seq_sp
let oft = zstd_seq_table(src, sp, ofmode, 1, null); sp = zstd_seq_sp let oft = zstd_seq_table(rt_zstd_st, src, sp, ofmode, 1, null); sp = rt_zstd_st.zstd_seq_sp
let mlt = zstd_seq_table(src, sp, mlmode, 2, null); sp = zstd_seq_sp let mlt = zstd_seq_table(rt_zstd_st, src, sp, mlmode, 2, null); sp = rt_zstd_st.zstd_seq_sp
zstd_rep0 = 1; zstd_rep1 = 4; zstd_rep2 = 8 rt_zstd_st.zstd_rep0 = 1; rt_zstd_st.zstd_rep1 = 4; rt_zstd_st.zstd_rep2 = 8
zb_init(src, sp, blockend - sp) zb_init(rt_zstd_st, src, sp, blockend - sp)
var llstate = zb_read(llt.log) var llstate = zb_read(rt_zstd_st, llt.log)
var ofstate = zb_read(oft.log) var ofstate = zb_read(rt_zstd_st, oft.log)
var mlstate = zb_read(mlt.log) var mlstate = zb_read(rt_zstd_st, mlt.log)
var q = 0 var q = 0
while q < nseq { while q < nseq {
var matchLen = mlt.base[mlstate] var matchLen = mlt.base[mlstate]
@ -628,46 +631,45 @@ function zstd_decomp_block(src: pointer, bstart: int, bsize: int, out: pointer,
if llt.base[llstate] == 0 { ll0 = 1 } if llt.base[llstate] == 0 { ll0 = 1 }
var offset = 0 var offset = 0
if ofBits > 1 { if ofBits > 1 {
offset = ofBase + zb_read(ofBits) offset = ofBase + zb_read(rt_zstd_st, ofBits)
zstd_rep2 = zstd_rep1; zstd_rep1 = zstd_rep0; zstd_rep0 = offset rt_zstd_st.zstd_rep2 = rt_zstd_st.zstd_rep1; rt_zstd_st.zstd_rep1 = rt_zstd_st.zstd_rep0; rt_zstd_st.zstd_rep0 = offset
} else { } else {
if ofBits == 0 { if ofBits == 0 {
if ll0 == 1 { offset = zstd_rep1; zstd_rep1 = zstd_rep0; zstd_rep0 = offset } if ll0 == 1 { offset = rt_zstd_st.zstd_rep1; rt_zstd_st.zstd_rep1 = rt_zstd_st.zstd_rep0; rt_zstd_st.zstd_rep0 = offset }
else { offset = zstd_rep0 } else { offset = rt_zstd_st.zstd_rep0 }
} else { } else {
let ov = ofBase + ll0 + zb_read(1) let ov = ofBase + ll0 + zb_read(rt_zstd_st, 1)
var temp = 0 var temp = 0
if ov == 1 { temp = zstd_rep1 } if ov == 1 { temp = rt_zstd_st.zstd_rep1 }
else { if ov == 3 { temp = zstd_rep0 - 1 } else { if ov == 3 { temp = rt_zstd_st.zstd_rep0 - 1 }
else { if ov >= 2 { temp = zstd_rep2 } else { temp = zstd_rep0 } } } else { if ov >= 2 { temp = rt_zstd_st.zstd_rep2 } else { temp = rt_zstd_st.zstd_rep0 } } }
if temp == 0 { temp = -1 } if temp == 0 { temp = -1 }
if ov == 1 { } else { zstd_rep2 = zstd_rep1 } if ov == 1 { } else { rt_zstd_st.zstd_rep2 = rt_zstd_st.zstd_rep1 }
zstd_rep1 = zstd_rep0 rt_zstd_st.zstd_rep1 = rt_zstd_st.zstd_rep0
zstd_rep0 = temp rt_zstd_st.zstd_rep0 = temp
offset = temp offset = temp
} }
} }
if mlBits > 0 { matchLen += zb_read(mlBits) } if mlBits > 0 { matchLen += zb_read(rt_zstd_st, mlBits) }
if llBits > 0 { litLen += zb_read(llBits) } if llBits > 0 { litLen += zb_read(rt_zstd_st, llBits) }
var i = 0 var i = 0
while i < litLen { out[op] = zstd_lit[litpos]; op += 1; litpos += 1; i += 1 } while i < litLen { out[op] = rt_zstd_st.zstd_lit[litpos]; op += 1; litpos += 1; i += 1 }
var k = 0 var k = 0
while k < matchLen { out[op] = out[op - offset]; op += 1; k += 1 } while k < matchLen { out[op] = out[op - offset]; op += 1; k += 1 }
q += 1 q += 1
if q < nseq { if q < nseq {
llstate = llt.ns[llstate] + zb_read(llt.nb[llstate]) llstate = llt.ns[llstate] + zb_read(rt_zstd_st, llt.nb[llstate])
mlstate = mlt.ns[mlstate] + zb_read(mlt.nb[mlstate]) mlstate = mlt.ns[mlstate] + zb_read(rt_zstd_st, mlt.nb[mlstate])
ofstate = oft.ns[ofstate] + zb_read(oft.nb[ofstate]) ofstate = oft.ns[ofstate] + zb_read(rt_zstd_st, oft.nb[ofstate])
} }
} }
while litpos < zstd_litn { out[op] = zstd_lit[litpos]; op += 1; litpos += 1 } while litpos < rt_zstd_st.zstd_litn { out[op] = rt_zstd_st.zstd_lit[litpos]; op += 1; litpos += 1 }
return op return op
} }
# ---- frame / block loop ---------------------------------------------------- # ---- frame / block loop ----------------------------------------------------
var zstd_err: int = 0
# decompress a single zstd frame. Returns bytes written, or -1. # decompress a single zstd frame. Returns bytes written, or -1.
function z_zstd(src: pointer, len: int, out: pointer, cap: int) -> int { function z_zstd(rt_zstd_st: mut RtZstdState, src: pointer, len: int, out: pointer, cap: int) -> int {
if len < 6 { return -1 } if len < 6 { return -1 }
if src[0] != '(' { return -1 } # 0x28 if src[0] != '(' { return -1 } # 0x28
if src[1] != 181 { return -1 } # 0xB5 if src[1] != 181 { return -1 } # 0xB5
@ -687,9 +689,9 @@ function z_zstd(src: pointer, len: int, out: pointer, cap: int) -> int {
if fcsFlag == 2 { pos += 4 } if fcsFlag == 2 { pos += 4 }
if fcsFlag == 3 { pos += 8 } if fcsFlag == 3 { pos += 8 }
# reset the repeat offsets per frame # reset the repeat offsets per frame
zstd_rep0 = 1; zstd_rep1 = 4; zstd_rep2 = 8 rt_zstd_st.zstd_rep0 = 1; rt_zstd_st.zstd_rep1 = 4; rt_zstd_st.zstd_rep2 = 8
zstd_huf_prev = null rt_zstd_st.zstd_huf_prev = null
zstd_err = 0 rt_zstd_st.zstd_err = 0
var op = 0 var op = 0
var last = 0 var last = 0
while last == 0 { while last == 0 {
@ -709,11 +711,11 @@ function z_zstd(src: pointer, len: int, out: pointer, cap: int) -> int {
while i < bsize { out[op] = b; op += 1; i += 1 } while i < bsize { out[op] = b; op += 1; i += 1 }
pos += 1 pos += 1
} else { if btype == 2 { # compressed block } else { if btype == 2 { # compressed block
op = zstd_decomp_block(src, pos, bsize, out, op, cap) op = zstd_decomp_block(rt_zstd_st, src, pos, bsize, out, op, cap)
pos += bsize pos += bsize
} else { return -1 } } } # reserved } else { return -1 } } } # reserved
if op > cap { return -1 } if op > cap { return -1 }
if zstd_err != 0 { return -1 } if rt_zstd_st.zstd_err != 0 { return -1 }
} }
return op return op
} }

View file

@ -500,6 +500,10 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
if (vdecl != null) { if (vdecl != null) {
let vnames = param_labels(vdecl) let vnames = param_labels(vdecl)
var vi = 0 var vi = 0
if vdecl.kind == N_FN {
nsfn = vdecl # 0.S: its states are the runtime's to give
vi = state_lead(vdecl)
}
while vi < len(vnames) { push(labels, vnames[vi]); vi += 1 } while vi < len(vnames) { push(labels, vnames[vi]); vi += 1 }
} }
} }
@ -510,6 +514,10 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
if (gdecl != null) { if (gdecl != null) {
let gnames = param_labels(gdecl) let gnames = param_labels(gdecl)
var gi = 0 var gi = 0
if gdecl.kind == N_FN {
nsfn = gdecl # 0.S: its states are the runtime's to give
gi = state_lead(gdecl)
}
while gi < len(gnames) { push(labels, gnames[gi]); gi += 1 } while gi < len(gnames) { push(labels, gnames[gi]); gi += 1 }
} }
} }

View file

@ -60,6 +60,7 @@ function emit_main(d: Node) -> void {
# game that renders (drives tick_render, draws, uses the engine light pass) has # game that renders (drives tick_render, draws, uses the engine light pass) has
# its framebuffer allocated. Headless it only allocates — no window, no output — # its framebuffer allocated. Headless it only allocates — no window, no output —
# so a non-rendering entry game is unchanged. # so a non-rendering entry game is unchanged.
emit(" call void @L_init_runtime()\n")
if (find_fn("rt_init") != null) { emit(" call void " + fn_sym("rt_init") + "()\n") } if (find_fn("rt_init") != null) { emit(" call void " + fn_sym("rt_init") + "()\n") }
emit(" call void @L_init_globals()\n") emit(" call void @L_init_globals()\n")
emit_block(d.a) emit_block(d.a)
@ -146,6 +147,7 @@ function emit_test_runner() -> void {
} }
emit(" ret i32 0\n") emit(" ret i32 0\n")
emit(`{lrun0}:\n`) emit(`{lrun0}:\n`)
emit(" call void @L_init_runtime()\n")
if has_ecs() { emit(" call void @rt_init()\n") } if has_ecs() { emit(" call void @rt_init()\n") }
emit(" call void @L_init_globals()\n") emit(" call void @L_init_globals()\n")
i = 0 i = 0

View file

@ -64,6 +64,12 @@ function global_needs_init_code(d: Node) -> bool {
# (`var run: Progress = new Progress`, `var speed: int = BASE * 2`, a call), in # (`var run: Progress = new Progress`, `var speed: int = BASE * 2`, a call), in
# declaration order, once at startup — after the runtime boots, before Start. # declaration order, once at startup — after the runtime boots, before Start.
function emit_global_init_fn() -> void { function emit_global_init_fn() -> void {
emit_init_fn("L_init_runtime", true)
emit_init_fn("L_init_globals", false)
}
# 0.S: the runtime's states are made first, before it boots (rt_init reads them); the rest after
function is_runtime_state_var(d: Node) -> bool { return d.uns == 1 and is_state_ty(d.ty) and is_runtime_file(d.file) }
function emit_init_fn(name: pointer, runtime: bool) -> void {
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0 ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0
ret_ty = "void" ret_ty = "void"
let fbody = buf_new() let fbody = buf_new()
@ -73,7 +79,7 @@ function emit_global_init_fn() -> void {
var i = 0 var i = 0
while i < len(prog) { while i < len(prog) {
let d = prog[i] let d = prog[i]
if d.kind == N_VAR and global_needs_init_code(d) { if d.kind == N_VAR and global_needs_init_code(d) and is_runtime_state_var(d) == runtime {
# an initializer is the global's own file's code: its errors, and what its module # an initializer is the global's own file's code: its errors, and what its module
# may see (L3), are that file's - not whichever statement was lowered last # may see (L3), are that file's - not whichever statement was lowered last
g_err_file = d.file g_err_file = d.file
@ -93,7 +99,7 @@ function emit_global_init_fn() -> void {
} }
emit(" ret void\n") emit(" ret void\n")
code = saved code = saved
emit("define void @L_init_globals() {\nentry:\n") emit(`define void @{name}() {{\nentry:\n`)
emit(buf_str(falloc)) emit(buf_str(falloc))
emit(buf_str(fbody)) emit(buf_str(fbody))
emit("}\n\n") emit("}\n\n")

View file

@ -445,6 +445,7 @@ function emit_game_main() -> void {
emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n") emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n")
emit(" store i32 %argc, ptr @L_argc\n") emit(" store i32 %argc, ptr @L_argc\n")
emit(" store ptr %argv, ptr @L_argv\n") emit(" store ptr %argv, ptr @L_argv\n")
emit(" call void @L_init_runtime()\n")
if (find_fn("rt_init") != null) { emit(" call void " + fn_sym("rt_init") + "()\n") } if (find_fn("rt_init") != null) { emit(" call void " + fn_sym("rt_init") + "()\n") }
emit(" call void @L_init_globals()\n") emit(" call void @L_init_globals()\n")
emit_calls_for_phase("Start") emit_calls_for_phase("Start")

View file

@ -53,6 +53,15 @@ function ns_alias_labels(i: int) -> []pointer {
if d != null { return param_labels(d) } if d != null { return param_labels(d) }
return out return out
} }
# 0.S: a target's leading states come first, by their own names (they are supplied, not written)
let t = find_fn(g_al_target[i])
if t != null {
var k = 0
while k < len(t.kids) and t.kids[k].kind == N_PARAM and is_state_ty(t.kids[k].ty) {
push(out, t.kids[k].s)
k += 1
}
}
var a = 0 var a = 0
var j = 0 var j = 0
let n = len(ls) let n = len(ls)

View file

@ -1027,7 +1027,7 @@ function parse_one_decl() -> void {
if is_id("component") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_ui_component(); return } # L11 if is_id("component") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_ui_component(); return } # L11
if is_id("state") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_state(); return } # 0.S if is_id("state") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_state(); return } # 0.S
if is_id("var") { if is_id("var") {
if not g_allow_globals and not has_sub(g_parse_file, "runtime/native/") { # the runtime: until it is migrated (0.S3) if not g_allow_globals {
perr(`a module-level var is refused: a module's changing data is its state (state Name {{ ... }}), passed to the functions that use it - or, if it never changes, a let`) perr(`a module-level var is refused: a module's changing data is its state (state Name {{ ... }}), passed to the functions that use it - or, if it never changes, a let`)
} }
push(prog, parse_var()) push(prog, parse_var())

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff