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:
parent
7b17b4a1b9
commit
02448e176c
38 changed files with 53877 additions and 53251 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
function http_init(rt_http_st: mut RtHttpState) -> void {
|
||||||
if h_ready { return }
|
if rt_http_st.h_ready { return }
|
||||||
h_used = words(HTTP_SLOTS)
|
rt_http_st.h_used = words(HTTP_SLOTS)
|
||||||
h_native = words(HTTP_SLOTS)
|
rt_http_st.h_native = words(HTTP_SLOTS)
|
||||||
h_sent = words(HTTP_SLOTS)
|
rt_http_st.h_sent = words(HTTP_SLOTS)
|
||||||
h_res = words(HTTP_SLOTS)
|
rt_http_st.h_res = words(HTTP_SLOTS)
|
||||||
h_status = words(HTTP_SLOTS)
|
rt_http_st.h_status = words(HTTP_SLOTS)
|
||||||
h_blen = words(HTTP_SLOTS)
|
rt_http_st.h_blen = words(HTTP_SLOTS)
|
||||||
h_req = pointers(HTTP_SLOTS)
|
rt_http_st.h_req = pointers(HTTP_SLOTS)
|
||||||
h_body = pointers(HTTP_SLOTS)
|
rt_http_st.h_body = pointers(HTTP_SLOTS)
|
||||||
h_hdr = pointers(HTTP_SLOTS)
|
rt_http_st.h_hdr = pointers(HTTP_SLOTS)
|
||||||
h_save = words(HTTP_SLOTS)
|
rt_http_st.h_save = words(HTTP_SLOTS)
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < HTTP_SLOTS {
|
while i < HTTP_SLOTS {
|
||||||
h_used[i] = 0; h_native[i] = 0; h_sent[i] = 0; h_res[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_status[i] = 0; h_blen[i] = 0
|
rt_http_st.h_status[i] = 0; rt_http_st.h_blen[i] = 0
|
||||||
h_req[i] = null; h_body[i] = null; h_hdr[i] = null
|
rt_http_st.h_req[i] = null; rt_http_st.h_body[i] = null; rt_http_st.h_hdr[i] = null
|
||||||
h_save[i] = 0
|
rt_http_st.h_save[i] = 0
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
h_ready = true
|
rt_http_st.h_ready = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function http_slot_alloc() -> int {
|
function http_slot_alloc(rt_http_st: mut RtHttpState) -> int {
|
||||||
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] == 0 {
|
if rt_http_st.h_used[i] == 0 {
|
||||||
h_used[i] = 1; h_native[i] = 0; h_sent[i] = 0; h_res[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
|
||||||
h_status[i] = 0; h_blen[i] = 0
|
rt_http_st.h_status[i] = 0; rt_http_st.h_blen[i] = 0
|
||||||
h_req[i] = null; h_body[i] = null; h_hdr[i] = null
|
rt_http_st.h_req[i] = null; rt_http_st.h_body[i] = null; rt_http_st.h_hdr[i] = null
|
||||||
h_save[i] = 0
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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("")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
50683
selfhost/ludicc.seed.ll
50683
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue