feat(lang): L7 memory is safe unless it says unsafe
The typed buffers are slices: words/floats/fixeds/doubles/pointers(n) make
zeroed, bounds-checked []int/[]float/... and the type names mean them. buffer(n)
is a []byte, with text_of, Fs.read_bytes/write_bytes and view(xs, start, n).
bytes(), indexing a raw pointer or bytes, free, resize, Memory.*, raw file calls,
data_of and C externs are refused outside unsafe { } / unsafe function, and a
project's own files may write unsafe only with --unsafe; the runtime and packages
are the platform. A slice passed to an extern goes as its data.
What the change found: Sync's atomics on a slice header, words(n) uninitialised,
input's fixed axes in ints, truetype's fixed outlines as ints, skin matrices
typed int, gl_shader's source table made from raw bytes. render3d gets safe
entry points (safe_api.ludic). Rendering is byte-identical; a frame costs the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9259808f80
commit
b0b0b62bce
70 changed files with 69189 additions and 64569 deletions
30
LANGUAGE.md
30
LANGUAGE.md
|
|
@ -215,6 +215,36 @@ way in its own files. What is still built into the compiler is the namespaces th
|
||||||
`Math`, `Text`, `List`, `Vector`, `Color`, `Time`, `Date` - and the few methods that choose their
|
`Math`, `Text`, `List`, `Vector`, `Color`, `Time`, `Date` - and the few methods that choose their
|
||||||
target by an argument's type (`Audio.play` of a handle or a name).
|
target by an argument's type (`Audio.play` of a handle or a name).
|
||||||
|
|
||||||
|
### Memory is safe unless it says `unsafe`
|
||||||
|
|
||||||
|
The typed buffers are slices: `words(n)`, `floats(n)`, `fixeds(n)`, `doubles(n)` and
|
||||||
|
`pointers(n)` make `n` zeroed elements of a `[]int`, `[]float`, `[]fixed`, `[]double` or
|
||||||
|
`[]pointer`, and the type names `words`, `floats` and the rest mean those slices. Every index is
|
||||||
|
checked against the length, so running off the end stops the program at that line instead of
|
||||||
|
writing into whatever lies next. `buffer(n)` is `n` zeroed bytes, a `[]byte`; `text_of(b, n)` makes
|
||||||
|
text of the first `n`; `Fs.read_bytes(path)` and `Fs.write_bytes(path, b, n)` move them to and from
|
||||||
|
a file; `view(xs, start, count)` is part of a slice sharing its storage, checked once when it is
|
||||||
|
made (make one where the buffer is made - each is a small allocation).
|
||||||
|
|
||||||
|
What is left is raw memory, and is refused outside `unsafe { }` or an `unsafe function`:
|
||||||
|
`bytes(n)`, indexing a `pointer` or `bytes`, `free`, `resize`, `Memory.*`, `file_read` and
|
||||||
|
`file_write`, `data_of(xs)` (a slice's first element, for C), and calling an `extern` C function.
|
||||||
|
A slice passed to an extern goes as its elements' address, never its header.
|
||||||
|
|
||||||
|
`unsafe` itself is for the platform: the runtime, a package from the toolchain or
|
||||||
|
`ludic_modules`, and what those import from beside them, all of which are unsafe throughout. A
|
||||||
|
project's own files may write it only when the build says `--unsafe` (`ludic build --unsafe`) -
|
||||||
|
the compiler and its tools build that way; a game is written against APIs and does not.
|
||||||
|
|
||||||
|
```ludic
|
||||||
|
# doc-check: skip — a fragment
|
||||||
|
let px = buffer(w * h * 3) # a []byte: bounds-checked
|
||||||
|
px[0] = 255
|
||||||
|
Fs.write_bytes("shot.raw", px, len(px))
|
||||||
|
let hp = floats(3) # a []float
|
||||||
|
hp[2] = 1.5
|
||||||
|
```
|
||||||
|
|
||||||
## Models (entity kinds)
|
## Models (entity kinds)
|
||||||
|
|
||||||
An `model` names a *kind* of entity and the fixed set of properties it
|
An `model` names a *kind* of entity and the fixed set of properties it
|
||||||
|
|
|
||||||
14
changes/unsafe.md
Normal file
14
changes/unsafe.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
bump: minor
|
||||||
|
type: feature
|
||||||
|
**Memory is safe unless it says `unsafe` (L7).** `words(n)`, `floats(n)`, `fixeds(n)`, `doubles(n)`
|
||||||
|
and `pointers(n)` are bounds-checked, zero-filled slices now, and the type names mean those slices;
|
||||||
|
`buffer(n)` is a `[]byte`, with `text_of`, `Fs.read_bytes`, `Fs.write_bytes` and `view(xs, start,
|
||||||
|
count)` beside it. `bytes(n)`, indexing a raw pointer, `free`, `resize`, `Memory.*`, the raw file
|
||||||
|
calls, `data_of` and calling a C `extern` are refused outside `unsafe { }` / `unsafe function`, and
|
||||||
|
a project's own files may write `unsafe` only with `--unsafe`; the runtime and packages are the
|
||||||
|
platform and are unsafe throughout. A slice passed to an extern goes as its data.
|
||||||
|
|
||||||
|
Making the buffers slices found, and this release fixes: `Sync`'s atomics operating on a slice's
|
||||||
|
header rather than its cell; `words(n)` handing back uninitialised memory; `input.ludic` keeping a
|
||||||
|
stick's fixed axes in an int buffer; truetype's fixed outlines allocated as ints; skinning's float
|
||||||
|
matrices typed as ints. Rendering is byte-identical, and a frame costs the same.
|
||||||
|
|
@ -7,55 +7,57 @@
|
||||||
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||||
program TiledP0 {
|
program TiledP0 {
|
||||||
entry {
|
entry {
|
||||||
# --- base64 decode: RFC 4648 test vectors (every padding remainder) ---
|
unsafe { # raw buffers on purpose: built with --unsafe (L7)
|
||||||
if Base64.decode("") == "" { print(1) }
|
# --- base64 decode: RFC 4648 test vectors (every padding remainder) ---
|
||||||
if Base64.decode("Zg==") == "f" { print(2) }
|
if Base64.decode("") == "" { print(1) }
|
||||||
if Base64.decode("Zm8=") == "fo" { print(3) }
|
if Base64.decode("Zg==") == "f" { print(2) }
|
||||||
if Base64.decode("Zm9v") == "foo" { print(4) }
|
if Base64.decode("Zm8=") == "fo" { print(3) }
|
||||||
if Base64.decode("Zm9vYg==") == "foob" { print(5) }
|
if Base64.decode("Zm9v") == "foo" { print(4) }
|
||||||
if Base64.decode("Zm9vYmE=") == "fooba" { print(6) }
|
if Base64.decode("Zm9vYg==") == "foob" { print(5) }
|
||||||
if Base64.decode("Zm9vYmFy") == "foobar" { print(7) }
|
if Base64.decode("Zm9vYmE=") == "fooba" { print(6) }
|
||||||
# decode ignores embedded whitespace/newlines (as TMX <data> carries)
|
if Base64.decode("Zm9vYmFy") == "foobar" { print(7) }
|
||||||
if Base64.decode("Zm9v\n YmFy") == "foobar" { print(8) }
|
# decode ignores embedded whitespace/newlines (as TMX <data> carries)
|
||||||
|
if Base64.decode("Zm9v\n YmFy") == "foobar" { print(8) }
|
||||||
|
|
||||||
# --- base64 encode round-trips ---
|
# --- base64 encode round-trips ---
|
||||||
if Base64.encode("foobar") == "Zm9vYmFy" { print(9) }
|
if Base64.encode("foobar") == "Zm9vYmFy" { print(9) }
|
||||||
if Base64.encode("f") == "Zg==" { print(10) }
|
if Base64.encode("f") == "Zg==" { print(10) }
|
||||||
if Base64.decode(Base64.encode("the quick brown fox")) == "the quick brown fox" { print(11) }
|
if Base64.decode(Base64.encode("the quick brown fox")) == "the quick brown fox" { print(11) }
|
||||||
|
|
||||||
# --- XML reader over a TSX element tree ---
|
# --- XML reader over a TSX element tree ---
|
||||||
let tsx = "<?xml version='1.0' encoding='UTF-8'?>\n<!-- a comment -->\n<tileset version='1.10' name='sheet & co' tilewidth='16' tileheight='16' tilecount='4' columns='2'>\n <image source='sheet.png' width='32' height='32'/>\n <tile id='0'><properties><property name='solid' type='bool' value='true'/></properties></tile>\n <data encoding='csv'>1,2,3,4</data>\n</tileset>\n"
|
let tsx = "<?xml version='1.0' encoding='UTF-8'?>\n<!-- a comment -->\n<tileset version='1.10' name='sheet & co' tilewidth='16' tileheight='16' tilecount='4' columns='2'>\n <image source='sheet.png' width='32' height='32'/>\n <tile id='0'><properties><property name='solid' type='bool' value='true'/></properties></tile>\n <data encoding='csv'>1,2,3,4</data>\n</tileset>\n"
|
||||||
let root = Xml.parse(tsx)
|
let root = Xml.parse(tsx)
|
||||||
if Xml.tag(root) == "tileset" { print(12) }
|
if Xml.tag(root) == "tileset" { print(12) }
|
||||||
if Xml.attr(root, "name") == "sheet & co" { print(13) } # entity decode
|
if Xml.attr(root, "name") == "sheet & co" { print(13) } # entity decode
|
||||||
if Xml.attr_int(root, "tilewidth", 0) == 16 { print(14) }
|
if Xml.attr_int(root, "tilewidth", 0) == 16 { print(14) }
|
||||||
if Xml.attr_int(root, "tilecount", 0) == 4 { print(15) }
|
if Xml.attr_int(root, "tilecount", 0) == 4 { print(15) }
|
||||||
let img = Xml.find(root, "image")
|
let img = Xml.find(root, "image")
|
||||||
if Xml.attr(img, "source") == "sheet.png" { print(16) }
|
if Xml.attr(img, "source") == "sheet.png" { print(16) }
|
||||||
let tile = Xml.find(root, "tile")
|
let tile = Xml.find(root, "tile")
|
||||||
let props = Xml.find(tile, "properties")
|
let props = Xml.find(tile, "properties")
|
||||||
let prop = Xml.find(props, "property")
|
let prop = Xml.find(props, "property")
|
||||||
if Xml.attr(prop, "value") == "true" { print(17) }
|
if Xml.attr(prop, "value") == "true" { print(17) }
|
||||||
let data = Xml.find(root, "data")
|
let data = Xml.find(root, "data")
|
||||||
if Xml.text(data) == "1,2,3,4" { print(18) }
|
if Xml.text(data) == "1,2,3,4" { print(18) }
|
||||||
|
|
||||||
# --- gzip framing: base64 -> bytes -> gunzip round-trips ---
|
# --- gzip framing: base64 -> bytes -> gunzip round-trips ---
|
||||||
let gz = "H4sIAAAAAAAC//NIzcnJ11EIycxJTVFIr8osUCjPL8pJUVQwMDQyNjE1M7ewVCjJSFUoLM1MzlZIKsovz1NIy6/QAwCLdXOzOAAAAA=="
|
let gz = "H4sIAAAAAAAC//NIzcnJ11EIycxJTVFIr8osUCjPL8pJUVQwMDQyNjE1M7ewVCjJSFUoLM1MzlZIKsovz1NIy6/QAwCLdXOzOAAAAA=="
|
||||||
let comp = bytes(256)
|
let comp = bytes(256)
|
||||||
let clen = b64_decode(gz, comp)
|
let clen = b64_decode(gz, comp)
|
||||||
let out = bytes(256)
|
let out = bytes(256)
|
||||||
let n = z_gunzip(comp, clen, out, 256)
|
let n = z_gunzip(comp, clen, out, 256)
|
||||||
out[n] = 0
|
out[n] = 0
|
||||||
if n == 56 { print(19) }
|
if n == 56 { print(19) }
|
||||||
if out == "Hello, Tiled gzip world! 0123456789 the quick brown fox." { print(20) }
|
if out == "Hello, Tiled gzip world! 0123456789 the quick brown fox." { print(20) }
|
||||||
|
|
||||||
# --- zlib still round-trips through the same path (regression) ---
|
# --- zlib still round-trips through the same path (regression) ---
|
||||||
let zl = "eJzzSM3JyddRCMnMSU1RSK/KLFAozy/KSVFUMDA0MjYxNTO3sFQoyUhVKCzNTM5WSCrKL89TSMuv0AMAA10SJA=="
|
let zl = "eJzzSM3JyddRCMnMSU1RSK/KLFAozy/KSVFUMDA0MjYxNTO3sFQoyUhVKCzNTM5WSCrKL89TSMuv0AMAA10SJA=="
|
||||||
let zc = bytes(256)
|
let zc = bytes(256)
|
||||||
let zclen = b64_decode(zl, zc)
|
let zclen = b64_decode(zl, zc)
|
||||||
let zout = bytes(256)
|
let zout = bytes(256)
|
||||||
let zn = z_uncompress(zc, zclen, zout, 256)
|
let zn = z_uncompress(zc, zclen, zout, 256)
|
||||||
zout[zn] = 0
|
zout[zn] = 0
|
||||||
if zout == "Hello, Tiled gzip world! 0123456789 the quick brown fox." { print(21) }
|
if zout == "Hello, Tiled gzip world! 0123456789 the quick brown fox." { print(21) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ program UdpDemo {
|
||||||
let pa = Udp.port(a)
|
let pa = Udp.port(a)
|
||||||
let pb = Udp.port(b)
|
let pb = Udp.port(b)
|
||||||
let lo = Udp.ip("127.0.0.1")
|
let lo = Udp.ip("127.0.0.1")
|
||||||
let out = Memory.bytes(16)
|
let out = buffer(16)
|
||||||
Memory.poke(out, 0, 104)
|
out[0] = 104
|
||||||
Memory.poke(out, 1, 105)
|
out[1] = 105
|
||||||
let sent = Udp.send(a, lo, pb, out, 2)
|
let sent = Udp.send(a, lo, pb, out, 2)
|
||||||
let inb = Memory.bytes(64)
|
let inb = buffer(64)
|
||||||
var got = 0
|
var got = 0
|
||||||
var tries = 0
|
var tries = 0
|
||||||
while got == 0 and tries < 200000 { got = Udp.recv(b, inb, 64); tries += 1 }
|
while got == 0 and tries < 200000 { got = Udp.recv(b, inb, 64); tries += 1 }
|
||||||
|
|
@ -20,7 +20,7 @@ program UdpDemo {
|
||||||
tries = 0
|
tries = 0
|
||||||
while back == 0 and tries < 200000 { back = Udp.recv(a, out, 16); tries += 1 }
|
while back == 0 and tries < 200000 { back = Udp.recv(a, out, 16); tries += 1 }
|
||||||
let bad = Udp.ip("1.2.3") == 0 and Udp.ip("300.1.1.1") == 0
|
let bad = Udp.ip("1.2.3") == 0 and Udp.ip("300.1.1.1") == 0
|
||||||
print(`{sent} {got} {Memory.peek(inb, 0)} {Memory.peek(inb, 1)} {from_ok} {back} {Udp.ip_text(lo)} {bad} {Udp.recv(a, out, 16)} {pa > 0}`)
|
print(`{sent} {got} {inb[0]} {inb[1]} {from_ok} {back} {Udp.ip_text(lo)} {bad} {Udp.recv(a, out, 16)} {pa > 0}`)
|
||||||
Udp.close(a)
|
Udp.close(a)
|
||||||
Udp.close(b)
|
Udp.close(b)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,19 @@
|
||||||
# them back through the loopback: prints 4, then 10 20 30 42.
|
# them back through the loopback: prints 4, then 10 20 30 42.
|
||||||
program NetEcho {
|
program NetEcho {
|
||||||
entry {
|
entry {
|
||||||
let out = bytes(4)
|
unsafe { # raw buffers on purpose: built with --unsafe (L7)
|
||||||
out[0] = '\n'
|
let out = bytes(4)
|
||||||
out[1] = 20
|
out[0] = '\n'
|
||||||
out[2] = 30
|
out[1] = 20
|
||||||
out[3] = '*'
|
out[2] = 30
|
||||||
net_send(0, out, 4) # onto the wire (the built-in loopback)
|
out[3] = '*'
|
||||||
|
net_send(0, out, 4) # onto the wire (the built-in loopback)
|
||||||
|
|
||||||
let inb = bytes(64)
|
let inb = bytes(64)
|
||||||
let n = net_poll(inb, 64) # take the next datagram back off
|
let n = net_poll(inb, 64) # take the next datagram back off
|
||||||
print(n) # 4
|
print(n) # 4
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < n { print(inb[i]); i += 1 } # 10 20 30 42
|
while i < n { print(inb[i]); i += 1 } # 10 20 30 42
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,17 @@ program NetSnapshot {
|
||||||
model Unit { Health }
|
model Unit { Health }
|
||||||
|
|
||||||
entry {
|
entry {
|
||||||
spawn Unit { Health { hp: 50, max: 100 } }
|
unsafe { # raw buffers on purpose: built with --unsafe (L7)
|
||||||
let buf = bytes(world_size())
|
spawn Unit { Health { hp: 50, max: 100 } }
|
||||||
for (Health) in query [Health, {Unit}] {
|
let buf = bytes(world_size())
|
||||||
print(Health.hp) # 50
|
for (Health) in query [Health, {Unit}] {
|
||||||
let n = world_save(buf) # snapshot the whole world
|
print(Health.hp) # 50
|
||||||
Health.hp = 7
|
let n = world_save(buf) # snapshot the whole world
|
||||||
print(Health.hp) # 7
|
Health.hp = 7
|
||||||
world_load(buf, n) # roll the world back
|
print(Health.hp) # 7
|
||||||
print(Health.hp) # 50 — restored from bytes
|
world_load(buf, n) # roll the world back
|
||||||
|
print(Health.hp) # 50 — restored from bytes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,15 @@ program VkCompute {
|
||||||
|
|
||||||
var dev: pointer = null
|
var dev: pointer = null
|
||||||
|
|
||||||
function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() }
|
unsafe function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() }
|
||||||
function handle(out: bytes) -> long { return vk_get_i64(out, 0) }
|
unsafe function handle(out: bytes) -> long { return vk_get_i64(out, 0) }
|
||||||
function has_ext(props: bytes, n: int, want: string) -> bool {
|
unsafe function has_ext(props: bytes, n: int, want: string) -> bool {
|
||||||
for i in 0 .. n {
|
for i in 0 .. n {
|
||||||
if string(vk_at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)) == want { return true }
|
if string(vk_at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)) == want { return true }
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
function read_all(path: string) -> bytes {
|
unsafe function read_all(path: string) -> bytes {
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@ program VkProbe {
|
||||||
|
|
||||||
const API_1_0: int = 4194304 # VK_MAKE_API_VERSION(0, 1, 0, 0)
|
const API_1_0: int = 4194304 # VK_MAKE_API_VERSION(0, 1, 0, 0)
|
||||||
|
|
||||||
function api_text(v: int) -> string { return `{(v >> 22) & 127}.{(v >> 12) & 1023}.{v & 4095}` }
|
unsafe function api_text(v: int) -> string { return `{(v >> 22) & 127}.{(v >> 12) & 1023}.{v & 4095}` }
|
||||||
function yes(b: bool) -> string { if b { return "yes" }; return "no " }
|
unsafe function yes(b: bool) -> string { if b { return "yes" }; return "no " }
|
||||||
|
|
||||||
# a name in an array of VkExtensionProperties
|
# a name in an array of VkExtensionProperties
|
||||||
function has_ext(props: bytes, n: int, want: string) -> bool {
|
unsafe function has_ext(props: bytes, n: int, want: string) -> bool {
|
||||||
for i in 0 .. n {
|
for i in 0 .. n {
|
||||||
let at = vk_at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)
|
let at = vk_at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)
|
||||||
if string(at) == want { return true }
|
if string(at) == want { return true }
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ program VkResources {
|
||||||
model Anchor { Marker }
|
model Anchor { Marker }
|
||||||
|
|
||||||
var failures: int = 0
|
var failures: int = 0
|
||||||
function check(what: string, ok: bool) -> void {
|
unsafe function check(what: string, ok: bool) -> void {
|
||||||
if ok { print(`vulkan: {what} ok`) } else { print(`vulkan: {what} FAILED`); failures += 1 }
|
if ok { print(`vulkan: {what} ok`) } else { print(`vulkan: {what} FAILED`); failures += 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,16 @@ program VkTriangle {
|
||||||
const H: int = 240
|
const H: int = 240
|
||||||
const FMT: int = VK_FORMAT_R8G8B8A8_UNORM
|
const FMT: int = VK_FORMAT_R8G8B8A8_UNORM
|
||||||
|
|
||||||
function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() }
|
unsafe function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() }
|
||||||
function handle(out: bytes) -> long { return vk_get_i64(out, 0) }
|
unsafe function handle(out: bytes) -> long { return vk_get_i64(out, 0) }
|
||||||
function has_ext(props: bytes, n: int, want: string) -> bool {
|
unsafe function has_ext(props: bytes, n: int, want: string) -> bool {
|
||||||
for i in 0 .. n {
|
for i in 0 .. n {
|
||||||
if string(vk_at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)) == want { return true }
|
if string(vk_at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)) == want { return true }
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
var spv_len: int = 0
|
var spv_len: int = 0
|
||||||
function read_all(path: string) -> bytes {
|
unsafe function read_all(path: string) -> bytes {
|
||||||
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)
|
||||||
|
|
@ -43,7 +43,7 @@ program VkTriangle {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
# the first memory type the resource allows that has every property wanted
|
# the first memory type the resource allows that has every property wanted
|
||||||
function mem_type(mp: bytes, allowed: int, want: int) -> int {
|
unsafe function mem_type(mp: bytes, allowed: int, want: int) -> int {
|
||||||
for t in 0 .. vk_get_i32(mp, VkPhysicalDeviceMemoryProperties_memoryTypeCount) {
|
for t in 0 .. vk_get_i32(mp, VkPhysicalDeviceMemoryProperties_memoryTypeCount) {
|
||||||
let pf = vk_get_i32(mp, VkPhysicalDeviceMemoryProperties_memoryTypes + t * VkMemoryType_sizeof + VkMemoryType_propertyFlags)
|
let pf = vk_get_i32(mp, VkPhysicalDeviceMemoryProperties_memoryTypes + t * VkMemoryType_sizeof + VkMemoryType_propertyFlags)
|
||||||
if ((allowed >> t) & 1) == 1 and (pf & want) == want { return t }
|
if ((allowed >> t) & 1) == 1 and (pf & want) == want { return t }
|
||||||
|
|
@ -51,14 +51,14 @@ program VkTriangle {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
# one colour image, one mip, one layer
|
# one colour image, one mip, one layer
|
||||||
function color_range(at: bytes, off: int) -> void {
|
unsafe function color_range(at: bytes, off: int) -> void {
|
||||||
vk_put_i32(at, off + VkImageSubresourceRange_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT)
|
vk_put_i32(at, off + VkImageSubresourceRange_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT)
|
||||||
vk_put_i32(at, off + VkImageSubresourceRange_baseMipLevel, 0)
|
vk_put_i32(at, off + VkImageSubresourceRange_baseMipLevel, 0)
|
||||||
vk_put_i32(at, off + VkImageSubresourceRange_levelCount, 1)
|
vk_put_i32(at, off + VkImageSubresourceRange_levelCount, 1)
|
||||||
vk_put_i32(at, off + VkImageSubresourceRange_baseArrayLayer, 0)
|
vk_put_i32(at, off + VkImageSubresourceRange_baseArrayLayer, 0)
|
||||||
vk_put_i32(at, off + VkImageSubresourceRange_layerCount, 1)
|
vk_put_i32(at, off + VkImageSubresourceRange_layerCount, 1)
|
||||||
}
|
}
|
||||||
function barrier(cb: pointer, image: long, old_layout: int, new_layout: int, src_access: int, dst_access: int, src_stage: int, dst_stage: int) -> void {
|
unsafe function barrier(cb: pointer, image: long, old_layout: int, new_layout: int, src_access: int, dst_access: int, src_stage: int, dst_stage: int) -> void {
|
||||||
let b = bytes(VkImageMemoryBarrier_sizeof)
|
let b = bytes(VkImageMemoryBarrier_sizeof)
|
||||||
vk_zero(b, VkImageMemoryBarrier_sizeof)
|
vk_zero(b, VkImageMemoryBarrier_sizeof)
|
||||||
vk_put_i32(b, VkImageMemoryBarrier_sType, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER)
|
vk_put_i32(b, VkImageMemoryBarrier_sType, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER)
|
||||||
|
|
@ -72,7 +72,7 @@ program VkTriangle {
|
||||||
color_range(b, VkImageMemoryBarrier_subresourceRange)
|
color_range(b, VkImageMemoryBarrier_subresourceRange)
|
||||||
vk_cmd_pipeline_barrier(cb, src_stage, dst_stage, 0, 0, null, 0, null, 1, b)
|
vk_cmd_pipeline_barrier(cb, src_stage, dst_stage, 0, 0, null, 0, null, 1, b)
|
||||||
}
|
}
|
||||||
function shader_module(dev: pointer, path: string) -> long {
|
unsafe function shader_module(dev: pointer, path: string) -> long {
|
||||||
let spv = read_all(path)
|
let spv = read_all(path)
|
||||||
if spv == null { print(`vulkan: no SPIR-V at {path} (compile vk_triangle.slang with slangc)`); quit() }
|
if spv == null { print(`vulkan: no SPIR-V at {path} (compile vk_triangle.slang with slangc)`); quit() }
|
||||||
let smci = bytes(VkShaderModuleCreateInfo_sizeof)
|
let smci = bytes(VkShaderModuleCreateInfo_sizeof)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ var prefs_n: int = 0
|
||||||
namespace Prefs {
|
namespace Prefs {
|
||||||
internal function init() -> void {
|
internal function init() -> void {
|
||||||
if prefs_names != null { return }
|
if prefs_names != null { return }
|
||||||
prefs_names = bytes(PREFS_MAX * 8)
|
prefs_names = pointers(PREFS_MAX)
|
||||||
prefs_vals = words(PREFS_MAX)
|
prefs_vals = words(PREFS_MAX)
|
||||||
prefs_n = 0
|
prefs_n = 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,17 @@ const F_HALF: int = 0x3F000000
|
||||||
const PI: float = 3.1415927
|
const PI: float = 3.1415927
|
||||||
const F_PI: int = 0x40490FDB
|
const F_PI: int = 0x40490FDB
|
||||||
|
|
||||||
|
# the n 4x4 matrices laid end to end in `m`, a view of each. Views are made once, where their
|
||||||
|
# buffer is: a view is a small allocation, and one per matrix per frame is one nobody frees.
|
||||||
|
function m4_views(m: floats, n: int) -> [][]float {
|
||||||
|
let out = new [][]float
|
||||||
|
var i = 0
|
||||||
|
while i < n {
|
||||||
|
push(out, view(m, i * 16, 16))
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
function fl(x: fixed) -> float { return float(x) }
|
function fl(x: fixed) -> float { return float(x) }
|
||||||
function fi(n: int) -> float { return float(n) }
|
function fi(n: int) -> float { return float(n) }
|
||||||
function fr(n: int, d: int) -> float { return float(n) / float(d) }
|
function fr(n: int, d: int) -> float { return float(n) / float(d) }
|
||||||
|
|
|
||||||
|
|
@ -241,19 +241,19 @@ function gpu_screenshot(path: string) -> bool { if gpu_kind == GPU_VK { return g
|
||||||
var gpu_u_tmp: words = null
|
var gpu_u_tmp: words = null
|
||||||
function gpu_tmp() -> words { if gpu_u_tmp == null { gpu_u_tmp = words(4) }; return gpu_u_tmp }
|
function gpu_tmp() -> words { if gpu_u_tmp == null { gpu_u_tmp = words(4) }; return gpu_u_tmp }
|
||||||
# float bits (IEEE singles in an int), like every other number in the renderer
|
# float bits (IEEE singles in an int), like every other number in the renderer
|
||||||
function u_f(loc: int, v: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(v); gvk_u_set(loc, t, 4, 1); return }; let t = gpu_tmp(); t[0] = float_bits(v); gl_uniform1fv(loc, 1, t) }
|
function u_f(loc: int, v: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(v); gvk_u_set(loc, data_of(t), 4, 1); return }; let t = gpu_tmp(); t[0] = float_bits(v); gl_uniform1fv(loc, 1, t) }
|
||||||
function u_f2(loc: int, x: float, y: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gvk_u_set(loc, t, 8, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gl_uniform2fv(loc, 1, t) }
|
function u_f2(loc: int, x: float, y: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gvk_u_set(loc, data_of(t), 8, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gl_uniform2fv(loc, 1, t) }
|
||||||
function u_f3(loc: int, x: float, y: float, z: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gvk_u_set(loc, t, 12, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gl_uniform3fv(loc, 1, t) }
|
function u_f3(loc: int, x: float, y: float, z: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gvk_u_set(loc, data_of(t), 12, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gl_uniform3fv(loc, 1, t) }
|
||||||
function u_f4(loc: int, x: float, y: float, z: float, w: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gvk_u_set(loc, t, 16, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gl_uniform4fv(loc, 1, t) }
|
function u_f4(loc: int, x: float, y: float, z: float, w: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gvk_u_set(loc, data_of(t), 16, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gl_uniform4fv(loc, 1, t) }
|
||||||
function u_v3(loc: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 12, 1); return }; gl_uniform3fv(loc, 1, v) }
|
function u_v3(loc: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(v), 12, 1); return }; gl_uniform3fv(loc, 1, v) }
|
||||||
function u_fv(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 4, n); return }; gl_uniform1fv(loc, n, v) }
|
function u_fv(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(v), 4, n); return }; gl_uniform1fv(loc, n, v) }
|
||||||
# n vec4s from 4n float bits. Not u_fv with 4n: on Vulkan an array element is copied at the size
|
# n vec4s from 4n float bits. Not u_fv with 4n: on Vulkan an array element is copied at the size
|
||||||
# given and placed at the array's stride, so a vec4 array fed floats got one float per element -
|
# given and placed at the array's stride, so a vec4 array fed floats got one float per element -
|
||||||
# which drew the chunked grass with every tile at a nonsense corner and zero blades a cell.
|
# which drew the chunked grass with every tile at a nonsense corner and zero blades a cell.
|
||||||
function u_f4v(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 16, n); return }; gl_uniform4fv(loc, n, v) }
|
function u_f4v(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(v), 16, n); return }; gl_uniform4fv(loc, n, v) }
|
||||||
function u_mat4(loc: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, m, 64, 1); return }; gl_uniform_matrix4fv(loc, 1, 0, m) }
|
function u_mat4(loc: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(m), 64, 1); return }; gl_uniform_matrix4fv(loc, 1, 0, m) }
|
||||||
function u_mat4n(loc: int, n: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, m, 64, n); return }; gl_uniform_matrix4fv(loc, n, 0, m) }
|
function u_mat4n(loc: int, n: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(m), 64, n); return }; gl_uniform_matrix4fv(loc, n, 0, m) }
|
||||||
function u_i(loc: int, v: int) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = v; gvk_u_set(loc, t, 4, 1); return }; gl_uniform1i(loc, v) }
|
function u_i(loc: int, v: int) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = v; gvk_u_set(loc, data_of(t), 4, 1); return }; gl_uniform1i(loc, v) }
|
||||||
|
|
||||||
# ---- what this machine can do ----------------------------------------------------
|
# ---- what this machine can do ----------------------------------------------------
|
||||||
# The advanced graphics features are Windows features: the Vulkan renderer, ray tracing,
|
# The advanced graphics features are Windows features: the Vulkan renderer, ray tracing,
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ function grass_blade_mesh(rows: int) -> Mesh {
|
||||||
idx[q * 6] = b; idx[q * 6 + 1] = b + 1; idx[q * 6 + 2] = b + 2
|
idx[q * 6] = b; idx[q * 6 + 1] = b + 1; idx[q * 6 + 2] = b + 2
|
||||||
idx[q * 6 + 3] = b + 1; idx[q * 6 + 4] = b + 3; idx[q * 6 + 5] = b + 2
|
idx[q * 6 + 3] = b + 1; idx[q * 6 + 4] = b + 3; idx[q * 6 + 5] = b + 2
|
||||||
}
|
}
|
||||||
gpu_mesh_indices(m, idx, nq * 6 * 4, 4)
|
gpu_mesh_indices(m, data_of(idx), nq * 6 * 4, 4)
|
||||||
free(idx)
|
free(idx)
|
||||||
m.count = nq * 6
|
m.count = nq * 6
|
||||||
gpu_mesh_done(m)
|
gpu_mesh_done(m)
|
||||||
|
|
@ -270,7 +270,7 @@ function grass_flush() -> void {
|
||||||
if grass_n == 0 { return }
|
if grass_n == 0 { return }
|
||||||
var p = grass_prog
|
var p = grass_prog
|
||||||
let mesh = grass_mesh_live()
|
let mesh = grass_mesh_live()
|
||||||
if mesh { p = grass_mesh_prog } else { gpu_buffer_upload(grass_cmds, grass_n * 20, grass_rec, GPU_DYNAMIC) }
|
if mesh { p = grass_mesh_prog } else { gpu_buffer_upload(grass_cmds, grass_n * 20, data_of(grass_rec), GPU_DYNAMIC) }
|
||||||
for b in 0 .. grass_band_n {
|
for b in 0 .. grass_band_n {
|
||||||
let s = grass_band_start[b]
|
let s = grass_band_start[b]
|
||||||
var e = grass_n
|
var e = grass_n
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ function mesh_grid(n: int, half: float) -> Mesh {
|
||||||
k += 6
|
k += 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gpu_mesh_indices(m, idx, ni * 4, 4)
|
gpu_mesh_indices(m, data_of(idx), ni * 4, 4)
|
||||||
free(idx)
|
free(idx)
|
||||||
m.count = ni
|
m.count = ni
|
||||||
gpu_mesh_done(m)
|
gpu_mesh_done(m)
|
||||||
|
|
@ -81,7 +81,7 @@ function mesh_card() -> Mesh {
|
||||||
free(v)
|
free(v)
|
||||||
let idx = words(6)
|
let idx = words(6)
|
||||||
idx[0] = 0; idx[1] = 1; idx[2] = 2; idx[3] = 0; idx[4] = 2; idx[5] = 3
|
idx[0] = 0; idx[1] = 1; idx[2] = 2; idx[3] = 0; idx[4] = 2; idx[5] = 3
|
||||||
gpu_mesh_indices(m, idx, 24, 4)
|
gpu_mesh_indices(m, data_of(idx), 24, 4)
|
||||||
free(idx)
|
free(idx)
|
||||||
m.count = 6
|
m.count = 6
|
||||||
gpu_mesh_done(m)
|
gpu_mesh_done(m)
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,17 @@ function q_rotate(o: floats, q: floats, v: floats) -> void {
|
||||||
}
|
}
|
||||||
# pitch about X, yaw about Y, roll about Z, composed as yaw * pitch * roll
|
# pitch about X, yaw about Y, roll about Z, composed as yaw * pitch * roll
|
||||||
var q_scratch: floats = null
|
var q_scratch: floats = null
|
||||||
|
var q_sy: floats = null # views of q_scratch's second, third and fourth
|
||||||
|
var q_sz: floats = null
|
||||||
|
var q_st: floats = null
|
||||||
function q_euler(o: floats, pitch: float, yaw: float, roll: float) -> void {
|
function q_euler(o: floats, pitch: float, yaw: float, roll: float) -> void {
|
||||||
if q_scratch == null { q_scratch = floats(16) }
|
if q_scratch == null {
|
||||||
let qx = q_scratch; let qy = mem_off(q_scratch, 16); let qz = mem_off(q_scratch, 32); let t = mem_off(q_scratch, 48)
|
q_scratch = floats(16)
|
||||||
|
q_sy = view(q_scratch, 4, 4)
|
||||||
|
q_sz = view(q_scratch, 8, 4)
|
||||||
|
q_st = view(q_scratch, 12, 4)
|
||||||
|
}
|
||||||
|
let qx = q_scratch; let qy = q_sy; let qz = q_sz; let t = q_st
|
||||||
q_axis_angle(qx, 1.0, 0.0, 0.0, pitch)
|
q_axis_angle(qx, 1.0, 0.0, 0.0, pitch)
|
||||||
q_axis_angle(qy, 0.0, 1.0, 0.0, yaw)
|
q_axis_angle(qy, 0.0, 1.0, 0.0, yaw)
|
||||||
q_axis_angle(qz, 0.0, 0.0, 1.0, roll)
|
q_axis_angle(qz, 0.0, 0.0, 1.0, roll)
|
||||||
|
|
|
||||||
|
|
@ -35,3 +35,4 @@ import "grass.ludic"
|
||||||
import "water.ludic"
|
import "water.ludic"
|
||||||
import "streamline.ludic"
|
import "streamline.ludic"
|
||||||
import "render.ludic"
|
import "render.ludic"
|
||||||
|
import "safe_api.ludic"
|
||||||
|
|
|
||||||
41
packages/ludic.render3d/safe_api.ludic
Normal file
41
packages/ludic.render3d/safe_api.ludic
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# safe_api.ludic — the renderer's raw buffers, as a program may hold them (L7). The renderer reads
|
||||||
|
# glTF accessors, the screen and PNG files into raw memory, which is its business; a game gets the
|
||||||
|
# same data as slices, bounds-checked, and never frees anything. Each of these copies or hands over
|
||||||
|
# at the boundary, so nothing raw crosses it.
|
||||||
|
|
||||||
|
# an accessor's float components (gltf_count elements of gltf_comps floats), as floats
|
||||||
|
function gltf_accessor_floats(idx: int) -> floats {
|
||||||
|
let p = gltf_accessor(idx)
|
||||||
|
let n = gltf_count * gltf_comps
|
||||||
|
let out = floats(n)
|
||||||
|
for i in 0 .. n { out[i] = float_from_bits(mem_get_f32_bits(p, i)) }
|
||||||
|
free(p)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
# how many elements an accessor holds, without reading them
|
||||||
|
function gltf_accessor_count(idx: int) -> int {
|
||||||
|
let acc = value_at(value_get(gltf_doc, "accessors"), idx)
|
||||||
|
return jint(acc, "count", 0)
|
||||||
|
}
|
||||||
|
# the presented frame as RGB8, bottom row first, into `out` (at least w * h * 3 bytes)
|
||||||
|
function gpu_read_screen_bytes(w: int, h: int, out: []byte) -> bool {
|
||||||
|
if out == null or len(out) < w * h * 3 { return false }
|
||||||
|
gpu_read_screen(w, h, data_of(out))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
# a PNG's samples (tex_w x tex_h x tex_channels, 8 or 16 bits): into `reuse` when it is large
|
||||||
|
# enough - a map swap decodes the same size again - else into a new buffer. null if unreadable.
|
||||||
|
function png_decode_bytes(path: string, reuse: []byte) -> []byte {
|
||||||
|
let p = png_decode(path)
|
||||||
|
if p == null { return null }
|
||||||
|
let n = tex_w * tex_h * tex_channels * (tex_depth / 8)
|
||||||
|
var out = reuse
|
||||||
|
if out == null or len(out) < n { out = buffer(n) }
|
||||||
|
for i in 0 .. n { out[i] = p[i] }
|
||||||
|
free(p)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
# upload samples laid out as the last decode left them (tex_w, tex_h, tex_channels, tex_depth)
|
||||||
|
function tex_upload_bytes(px: []byte, srgb: bool, mips: bool) -> int {
|
||||||
|
return tex_upload(data_of(px), srgb, mips)
|
||||||
|
}
|
||||||
|
|
@ -116,7 +116,7 @@ function model_cross_card() -> Model {
|
||||||
let idx = words(12)
|
let idx = words(12)
|
||||||
idx[0] = 0; idx[1] = 1; idx[2] = 2; idx[3] = 0; idx[4] = 2; idx[5] = 3
|
idx[0] = 0; idx[1] = 1; idx[2] = 2; idx[3] = 0; idx[4] = 2; idx[5] = 3
|
||||||
idx[6] = 4; idx[7] = 5; idx[8] = 6; idx[9] = 4; idx[10] = 6; idx[11] = 7
|
idx[6] = 4; idx[7] = 5; idx[8] = 6; idx[9] = 4; idx[10] = 6; idx[11] = 7
|
||||||
gpu_mesh_indices(m, idx, 48, 4)
|
gpu_mesh_indices(m, data_of(idx), 48, 4)
|
||||||
free(idx)
|
free(idx)
|
||||||
m.count = 12
|
m.count = 12
|
||||||
gpu_mesh_done(m)
|
gpu_mesh_done(m)
|
||||||
|
|
@ -194,7 +194,7 @@ function model_lupine() -> Model {
|
||||||
gpu_mesh_vertices(m, v, gl_bytes_of(nq * 4 * 8), GPU_STATIC)
|
gpu_mesh_vertices(m, v, gl_bytes_of(nq * 4 * 8), GPU_STATIC)
|
||||||
sc_model_layout(m)
|
sc_model_layout(m)
|
||||||
free(v)
|
free(v)
|
||||||
gpu_mesh_indices(m, idx, nq * 6 * 4, 4)
|
gpu_mesh_indices(m, data_of(idx), nq * 6 * 4, 4)
|
||||||
free(idx)
|
free(idx)
|
||||||
m.count = nq * 6
|
m.count = nq * 6
|
||||||
gpu_mesh_done(m)
|
gpu_mesh_done(m)
|
||||||
|
|
@ -267,7 +267,7 @@ function model_lupine_dense() -> Model {
|
||||||
gpu_mesh_vertices(m, v, gl_bytes_of(nq * 4 * 8), GPU_STATIC)
|
gpu_mesh_vertices(m, v, gl_bytes_of(nq * 4 * 8), GPU_STATIC)
|
||||||
sc_model_layout(m)
|
sc_model_layout(m)
|
||||||
free(v)
|
free(v)
|
||||||
gpu_mesh_indices(m, idx, nq * 6 * 4, 4)
|
gpu_mesh_indices(m, data_of(idx), nq * 6 * 4, 4)
|
||||||
free(idx)
|
free(idx)
|
||||||
m.count = nq * 6
|
m.count = nq * 6
|
||||||
gpu_mesh_done(m)
|
gpu_mesh_done(m)
|
||||||
|
|
@ -315,7 +315,7 @@ function model_blade() -> Model {
|
||||||
idx[k + 3] = a + 1; idx[k + 4] = a + 3; idx[k + 5] = a + 2
|
idx[k + 3] = a + 1; idx[k + 4] = a + 3; idx[k + 5] = a + 2
|
||||||
k += 6
|
k += 6
|
||||||
}
|
}
|
||||||
gpu_mesh_indices(m, idx, ni * 4, 4)
|
gpu_mesh_indices(m, data_of(idx), ni * 4, 4)
|
||||||
free(idx)
|
free(idx)
|
||||||
m.count = ni
|
m.count = ni
|
||||||
gpu_mesh_done(m)
|
gpu_mesh_done(m)
|
||||||
|
|
@ -667,7 +667,7 @@ function layer_gpu_prepare(l: Layer) -> bool {
|
||||||
}
|
}
|
||||||
let n = l.n_lods
|
let n = l.n_lods
|
||||||
let cap = l.count
|
let cap = l.count
|
||||||
gpu_buffer_upload(l.g_src, cap * INST_FLOATS * 4, l.inst, GPU_STATIC)
|
gpu_buffer_upload(l.g_src, cap * INST_FLOATS * 4, data_of(l.inst), GPU_STATIC)
|
||||||
gpu_buffer_upload(l.g_dst, (n + 1) * cap * INST_FLOATS * 4, null, GPU_DYNAMIC)
|
gpu_buffer_upload(l.g_dst, (n + 1) * cap * INST_FLOATS * 4, null, GPU_DYNAMIC)
|
||||||
if l.g_arena == null { layer_arena_build(l) }
|
if l.g_arena == null { layer_arena_build(l) }
|
||||||
let n_mat = len(l.g_arena)
|
let n_mat = len(l.g_arena)
|
||||||
|
|
@ -690,14 +690,14 @@ function layer_gpu_prepare(l: Layer) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gpu_buffer_upload(l.g_cmds, SC_RECS * SC_REC_W, rec, GPU_DYNAMIC)
|
gpu_buffer_upload(l.g_cmds, SC_RECS * SC_REC_W, data_of(rec), GPU_DYNAMIC)
|
||||||
let zeros = words(5)
|
let zeros = words(5)
|
||||||
for i in 0 .. 5 { zeros[i] = 0 }
|
for i in 0 .. 5 { zeros[i] = 0 }
|
||||||
gpu_buffer_upload(l.g_counts, 20, zeros, GPU_DYNAMIC)
|
gpu_buffer_upload(l.g_counts, 20, data_of(zeros), GPU_DYNAMIC)
|
||||||
free(rec); free(zeros)
|
free(rec); free(zeros)
|
||||||
# the card casts every instance, as on the CPU path (layer_grid_build uploads this there)
|
# the card casts every instance, as on the CPU path (layer_grid_build uploads this there)
|
||||||
l.n_sh = l.count
|
l.n_sh = l.count
|
||||||
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_STATIC)
|
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_STATIC)
|
||||||
l.g_n = l.count
|
l.g_n = l.count
|
||||||
l.g_on = true
|
l.g_on = true
|
||||||
return true
|
return true
|
||||||
|
|
@ -715,7 +715,7 @@ function layer_gpu_cull(l: Layer) -> void {
|
||||||
pr[32] = float_bits(l.lods[0].height * 2.0); pr[33] = float_bits(4.0)
|
pr[32] = float_bits(l.lods[0].height * 2.0); pr[33] = float_bits(4.0)
|
||||||
let bufs = words(4)
|
let bufs = words(4)
|
||||||
bufs[0] = l.g_src; bufs[1] = l.g_dst; bufs[2] = l.g_cmds; bufs[3] = l.g_counts
|
bufs[0] = l.g_src; bufs[1] = l.g_dst; bufs[2] = l.g_cmds; bufs[3] = l.g_counts
|
||||||
gpu_dispatch(sc_cull_prog, pr, 144, bufs, 1)
|
gpu_dispatch(sc_cull_prog, data_of(pr), 144, bufs, 1)
|
||||||
free(pr); free(bufs)
|
free(pr); free(bufs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -762,7 +762,7 @@ function layer_grid_build(l: Layer, cell: float) -> void {
|
||||||
free(cellof); free(fill)
|
free(cellof); free(fill)
|
||||||
if l.vis == null { l.vis = floats(l.cap * INST_FLOATS) }
|
if l.vis == null { l.vis = floats(l.cap * INST_FLOATS) }
|
||||||
l.n_sh = l.count
|
l.n_sh = l.count
|
||||||
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_STATIC)
|
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_STATIC)
|
||||||
}
|
}
|
||||||
|
|
||||||
# gather the instances of the cells the camera can see (and that are within cull)
|
# gather the instances of the cells the camera can see (and that are within cull)
|
||||||
|
|
@ -848,7 +848,7 @@ function layer_partition_lods(l: Layer, src: floats, total: int) -> void {
|
||||||
# casters: the whole (gathered) set from the shadow buffer, unless the impostor casts
|
# casters: the whole (gathered) set from the shadow buffer, unless the impostor casts
|
||||||
if l.gcell == 0.0 {
|
if l.gcell == 0.0 {
|
||||||
l.n_sh = total
|
l.n_sh = total
|
||||||
if total > 0 { gpu_buffer_upload(l.sh_buf, total * INST_FLOATS * 4, src, GPU_DYNAMIC) }
|
if total > 0 { gpu_buffer_upload(l.sh_buf, total * INST_FLOATS * 4, data_of(src), GPU_DYNAMIC) }
|
||||||
}
|
}
|
||||||
free(counts); free(start); free(fill)
|
free(counts); free(start); free(fill)
|
||||||
}
|
}
|
||||||
|
|
@ -866,7 +866,7 @@ function layer_update(l: Layer) -> void {
|
||||||
# per-instance loop — one upload, and the same buffer casts its shadows.
|
# per-instance loop — one upload, and the same buffer casts its shadows.
|
||||||
if l.streamed and l.imp == null and l.near == 0.0 and l.n_lods <= 1 {
|
if l.streamed and l.imp == null and l.near == 0.0 and l.n_lods <= 1 {
|
||||||
l.n_near = l.count; l.n_far = 0; l.n_sh = l.count
|
l.n_near = l.count; l.n_far = 0; l.n_sh = l.count
|
||||||
gpu_buffer_upload(l.buf, l.count * INST_FLOATS * 4, l.inst, GPU_DYNAMIC)
|
gpu_buffer_upload(l.buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_DYNAMIC)
|
||||||
prof_layer_add(gl_now_us() - t_lu, l.count * INST_FLOATS * 4)
|
prof_layer_add(gl_now_us() - t_lu, l.count * INST_FLOATS * 4)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -916,10 +916,10 @@ function layer_update(l: Layer) -> void {
|
||||||
if l.gcell == 0.0 {
|
if l.gcell == 0.0 {
|
||||||
l.n_sh = l.count
|
l.n_sh = l.count
|
||||||
if l.count > 0 {
|
if l.count > 0 {
|
||||||
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_DYNAMIC)
|
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_DYNAMIC)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gpu_buffer_upload(l.buf, nn * INST_FLOATS * 4, tmp, GPU_DYNAMIC)
|
gpu_buffer_upload(l.buf, nn * INST_FLOATS * 4, data_of(tmp), GPU_DYNAMIC)
|
||||||
if nf > 0 {
|
if nf > 0 {
|
||||||
gpu_buffer_upload(l.imp_buf, nf * INST_FLOATS * 4, mem_off(tmp, (far_off - nf * INST_FLOATS) * 4), GPU_DYNAMIC)
|
gpu_buffer_upload(l.imp_buf, nf * INST_FLOATS * 4, mem_off(tmp, (far_off - nf * INST_FLOATS) * 4), GPU_DYNAMIC)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ const SHADOW_CASCADES: int = 5
|
||||||
|
|
||||||
var sh_tex: int = 0
|
var sh_tex: int = 0
|
||||||
var sh_fbo: int = 0
|
var sh_fbo: int = 0
|
||||||
var sh_vp: floats = null # 4 x 16 float bits
|
var sh_vp: floats = null # the cascades' view-projections, 16 floats each
|
||||||
|
var sh_vp_v: [][]float = null # a view of each
|
||||||
var sh_split: floats = null # view-space far distance of each cascade
|
var sh_split: floats = null # view-space far distance of each cascade
|
||||||
var sh_range: floats = null # 4 light-frustum depth extents (metres)
|
var sh_range: floats = null # 4 light-frustum depth extents (metres)
|
||||||
var sh_texel: floats = null # 4 shadow texel sizes (metres)
|
var sh_texel: floats = null # 4 shadow texel sizes (metres)
|
||||||
|
|
@ -29,6 +30,7 @@ function shadow_init() -> void {
|
||||||
gpu_fb_no_color()
|
gpu_fb_no_color()
|
||||||
gpu_fb_bind(0)
|
gpu_fb_bind(0)
|
||||||
sh_vp = floats(16 * SHADOW_CASCADES)
|
sh_vp = floats(16 * SHADOW_CASCADES)
|
||||||
|
sh_vp_v = m4_views(sh_vp, SHADOW_CASCADES)
|
||||||
sh_split = floats(SHADOW_CASCADES)
|
sh_split = floats(SHADOW_CASCADES)
|
||||||
sh_range = floats(SHADOW_CASCADES)
|
sh_range = floats(SHADOW_CASCADES)
|
||||||
sh_texel = floats(SHADOW_CASCADES)
|
sh_texel = floats(SHADOW_CASCADES)
|
||||||
|
|
@ -141,7 +143,7 @@ function shadow_fit(c: int, near: float, far: float) -> void {
|
||||||
free(out); free(eye); free(up); free(center); free(corners)
|
free(out); free(eye); free(up); free(center); free(corners)
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadow_cascade_vp(c: int) -> floats { return mem_off(sh_vp, c * 64) }
|
function shadow_cascade_vp(c: int) -> floats { return sh_vp_v[c] }
|
||||||
|
|
||||||
|
|
||||||
# render every cascade; `draw` happens through terrain_draw_shadow + the scene's casters
|
# render every cascade; `draw` happens through terrain_draw_shadow + the scene's casters
|
||||||
|
|
@ -211,7 +213,7 @@ function shadow_dump() -> void {
|
||||||
let buf = floats(n * SHADOW_CASCADES)
|
let buf = floats(n * SHADOW_CASCADES)
|
||||||
gpu_tex_bind(GPU_TEX2D_ARRAY, sh_tex)
|
gpu_tex_bind(GPU_TEX2D_ARRAY, sh_tex)
|
||||||
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_NONE)
|
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_NONE)
|
||||||
gpu_tex_read(GPU_TEX2D_ARRAY, GL_DEPTH_COMPONENT, GL_FLOAT, buf)
|
gpu_tex_read(GPU_TEX2D_ARRAY, GL_DEPTH_COMPONENT, GL_FLOAT, data_of(buf))
|
||||||
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE)
|
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE)
|
||||||
if sh_probe_x != 0.0 {
|
if sh_probe_x != 0.0 {
|
||||||
let vp = shadow_cascade_vp(2)
|
let vp = shadow_cascade_vp(2)
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,14 @@ property Skin {
|
||||||
names: []string,
|
names: []string,
|
||||||
pose_r: floats, # 4 per node: the pose rotation, model frame
|
pose_r: floats, # 4 per node: the pose rotation, model frame
|
||||||
pose_t: floats, # 3 per node: the pose offset, model frame (metres)
|
pose_t: floats, # 3 per node: the pose offset, model frame (metres)
|
||||||
gmat: words, # 16 per node: global matrix this pose
|
gmat: floats, # 16 per node: global matrix this pose
|
||||||
|
gmat_v: [][]float, # a view of each node's matrix in gmat
|
||||||
n_joints: int = 0,
|
n_joints: int = 0,
|
||||||
joints: words, # node index per joint
|
joints: words, # node index per joint
|
||||||
inv_bind: words, # 16 per joint
|
inv_bind: floats, # 16 per joint
|
||||||
|
inv_v: [][]float, # a view of each joint's inverse bind matrix
|
||||||
bones: floats, # 16 per joint: what the vertex shader skins with
|
bones: floats, # 16 per joint: what the vertex shader skins with
|
||||||
|
bones_v: [][]float, # a view of each joint's matrix in bones
|
||||||
tmp_l: floats,
|
tmp_l: floats,
|
||||||
tmp_q: floats,
|
tmp_q: floats,
|
||||||
tmp_a: floats,
|
tmp_a: floats,
|
||||||
|
|
@ -76,7 +79,8 @@ function skin_load(idx: int) -> Skin {
|
||||||
sk.n_nodes = n
|
sk.n_nodes = n
|
||||||
sk.par = words(n); sk.walk = words(n)
|
sk.par = words(n); sk.walk = words(n)
|
||||||
sk.rest_t = floats(n * 3); sk.rest_r = floats(n * 4); sk.rest_s = floats(n * 3); sk.rest_g = floats(n * 4)
|
sk.rest_t = floats(n * 3); sk.rest_r = floats(n * 4); sk.rest_s = floats(n * 3); sk.rest_g = floats(n * 4)
|
||||||
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = words(n * 16)
|
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = floats(n * 16)
|
||||||
|
sk.gmat_v = m4_views(sk.gmat, n)
|
||||||
sk.names = new []string
|
sk.names = new []string
|
||||||
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = floats(3)
|
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = floats(3)
|
||||||
for i in 0 .. n { sk.par[i] = -1 }
|
for i in 0 .. n { sk.par[i] = -1 }
|
||||||
|
|
@ -123,15 +127,17 @@ function skin_load(idx: int) -> Skin {
|
||||||
if nj > SKIN_MAX_JOINTS { print(`skin: {nj} joints, only the first {SKIN_MAX_JOINTS} are used`); nj = SKIN_MAX_JOINTS }
|
if nj > SKIN_MAX_JOINTS { print(`skin: {nj} joints, only the first {SKIN_MAX_JOINTS} are used`); nj = SKIN_MAX_JOINTS }
|
||||||
sk.n_joints = nj
|
sk.n_joints = nj
|
||||||
sk.joints = words(nj)
|
sk.joints = words(nj)
|
||||||
sk.inv_bind = words(nj * 16)
|
sk.inv_bind = floats(nj * 16)
|
||||||
|
sk.inv_v = m4_views(sk.inv_bind, nj)
|
||||||
sk.bones = floats(nj * 16)
|
sk.bones = floats(nj * 16)
|
||||||
|
sk.bones_v = m4_views(sk.bones, nj)
|
||||||
for j in 0 .. nj { sk.joints[j] = value_as_int(value_at(jl, j)) }
|
for j in 0 .. nj { sk.joints[j] = value_as_int(value_at(jl, j)) }
|
||||||
if value_has(skv, "inverseBindMatrices") != 0 {
|
if value_has(skv, "inverseBindMatrices") != 0 {
|
||||||
let ib = gltf_accessor(value_as_int(value_get(skv, "inverseBindMatrices")))
|
let ib = gltf_accessor(value_as_int(value_get(skv, "inverseBindMatrices")))
|
||||||
for i in 0 .. nj * 16 { sk.inv_bind[i] = mem_get_f32_bits(ib, i) }
|
for i in 0 .. nj * 16 { sk.inv_bind[i] = float_from_bits(mem_get_f32_bits(ib, i)) }
|
||||||
free(ib)
|
free(ib)
|
||||||
} else {
|
} else {
|
||||||
for j in 0 .. nj { m4_identity(mem_off(sk.inv_bind, j * 64)) }
|
for j in 0 .. nj { m4_identity(sk.inv_v[j]) }
|
||||||
}
|
}
|
||||||
skin_reset(sk)
|
skin_reset(sk)
|
||||||
skin_pose(sk)
|
skin_pose(sk)
|
||||||
|
|
@ -144,7 +150,7 @@ function skin_find(sk: Skin, name: string) -> int {
|
||||||
print(`skin: no node {name}`)
|
print(`skin: no node {name}`)
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
function skin_mat(sk: Skin, node: int) -> floats { return mem_off(sk.gmat, node * 64) }
|
function skin_mat(sk: Skin, node: int) -> floats { return sk.gmat_v[node] }
|
||||||
|
|
||||||
# back to the rest pose
|
# back to the rest pose
|
||||||
function skin_reset(sk: Skin) -> void {
|
function skin_reset(sk: Skin) -> void {
|
||||||
|
|
@ -192,7 +198,7 @@ function skin_pose(sk: Skin) -> void {
|
||||||
else { m4_copy(skin_mat(sk, i), sk.tmp_l) }
|
else { m4_copy(skin_mat(sk, i), sk.tmp_l) }
|
||||||
}
|
}
|
||||||
for j in 0 .. sk.n_joints {
|
for j in 0 .. sk.n_joints {
|
||||||
m4_mul(mem_off(sk.bones, j * 64), skin_mat(sk, sk.joints[j]), mem_off(sk.inv_bind, j * 64))
|
m4_mul(sk.bones_v[j], skin_mat(sk, sk.joints[j]), sk.inv_v[j])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -209,10 +215,12 @@ function skin_clone(src: Skin) -> Skin {
|
||||||
sk.n_nodes = src.n_nodes; sk.par = src.par; sk.walk = src.walk
|
sk.n_nodes = src.n_nodes; sk.par = src.par; sk.walk = src.walk
|
||||||
sk.rest_t = src.rest_t; sk.rest_r = src.rest_r; sk.rest_s = src.rest_s; sk.rest_g = src.rest_g
|
sk.rest_t = src.rest_t; sk.rest_r = src.rest_r; sk.rest_s = src.rest_s; sk.rest_g = src.rest_g
|
||||||
sk.names = src.names
|
sk.names = src.names
|
||||||
sk.n_joints = src.n_joints; sk.joints = src.joints; sk.inv_bind = src.inv_bind
|
sk.n_joints = src.n_joints; sk.joints = src.joints; sk.inv_bind = src.inv_bind; sk.inv_v = src.inv_v
|
||||||
let n = src.n_nodes
|
let n = src.n_nodes
|
||||||
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = words(n * 16)
|
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = floats(n * 16)
|
||||||
|
sk.gmat_v = m4_views(sk.gmat, n)
|
||||||
sk.bones = floats(src.n_joints * 16)
|
sk.bones = floats(src.n_joints * 16)
|
||||||
|
sk.bones_v = m4_views(sk.bones, src.n_joints)
|
||||||
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = floats(3)
|
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = floats(3)
|
||||||
skin_reset(sk)
|
skin_reset(sk)
|
||||||
skin_pose(sk)
|
skin_pose(sk)
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,7 @@ function stream_update(s: Stream, cam_x: float, cam_z: float) -> void {
|
||||||
}
|
}
|
||||||
if c != null and c.count > 0 and l.count + c.count <= l.cap and stream_chunk_visible(s, cx, cz, c) {
|
if c != null and c.count > 0 and l.count + c.count <= l.cap and stream_chunk_visible(s, cx, cz, c) {
|
||||||
let tg = gl_now_us()
|
let tg = gl_now_us()
|
||||||
mem_copy(mem_off(l.inst, l.count * INST_FLOATS * 4), c.data, c.count * INST_FLOATS * 4)
|
mem_copy(mem_off(l.inst, l.count * INST_FLOATS * 4), data_of(c.data), c.count * INST_FLOATS * 4)
|
||||||
l.count += c.count
|
l.count += c.count
|
||||||
stream_us_gather = stream_us_gather + (gl_now_us() - tg)
|
stream_us_gather = stream_us_gather + (gl_now_us() - tg)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ function terrain_generate() -> void {
|
||||||
ter_heights = floats(TERRAIN_RES * TERRAIN_RES)
|
ter_heights = floats(TERRAIN_RES * TERRAIN_RES)
|
||||||
gpu_tex_bind(GPU_TEX2D, ter_height_tex)
|
gpu_tex_bind(GPU_TEX2D, ter_height_tex)
|
||||||
gpu_pixel_store(GL_PACK_ALIGNMENT, 4)
|
gpu_pixel_store(GL_PACK_ALIGNMENT, 4)
|
||||||
gpu_tex_read(GPU_TEX2D, GL_RED, GL_FLOAT, ter_heights)
|
gpu_tex_read(GPU_TEX2D, GL_RED, GL_FLOAT, data_of(ter_heights))
|
||||||
gpu_fb_bind(0)
|
gpu_fb_bind(0)
|
||||||
gpu_fb_free(fbo)
|
gpu_fb_free(fbo)
|
||||||
gpu_program_free(p)
|
gpu_program_free(p)
|
||||||
|
|
@ -663,7 +663,7 @@ function cdlod_init() -> void {
|
||||||
k += 6
|
k += 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gpu_mesh_indices(m, idx, ni * 4, 4)
|
gpu_mesh_indices(m, data_of(idx), ni * 4, 4)
|
||||||
free(idx)
|
free(idx)
|
||||||
m.count = ni
|
m.count = ni
|
||||||
gpu_mesh_done(m)
|
gpu_mesh_done(m)
|
||||||
|
|
|
||||||
|
|
@ -389,7 +389,7 @@ function tex_load_hdr(path: pointer) -> int {
|
||||||
let id = gpu_tex_new()
|
let id = gpu_tex_new()
|
||||||
gpu_tex_bind(GPU_TEX2D, id)
|
gpu_tex_bind(GPU_TEX2D, id)
|
||||||
gpu_pixel_store(GL_UNPACK_ALIGNMENT, 4)
|
gpu_pixel_store(GL_UNPACK_ALIGNMENT, 4)
|
||||||
gpu_tex_image2d(GL_RGB16F, tex_w, tex_h, GL_RGB, GL_FLOAT, px)
|
gpu_tex_image2d(GL_RGB16F, tex_w, tex_h, GL_RGB, GL_FLOAT, data_of(px))
|
||||||
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
||||||
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
||||||
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
||||||
|
|
@ -417,7 +417,7 @@ function tex_max(tex: int, w: int, h: int, tag: pointer) -> void {
|
||||||
let buf = floats(w * h * 4)
|
let buf = floats(w * h * 4)
|
||||||
gpu_tex_bind(GPU_TEX2D, tex)
|
gpu_tex_bind(GPU_TEX2D, tex)
|
||||||
gpu_pixel_store(GL_PACK_ALIGNMENT, 4)
|
gpu_pixel_store(GL_PACK_ALIGNMENT, 4)
|
||||||
gpu_tex_read(GPU_TEX2D, GL_RGBA, GL_FLOAT, buf)
|
gpu_tex_read(GPU_TEX2D, GL_RGBA, GL_FLOAT, data_of(buf))
|
||||||
var best = 0.0; var bx = 0; var by = 0
|
var best = 0.0; var bx = 0; var by = 0
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < w * h {
|
while i < w * h {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ var wt_wade_s: float = 0.0
|
||||||
var water_refl: Target = null # the world mirrored in the surface, half resolution
|
var water_refl: Target = null # the world mirrored in the surface, half resolution
|
||||||
var water_refl_div: int = 2 # R3D_REFLDIV overrides: 2 = half res, 4 = quarter
|
var water_refl_div: int = 2 # R3D_REFLDIV overrides: 2 = half res, 4 = quarter
|
||||||
var water_saved: floats = null # the real camera's matrices, restored after the pass
|
var water_saved: floats = null # the real camera's matrices, restored after the pass
|
||||||
|
var water_saved_vp: floats = null # views of its second and third matrices
|
||||||
|
var water_saved_ivp: floats = null
|
||||||
var water_dumped: bool = false
|
var water_dumped: bool = false
|
||||||
# Several still-water planes, each at its own level over its own bounds: the sea round an
|
# Several still-water planes, each at its own level over its own bounds: the sea round an
|
||||||
# island and a lake a hundred metres above it cannot be one surface. Each draws the same way;
|
# island and a lake a hundred metres above it cannot be one surface. Each draws the same way;
|
||||||
|
|
@ -49,11 +51,13 @@ function water_reflection_pass() -> void {
|
||||||
# rendered at the window's size would be paying for pixels the water never samples
|
# rendered at the window's size would be paying for pixels the water never samples
|
||||||
water_refl = target_new(post_w / water_refl_div, post_h / water_refl_div, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, true, GL_LINEAR)
|
water_refl = target_new(post_w / water_refl_div, post_h / water_refl_div, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, true, GL_LINEAR)
|
||||||
water_saved = floats(16 * 4 + 3)
|
water_saved = floats(16 * 4 + 3)
|
||||||
|
water_saved_vp = view(water_saved, 16, 16)
|
||||||
|
water_saved_ivp = view(water_saved, 32, 16)
|
||||||
}
|
}
|
||||||
# save the camera
|
# save the camera
|
||||||
m4_copy(water_saved, cam_view)
|
m4_copy(water_saved, cam_view)
|
||||||
m4_copy(mem_off(water_saved, 64), cam_vp)
|
m4_copy(water_saved_vp, cam_vp)
|
||||||
m4_copy(mem_off(water_saved, 128), cam_inv_vp)
|
m4_copy(water_saved_ivp, cam_inv_vp)
|
||||||
let sx = cam_pos[0]; let sy = cam_pos[1]; let sz = cam_pos[2]
|
let sx = cam_pos[0]; let sy = cam_pos[1]; let sz = cam_pos[2]
|
||||||
# the mirrored camera: view' = view * R, R reflecting y about the surface (y' = 2L - y).
|
# the mirrored camera: view' = view * R, R reflecting y about the surface (y' = 2L - y).
|
||||||
# R has determinant -1, so the winding flips (front faces culled below) and the image
|
# R has determinant -1, so the winding flips (front faces culled below) and the image
|
||||||
|
|
@ -98,8 +102,8 @@ function water_reflection_pass() -> void {
|
||||||
# restore
|
# restore
|
||||||
r3d_clip_y = -2147483600.0
|
r3d_clip_y = -2147483600.0
|
||||||
m4_copy(cam_view, water_saved)
|
m4_copy(cam_view, water_saved)
|
||||||
m4_copy(cam_vp, mem_off(water_saved, 64))
|
m4_copy(cam_vp, water_saved_vp)
|
||||||
m4_copy(cam_inv_vp, mem_off(water_saved, 128))
|
m4_copy(cam_inv_vp, water_saved_ivp)
|
||||||
v3_set(cam_pos, sx, sy, sz)
|
v3_set(cam_pos, sx, sy, sz)
|
||||||
free(eye); free(fwd); free(up); free(at)
|
free(eye); free(fwd); free(up); free(at)
|
||||||
gpu_fb_bind(0)
|
gpu_fb_bind(0)
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ var ing_qty: words = null
|
||||||
function craft_ensure() -> void {
|
function craft_ensure() -> void {
|
||||||
if recipe_reg == null {
|
if recipe_reg == null {
|
||||||
recipe_reg = Dict.new()
|
recipe_reg = Dict.new()
|
||||||
recipe_out = bytes(CRAFT_MAX * 8); recipe_outqty = words(CRAFT_MAX)
|
recipe_out = pointers(CRAFT_MAX); recipe_outqty = words(CRAFT_MAX)
|
||||||
ing_rid = words(CRAFT_ING_MAX); ing_name = bytes(CRAFT_ING_MAX * 8); ing_qty = words(CRAFT_ING_MAX)
|
ing_rid = words(CRAFT_ING_MAX); ing_name = pointers(CRAFT_ING_MAX); ing_qty = words(CRAFT_ING_MAX)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ function dlg_ensure() -> void {
|
||||||
if dlg_reg == null {
|
if dlg_reg == null {
|
||||||
dlg_reg = Dict.new()
|
dlg_reg = Dict.new()
|
||||||
dlg_tree_start = words(DLG_TREE_MAX)
|
dlg_tree_start = words(DLG_TREE_MAX)
|
||||||
dlg_node_tree = words(DLG_NODE_MAX); dlg_node_text = bytes(DLG_NODE_MAX * 8)
|
dlg_node_tree = words(DLG_NODE_MAX); dlg_node_text = pointers(DLG_NODE_MAX)
|
||||||
dlg_choice_node = words(DLG_CHOICE_MAX); dlg_choice_label = bytes(DLG_CHOICE_MAX * 8); dlg_choice_next = words(DLG_CHOICE_MAX)
|
dlg_choice_node = words(DLG_CHOICE_MAX); dlg_choice_label = pointers(DLG_CHOICE_MAX); dlg_choice_next = words(DLG_CHOICE_MAX)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,10 +65,10 @@ function atlas_init() -> void {
|
||||||
at_spr_sy = words(ATLAS_MAX_SPR)
|
at_spr_sy = words(ATLAS_MAX_SPR)
|
||||||
at_spr_w = words(ATLAS_MAX_SPR)
|
at_spr_w = words(ATLAS_MAX_SPR)
|
||||||
at_spr_h = words(ATLAS_MAX_SPR)
|
at_spr_h = words(ATLAS_MAX_SPR)
|
||||||
at_name_str = bytes(ATLAS_MAX_NAME * 8) # a pointer (8 bytes) per name slot
|
at_name_str = pointers(ATLAS_MAX_NAME) # a pointer (8 bytes) per name slot
|
||||||
at_name_id = words(ATLAS_MAX_NAME)
|
at_name_id = words(ATLAS_MAX_NAME)
|
||||||
at_q_name = bytes(ATLAS_MAX_QUEUE * 8)
|
at_q_name = pointers(ATLAS_MAX_QUEUE)
|
||||||
at_q_path = bytes(ATLAS_MAX_QUEUE * 8)
|
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.
|
||||||
|
|
@ -376,7 +376,7 @@ function assets_load_one(name: pointer, path: pointer) -> void {
|
||||||
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 = bytes(ATLAS_MAX_FONT * 8); at_font_id = words(ATLAS_MAX_FONT) }
|
if at_font_name == null { at_font_name = pointers(ATLAS_MAX_FONT); at_font_id = words(ATLAS_MAX_FONT) }
|
||||||
if at_font_n < ATLAS_MAX_FONT {
|
if at_font_n < ATLAS_MAX_FONT {
|
||||||
at_font_name[at_font_n] = name
|
at_font_name[at_font_n] = name
|
||||||
at_font_id[at_font_n] = rt_font_load(path)
|
at_font_id[at_font_n] = rt_font_load(path)
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@ var snd_bank_n: int = 0
|
||||||
|
|
||||||
function audio_init() -> void {
|
function audio_init() -> void {
|
||||||
if snd_ready { return }
|
if snd_ready { return }
|
||||||
snd_tab = bytes(AUDIO_CAP * 8) # one 8-byte pointer slot per handle
|
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(snd_tab, 0, AUDIO_CAP * 8) # malloc does not zero; empty slots must read null
|
||||||
snd_bank_name = bytes(AUDIO_CAP * 8)
|
snd_bank_name = pointers(AUDIO_CAP)
|
||||||
snd_bank_id = words(AUDIO_CAP)
|
snd_bank_id = words(AUDIO_CAP)
|
||||||
snd_ready = true
|
snd_ready = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
43
runtime/native/bytes.ludic
Normal file
43
runtime/native/bytes.ludic
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# bytes.ludic — L7: bytes a program can hold without raw memory. A `[]byte` from buffer(n) is
|
||||||
|
# bounds-checked like any slice; these turn one into text and move one to and from a file. The
|
||||||
|
# runtime is the platform, so the raw calls underneath (file_read, the NUL-terminated copy) are
|
||||||
|
# its business, not the caller's.
|
||||||
|
|
||||||
|
# the first n bytes of b as text (it stops at b's end, and the text ends at a zero byte if one comes first)
|
||||||
|
function text_of(b: []byte, n: int) -> string {
|
||||||
|
var k = n
|
||||||
|
if b == null { k = 0 }
|
||||||
|
if k > len(b) { k = len(b) }
|
||||||
|
if k < 0 { k = 0 }
|
||||||
|
let out = bytes(k + 1)
|
||||||
|
var i = 0
|
||||||
|
while i < k {
|
||||||
|
out[i] = b[i]
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
out[k] = 0
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
# a whole file as bytes - through the asset pack when the file is in one - or null
|
||||||
|
function fs_read_bytes(path: string) -> []byte {
|
||||||
|
let f = file_open(path, "rb")
|
||||||
|
if f == null { return null }
|
||||||
|
file_seek(f, 0, 2)
|
||||||
|
let n = file_tell(f)
|
||||||
|
file_seek(f, 0, 0)
|
||||||
|
let b = buffer(n)
|
||||||
|
let got = file_read(f, b, n)
|
||||||
|
file_close(f)
|
||||||
|
if got < n { return view(b, 0, got) }
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
# the first `count` bytes of b to a file, replacing it; false when it cannot be written whole
|
||||||
|
function fs_write_bytes(path: string, b: []byte, count: int) -> bool {
|
||||||
|
var k = count
|
||||||
|
if k > len(b) { k = len(b) }
|
||||||
|
let f = file_open(path, "wb")
|
||||||
|
if f == null { return false }
|
||||||
|
let put = file_write(f, b, k)
|
||||||
|
file_close(f)
|
||||||
|
return put == k
|
||||||
|
}
|
||||||
|
|
@ -522,7 +522,7 @@ function rt_running() -> bool {
|
||||||
if is_windowed() {
|
if is_windowed() {
|
||||||
return win_running()
|
return win_running()
|
||||||
}
|
}
|
||||||
return rt_alive
|
return rt_alive != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- writing the frame out ------------------------------------------------
|
# ---- writing the frame out ------------------------------------------------
|
||||||
|
|
@ -792,7 +792,7 @@ function rt_status_text() -> pointer {
|
||||||
# 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(f: pointer) -> void {
|
||||||
let w: words = bytes(16)
|
let w: words = words(4)
|
||||||
w[0] = rt_rng
|
w[0] = rt_rng
|
||||||
w[1] = rt_mapw
|
w[1] = rt_mapw
|
||||||
w[2] = rt_maph
|
w[2] = rt_maph
|
||||||
|
|
@ -805,7 +805,7 @@ function rt_save_state(f: pointer) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function rt_load_state(f: pointer) -> void {
|
function rt_load_state(f: pointer) -> void {
|
||||||
let w: words = bytes(16)
|
let w: words = words(4)
|
||||||
file_read(f, w, 16)
|
file_read(f, w, 16)
|
||||||
rt_rng = w[0]
|
rt_rng = w[0]
|
||||||
rt_mapw = w[1]
|
rt_mapw = w[1]
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ property Dict { keys: pointers, vals: words, used: words, cap: int = 0, count: i
|
||||||
# (re)allocate the three parallel arrays to `cap` slots, all cleared
|
# (re)allocate the three parallel arrays to `cap` slots, all cleared
|
||||||
function dict_init(d: Dict, cap: int) -> void {
|
function dict_init(d: Dict, cap: int) -> void {
|
||||||
d.cap = cap; d.count = 0
|
d.cap = cap; d.count = 0
|
||||||
d.keys = bytes(cap * 8); fill(d.keys, 0, cap * 8) # null slots
|
d.keys = pointers(cap); fill(d.keys, 0, cap * 8) # null slots
|
||||||
d.vals = words(cap); fill(d.vals, 0, cap * 4)
|
d.vals = words(cap); fill(d.vals, 0, cap * 4)
|
||||||
d.used = words(cap); fill(d.used, 0, cap * 4)
|
d.used = words(cap); fill(d.used, 0, cap * 4)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ 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(kind: int, src: pointer) -> int {
|
||||||
let id = gl_create_shader(kind)
|
let id = gl_create_shader(kind)
|
||||||
var srcs: pointers = bytes(8)
|
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)
|
||||||
|
|
@ -286,3 +286,8 @@ function gl_framebuffer() -> int {
|
||||||
gl_gen_framebuffers(1, ids)
|
gl_gen_framebuffers(1, ids)
|
||||||
return ids[0]
|
return ids[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Time.now_us / Time.sleep_us (runtime/native/namespaces.ludic): the platform's clock, as an API a
|
||||||
|
# program reaches without calling C (L7)
|
||||||
|
function time_now_us() -> long { return gl_now_us() }
|
||||||
|
function time_sleep_us(us: long) -> void { gl_sleep_us(us) }
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,9 @@ function http_init() -> void {
|
||||||
h_res = words(HTTP_SLOTS)
|
h_res = words(HTTP_SLOTS)
|
||||||
h_status = words(HTTP_SLOTS)
|
h_status = words(HTTP_SLOTS)
|
||||||
h_blen = words(HTTP_SLOTS)
|
h_blen = words(HTTP_SLOTS)
|
||||||
h_req = bytes(HTTP_SLOTS * 8)
|
h_req = pointers(HTTP_SLOTS)
|
||||||
h_body = bytes(HTTP_SLOTS * 8)
|
h_body = pointers(HTTP_SLOTS)
|
||||||
h_hdr = bytes(HTTP_SLOTS * 8)
|
h_hdr = pointers(HTTP_SLOTS)
|
||||||
h_save = words(HTTP_SLOTS)
|
h_save = words(HTTP_SLOTS)
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < HTTP_SLOTS {
|
while i < HTTP_SLOTS {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ 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: pointers = null # IMG_MAX pointers to RGBA buffers
|
var img_px: [][]int = null # IMG_MAX RGBA buffers
|
||||||
var img_w: words = null
|
var img_w: words = null
|
||||||
var img_h: words = null
|
var img_h: words = null
|
||||||
var img_n: int = 0
|
var img_n: int = 0
|
||||||
|
|
@ -22,7 +22,12 @@ var spr_px: words = null # SPR_MAX * 16 * 16 RGBA pixels, one block
|
||||||
var spr_n: int = 0
|
var spr_n: int = 0
|
||||||
|
|
||||||
function rt_image_init() -> void {
|
function rt_image_init() -> void {
|
||||||
img_px = bytes(IMG_MAX * 8)
|
img_px = new [][]int
|
||||||
|
var pi0 = 0
|
||||||
|
while pi0 < IMG_MAX {
|
||||||
|
push(img_px, null)
|
||||||
|
pi0 += 1
|
||||||
|
}
|
||||||
img_w = words(IMG_MAX)
|
img_w = words(IMG_MAX)
|
||||||
img_h = words(IMG_MAX)
|
img_h = words(IMG_MAX)
|
||||||
spr_px = words(SPR_MAX * SPR_SZ * SPR_SZ)
|
spr_px = words(SPR_MAX * SPR_SZ * SPR_SZ)
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ const Z_FAST: int = 10
|
||||||
const Z_FASTSZ: int = 1024 # 1 << Z_FAST
|
const Z_FASTSZ: int = 1024 # 1 << Z_FAST
|
||||||
const Z_SYMS: int = 1040 # 16 + Z_FASTSZ: where the symbols start
|
const Z_SYMS: int = 1040 # 16 + Z_FASTSZ: where the symbols start
|
||||||
|
|
||||||
function z_table_new(nsym: int) -> pointer {
|
function z_table_new(nsym: int) -> words {
|
||||||
return words((Z_SYMS + nsym))
|
return words((Z_SYMS + nsym))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,7 +238,7 @@ function z_stored(out: pointer, at: int, cap: int) -> int {
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
function z_codes(out: pointer, at: int, cap: int, lit: pointer, dist: pointer) -> int {
|
function z_codes(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(lit)
|
||||||
while sym != 256 {
|
while sym != 256 {
|
||||||
|
|
@ -273,7 +273,7 @@ function z_codes(out: pointer, at: int, cap: int, lit: pointer, dist: pointer) -
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
function z_fixed_tables(lit: pointer, dist: pointer) -> void {
|
function z_fixed_tables(lit: words, dist: words) -> void {
|
||||||
let lengths: words = words(288)
|
let lengths: words = words(288)
|
||||||
for i in 0 .. 144 { lengths[i] = 8 }
|
for i in 0 .. 144 { lengths[i] = 8 }
|
||||||
for i in 144 .. 256 { lengths[i] = 9 }
|
for i in 144 .. 256 { lengths[i] = 9 }
|
||||||
|
|
@ -285,7 +285,7 @@ function z_fixed_tables(lit: pointer, dist: pointer) -> void {
|
||||||
free(lengths)
|
free(lengths)
|
||||||
}
|
}
|
||||||
|
|
||||||
function z_dynamic_tables(lit: pointer, dist: pointer) -> int {
|
function z_dynamic_tables(lit: words, dist: words) -> int {
|
||||||
let nlen = z_bits(5) + 257
|
let nlen = z_bits(5) + 257
|
||||||
let ndist = z_bits(5) + 1
|
let ndist = z_bits(5) + 1
|
||||||
let ncode = z_bits(4) + 4
|
let ncode = z_bits(4) + 4
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ var input_pos: int = 0 # replay / record cursor
|
||||||
|
|
||||||
function input_init() -> void {
|
function input_init() -> void {
|
||||||
if input_names == null {
|
if input_names == null {
|
||||||
input_names = bytes(INPUT_MAX_ACT * 8) # a pointer (8 bytes) per action slot
|
input_names = pointers(INPUT_MAX_ACT) # a pointer (8 bytes) per action slot
|
||||||
input_keys = words(INPUT_MAX_ACT * INPUT_MAX_KEYS)
|
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)
|
input_pads = words(INPUT_MAX_ACT * INPUT_MAX_KEYS) # #83 pad buttons per action (+1 encoded)
|
||||||
}
|
}
|
||||||
|
|
@ -236,7 +236,7 @@ var in_wheel: int = 0 # wheel delta this frame
|
||||||
var in_pad_conn: words = null # IN_PADS
|
var in_pad_conn: words = null # IN_PADS
|
||||||
var in_pad_btn: 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_btn0: words = null # IN_PADS — pad button mask last frame (edges) — #83
|
||||||
var in_pad_axis: words = null # IN_PADS * IN_AXES (fixed)
|
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_on: words = null # IN_TOUCH
|
||||||
var in_touch_x: words = null # IN_TOUCH
|
var in_touch_x: words = null # IN_TOUCH
|
||||||
|
|
@ -253,7 +253,7 @@ function in_init() -> void {
|
||||||
in_pad_conn = words(IN_PADS)
|
in_pad_conn = words(IN_PADS)
|
||||||
in_pad_btn = words(IN_PADS)
|
in_pad_btn = words(IN_PADS)
|
||||||
in_pad_btn0 = words(IN_PADS)
|
in_pad_btn0 = words(IN_PADS)
|
||||||
in_pad_axis = words(IN_PADS * IN_AXES)
|
in_pad_axis = fixeds(IN_PADS * IN_AXES)
|
||||||
in_touch_on = words(IN_TOUCH)
|
in_touch_on = words(IN_TOUCH)
|
||||||
in_touch_x = words(IN_TOUCH)
|
in_touch_x = words(IN_TOUCH)
|
||||||
in_touch_y = words(IN_TOUCH)
|
in_touch_y = words(IN_TOUCH)
|
||||||
|
|
|
||||||
|
|
@ -415,14 +415,14 @@ var ch_lock: pointers = null # a mutex per channel
|
||||||
function sy_init() -> void {
|
function sy_init() -> void {
|
||||||
if sy_ready { return }
|
if sy_ready { return }
|
||||||
mx_used = words(SYNC_MUTEX); fill(mx_used, 0, SYNC_MUTEX * 4)
|
mx_used = words(SYNC_MUTEX); fill(mx_used, 0, SYNC_MUTEX * 4)
|
||||||
mx_obj = bytes(SYNC_MUTEX * 8); fill(mx_obj, 0, SYNC_MUTEX * 8)
|
mx_obj = pointers(SYNC_MUTEX); fill(mx_obj, 0, SYNC_MUTEX * 8)
|
||||||
at_used = words(SYNC_ATOMIC); fill(at_used, 0, SYNC_ATOMIC * 4)
|
at_used = words(SYNC_ATOMIC); fill(at_used, 0, SYNC_ATOMIC * 4)
|
||||||
at_cell = bytes(SYNC_ATOMIC * 8); fill(at_cell, 0, SYNC_ATOMIC * 8)
|
at_cell = pointers(SYNC_ATOMIC); fill(at_cell, 0, SYNC_ATOMIC * 8)
|
||||||
ch_used = words(SYNC_CHAN); fill(ch_used, 0, SYNC_CHAN * 4)
|
ch_used = words(SYNC_CHAN); fill(ch_used, 0, SYNC_CHAN * 4)
|
||||||
ch_head = words(SYNC_CHAN); fill(ch_head, 0, SYNC_CHAN * 4)
|
ch_head = words(SYNC_CHAN); fill(ch_head, 0, SYNC_CHAN * 4)
|
||||||
ch_count = words(SYNC_CHAN); fill(ch_count, 0, SYNC_CHAN * 4)
|
ch_count = words(SYNC_CHAN); fill(ch_count, 0, SYNC_CHAN * 4)
|
||||||
ch_buf = words(SYNC_CHAN * CHAN_CAP); fill(ch_buf, 0, SYNC_CHAN * CHAN_CAP * 4)
|
ch_buf = words(SYNC_CHAN * CHAN_CAP); fill(ch_buf, 0, SYNC_CHAN * CHAN_CAP * 4)
|
||||||
ch_lock = bytes(SYNC_CHAN * 8); fill(ch_lock, 0, SYNC_CHAN * 8)
|
ch_lock = pointers(SYNC_CHAN); fill(ch_lock, 0, SYNC_CHAN * 8)
|
||||||
sy_ready = true
|
sy_ready = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -467,7 +467,7 @@ function sync_atomic() -> int {
|
||||||
while i < SYNC_ATOMIC {
|
while i < SYNC_ATOMIC {
|
||||||
if at_used[i] == 0 {
|
if at_used[i] == 0 {
|
||||||
at_used[i] = 1
|
at_used[i] = 1
|
||||||
if at_cell[i] == null { at_cell[i] = words(1) }
|
if at_cell[i] == null { 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(at_cell[i], 0)
|
||||||
return i + 1
|
return i + 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ function light_isqrt(n: int) -> int {
|
||||||
# sqrt(v)*2^8. Used by the normal-map N·L (unit vectors), where inputs are <= 1.
|
# sqrt(v)*2^8. Used by the normal-map N·L (unit vectors), where inputs are <= 1.
|
||||||
function light_fsqrt(v: fixed) -> fixed {
|
function light_fsqrt(v: fixed) -> fixed {
|
||||||
if v <= 0 { return fixed(0) }
|
if v <= 0 { return fixed(0) }
|
||||||
return light_isqrt(v) << 8
|
return light_isqrt(as_int(v)) << 8
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- tier controls (globals) ----------------------------------------------
|
# ---- tier controls (globals) ----------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -542,3 +542,11 @@ namespace Tiled {
|
||||||
alias world_count(world) = tiled_world_count
|
alias world_count(world) = tiled_world_count
|
||||||
alias world_map(world, index) = tiled_world_map
|
alias world_map(world, index) = tiled_world_map
|
||||||
}
|
}
|
||||||
|
namespace Time {
|
||||||
|
alias now_us() = time_now_us # a microsecond clock, for measuring - not the game's time
|
||||||
|
alias sleep_us(us) = time_sleep_us
|
||||||
|
}
|
||||||
|
namespace Fs {
|
||||||
|
alias read_bytes(path) = fs_read_bytes # the whole file as a []byte, or null
|
||||||
|
alias write_bytes(path, data, count) = fs_write_bytes
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ function proc_cmdline(path: pointer, args: []pointer) -> pointer {
|
||||||
function process_spawn(path: pointer, args: []pointer) -> int {
|
function process_spawn(path: pointer, args: []pointer) -> int {
|
||||||
if (path == null) or (path[0] == 0) { return -1 }
|
if (path == null) or (path[0] == 0) { return -1 }
|
||||||
let n = len(args)
|
let n = len(args)
|
||||||
var argv: pointers = bytes((n + 2) * 8)
|
var argv: pointers = pointers(n + 2)
|
||||||
argv[0] = path
|
argv[0] = path
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ var anim_nclips: int = 0
|
||||||
|
|
||||||
function anim_clip_init() -> void {
|
function anim_clip_init() -> void {
|
||||||
if anim_clip_names == null {
|
if anim_clip_names == null {
|
||||||
anim_clip_names = bytes(ANIM_MAX_CLIPS * 8) # a pointer (8 bytes) per slot
|
anim_clip_names = pointers(ANIM_MAX_CLIPS) # a pointer (8 bytes) per slot
|
||||||
anim_clip_fps = words(ANIM_MAX_CLIPS)
|
anim_clip_fps = words(ANIM_MAX_CLIPS)
|
||||||
anim_clip_frames = words(ANIM_MAX_CLIPS)
|
anim_clip_frames = words(ANIM_MAX_CLIPS)
|
||||||
anim_clip_mode = words(ANIM_MAX_CLIPS)
|
anim_clip_mode = words(ANIM_MAX_CLIPS)
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ var gc_adv: words = null
|
||||||
var gc_bmp: pointers = null
|
var gc_bmp: pointers = null
|
||||||
|
|
||||||
function rt_tt_init() -> void {
|
function rt_tt_init() -> void {
|
||||||
tt_data = bytes(TT_MAX * 8)
|
tt_data = pointers(TT_MAX)
|
||||||
tt_size = words(TT_MAX)
|
tt_size = words(TT_MAX)
|
||||||
tt_upem = words(TT_MAX)
|
tt_upem = words(TT_MAX)
|
||||||
tt_nglyf = words(TT_MAX)
|
tt_nglyf = words(TT_MAX)
|
||||||
|
|
@ -79,16 +79,16 @@ function rt_tt_init() -> void {
|
||||||
tt_cmap = words(TT_MAX)
|
tt_cmap = words(TT_MAX)
|
||||||
tt_cfmt = words(TT_MAX)
|
tt_cfmt = words(TT_MAX)
|
||||||
|
|
||||||
ol_x = words(TT_PTS)
|
ol_x = fixeds(TT_PTS)
|
||||||
ol_y = words(TT_PTS)
|
ol_y = fixeds(TT_PTS)
|
||||||
ol_on = words(TT_PTS)
|
ol_on = words(TT_PTS)
|
||||||
ol_ends = words(256)
|
ol_ends = words(256)
|
||||||
|
|
||||||
ed_x0 = words(TT_EDGES)
|
ed_x0 = fixeds(TT_EDGES)
|
||||||
ed_y0 = words(TT_EDGES)
|
ed_y0 = fixeds(TT_EDGES)
|
||||||
ed_x1 = words(TT_EDGES)
|
ed_x1 = fixeds(TT_EDGES)
|
||||||
ed_y1 = words(TT_EDGES)
|
ed_y1 = fixeds(TT_EDGES)
|
||||||
sc_x = words(TT_EDGES)
|
sc_x = fixeds(TT_EDGES)
|
||||||
sc_d = words(TT_EDGES)
|
sc_d = words(TT_EDGES)
|
||||||
|
|
||||||
gc_used = words(TT_GC)
|
gc_used = words(TT_GC)
|
||||||
|
|
@ -100,7 +100,7 @@ function rt_tt_init() -> void {
|
||||||
gc_ox = words(TT_GC)
|
gc_ox = words(TT_GC)
|
||||||
gc_oy = words(TT_GC)
|
gc_oy = words(TT_GC)
|
||||||
gc_adv = words(TT_GC)
|
gc_adv = words(TT_GC)
|
||||||
gc_bmp = bytes(TT_GC * 8)
|
gc_bmp = pointers(TT_GC)
|
||||||
fill(gc_used, 0, TT_GC * 4)
|
fill(gc_used, 0, TT_GC * 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,13 +55,15 @@ function udp_open(port: int) -> int {
|
||||||
# the port a socket is bound to (the one the system picked, for port 0)
|
# the port a socket is bound to (the one the system picked, for port 0)
|
||||||
function udp_port(h: int) -> int { return lu_udp_port(h) }
|
function udp_port(h: int) -> int { return lu_udp_port(h) }
|
||||||
# one datagram of `n` bytes from `buf` to ip:port: the bytes sent, or -1
|
# one datagram of `n` bytes from `buf` to ip:port: the bytes sent, or -1
|
||||||
function udp_send(h: int, ip: int, port: int, buf: pointer, n: int) -> int {
|
function udp_send(h: int, ip: int, port: int, buf: []byte, n: int) -> int {
|
||||||
if (buf == null) or (n <= 0) { return -1 }
|
if (buf == null) or (n <= 0) or (n > len(buf)) { return -1 }
|
||||||
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: pointer, cap: int) -> int {
|
function udp_recv(h: int, buf: []byte, cap0: int) -> int {
|
||||||
udp_init()
|
udp_init()
|
||||||
|
var cap = cap0
|
||||||
|
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, u_ipport)
|
||||||
if (n > 0) and (h >= 1) and (h <= UDP_HANDLES) {
|
if (n > 0) and (h >= 1) and (h <= UDP_HANDLES) {
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ function rt_ui_init() -> void {
|
||||||
ui_fired = words(UI_MAX)
|
ui_fired = words(UI_MAX)
|
||||||
ui_hasdyn = words(UI_MAX)
|
ui_hasdyn = words(UI_MAX)
|
||||||
ui_dyn = bytes(UI_MAX * 96)
|
ui_dyn = bytes(UI_MAX * 96)
|
||||||
ui_text = bytes(UI_MAX * 8)
|
ui_text = pointers(UI_MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- build-time interface (called by compiler-emitted code) ---------------
|
# ---- build-time interface (called by compiler-emitted code) ---------------
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,13 @@ function first_arg_is_text(e: Node) -> bool {
|
||||||
return t == "string"
|
return t == "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# a namespace that computes inline also takes the methods an `alias` gives it (L6): Time.now_us
|
||||||
|
# is declared in runtime/native/namespaces.ludic beside Time.now, which the compiler computes
|
||||||
|
function emit_alias_or_fail(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
|
let al = ns_alias_find(ns, meth)
|
||||||
|
if al < 0 { perr(`unknown builtin {ns}.{meth}`) }
|
||||||
|
return emit_alias_call(al, e)
|
||||||
|
}
|
||||||
function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
# `Prop.of(e)` / `Prop.has(e)` — typed access to one entity's component, the
|
# `Prop.of(e)` / `Prop.has(e)` — typed access to one entity's component, the
|
||||||
# same binding a query loop makes but for an entity handle held in a variable.
|
# same binding a query loop makes but for an entity handle held in a variable.
|
||||||
|
|
@ -120,19 +127,19 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
# bare rt_ name — so `floor`/`round`/`lerp` never leak into the bare namespace.
|
# bare rt_ name — so `floor`/`round`/`lerp` never leak into the bare namespace.
|
||||||
if (ns == "Math") {
|
if (ns == "Math") {
|
||||||
if is_math_ns(meth) { return emit_math_ns(meth, e) }
|
if is_math_ns(meth) { return emit_math_ns(meth, e) }
|
||||||
perr(`unknown builtin Math.{meth}`)
|
return emit_alias_or_fail("Math", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Text") {
|
if (ns == "Text") {
|
||||||
if is_text_ns(meth) { return emit_text_ns(meth, e) }
|
if is_text_ns(meth) { return emit_text_ns(meth, e) }
|
||||||
perr(`unknown builtin Text.{meth}`)
|
return emit_alias_or_fail("Text", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "List") {
|
if (ns == "List") {
|
||||||
if is_list_ns(meth) { return emit_list_ns(meth, e) }
|
if is_list_ns(meth) { return emit_list_ns(meth, e) }
|
||||||
perr(`unknown builtin List.{meth}`)
|
return emit_alias_or_fail("List", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Ease") {
|
if (ns == "Ease") {
|
||||||
if is_ease_ns(meth) { return emit_ease_ns(meth, e) }
|
if is_ease_ns(meth) { return emit_ease_ns(meth, e) }
|
||||||
perr(`unknown builtin Ease.{meth}`)
|
return emit_alias_or_fail("Ease", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Anim") {
|
if (ns == "Anim") {
|
||||||
if is_anim_ns(meth) { return emit_anim_ns(meth, e) }
|
if is_anim_ns(meth) { return emit_anim_ns(meth, e) }
|
||||||
|
|
@ -148,87 +155,87 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
}
|
}
|
||||||
if (ns == "Collision") {
|
if (ns == "Collision") {
|
||||||
if is_collide_ns(meth) { return emit_collide_ns(meth, e) }
|
if is_collide_ns(meth) { return emit_collide_ns(meth, e) }
|
||||||
perr(`unknown builtin Collision.{meth}`)
|
return emit_alias_or_fail("Collision", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Memory") {
|
if (ns == "Memory") {
|
||||||
if is_mem_ns(meth) { return emit_mem_ns(meth, e) }
|
if is_mem_ns(meth) { return emit_mem_ns(meth, e) }
|
||||||
perr(`unknown builtin Memory.{meth}`)
|
return emit_alias_or_fail("Memory", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Color") {
|
if (ns == "Color") {
|
||||||
if is_colorfn_ns(meth) { return emit_colorfn_ns(meth, e) }
|
if is_colorfn_ns(meth) { return emit_colorfn_ns(meth, e) }
|
||||||
perr(`unknown builtin Color.{meth}`)
|
return emit_alias_or_fail("Color", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Time") {
|
if (ns == "Time") {
|
||||||
if is_time_ns(meth) { return emit_time_ns(meth, e) }
|
if is_time_ns(meth) { return emit_time_ns(meth, e) }
|
||||||
perr(`unknown builtin Time.{meth}`)
|
return emit_alias_or_fail("Time", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Hash") {
|
if (ns == "Hash") {
|
||||||
if is_hash_ns(meth) { return emit_hash_ns(meth, e) }
|
if is_hash_ns(meth) { return emit_hash_ns(meth, e) }
|
||||||
perr(`unknown builtin Hash.{meth}`)
|
return emit_alias_or_fail("Hash", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Crypto") {
|
if (ns == "Crypto") {
|
||||||
if is_crypto_ns(meth) { return emit_crypto_ns(meth, e) }
|
if is_crypto_ns(meth) { return emit_crypto_ns(meth, e) }
|
||||||
perr(`unknown builtin Crypto.{meth}`)
|
return emit_alias_or_fail("Crypto", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Uuid") {
|
if (ns == "Uuid") {
|
||||||
if is_uuid_ns(meth) { return emit_uuid_ns(meth, e) }
|
if is_uuid_ns(meth) { return emit_uuid_ns(meth, e) }
|
||||||
perr(`unknown builtin Uuid.{meth}`)
|
return emit_alias_or_fail("Uuid", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Noise") {
|
if (ns == "Noise") {
|
||||||
if is_noise_ns(meth) { return emit_noise_ns(meth, e) }
|
if is_noise_ns(meth) { return emit_noise_ns(meth, e) }
|
||||||
perr(`unknown builtin Noise.{meth}`)
|
return emit_alias_or_fail("Noise", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Log") {
|
if (ns == "Log") {
|
||||||
if is_log_ns(meth) { return emit_log_ns(meth, e) }
|
if is_log_ns(meth) { return emit_log_ns(meth, e) }
|
||||||
perr(`unknown builtin Log.{meth}`)
|
return emit_alias_or_fail("Log", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Os") {
|
if (ns == "Os") {
|
||||||
if is_os_ns(meth) { return emit_os_ns(meth, e) }
|
if is_os_ns(meth) { return emit_os_ns(meth, e) }
|
||||||
perr(`unknown builtin Os.{meth}`)
|
return emit_alias_or_fail("Os", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Unicode") {
|
if (ns == "Unicode") {
|
||||||
if is_unicode_ns(meth) { return emit_unicode_ns(meth, e) }
|
if is_unicode_ns(meth) { return emit_unicode_ns(meth, e) }
|
||||||
perr(`unknown builtin Unicode.{meth}`)
|
return emit_alias_or_fail("Unicode", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Fs") {
|
if (ns == "Fs") {
|
||||||
if is_fs_ns(meth) { return emit_fs_ns(meth, e) }
|
if is_fs_ns(meth) { return emit_fs_ns(meth, e) }
|
||||||
perr(`unknown builtin Fs.{meth}`)
|
return emit_alias_or_fail("Fs", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Path") {
|
if (ns == "Path") {
|
||||||
if is_path_ns(meth) { return emit_path_ns(meth, e) }
|
if is_path_ns(meth) { return emit_path_ns(meth, e) }
|
||||||
perr(`unknown builtin Path.{meth}`)
|
return emit_alias_or_fail("Path", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Mime") {
|
if (ns == "Mime") {
|
||||||
if is_mime_ns(meth) { return emit_mime_ns(meth, e) }
|
if is_mime_ns(meth) { return emit_mime_ns(meth, e) }
|
||||||
perr(`unknown builtin Mime.{meth}`)
|
return emit_alias_or_fail("Mime", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Vector") {
|
if (ns == "Vector") {
|
||||||
if is_vector_ns(meth) { return emit_vector_ns(meth, e) }
|
if is_vector_ns(meth) { return emit_vector_ns(meth, e) }
|
||||||
perr(`unknown builtin Vector.{meth}`)
|
return emit_alias_or_fail("Vector", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "IVec2") {
|
if (ns == "IVec2") {
|
||||||
if is_ivec_ns(meth) { return emit_ivec_ns(meth, e) }
|
if is_ivec_ns(meth) { return emit_ivec_ns(meth, e) }
|
||||||
perr(`unknown builtin IVec2.{meth}`)
|
return emit_alias_or_fail("IVec2", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Rect") {
|
if (ns == "Rect") {
|
||||||
if is_rect_ns(meth) { return emit_rect_ns(meth, e) }
|
if is_rect_ns(meth) { return emit_rect_ns(meth, e) }
|
||||||
perr(`unknown builtin Rect.{meth}`)
|
return emit_alias_or_fail("Rect", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Duration") {
|
if (ns == "Duration") {
|
||||||
if is_duration_ns(meth) { return emit_duration_ns(meth, e) }
|
if is_duration_ns(meth) { return emit_duration_ns(meth, e) }
|
||||||
perr(`unknown builtin Duration.{meth}`)
|
return emit_alias_or_fail("Duration", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Date") {
|
if (ns == "Date") {
|
||||||
if is_date_ns(meth) { return emit_date_ns(meth, e) }
|
if is_date_ns(meth) { return emit_date_ns(meth, e) }
|
||||||
perr(`unknown builtin Date.{meth}`)
|
return emit_alias_or_fail("Date", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "DateTime") {
|
if (ns == "DateTime") {
|
||||||
if is_datetime_ns(meth) { return emit_datetime_ns(meth, e) }
|
if is_datetime_ns(meth) { return emit_datetime_ns(meth, e) }
|
||||||
perr(`unknown builtin DateTime.{meth}`)
|
return emit_alias_or_fail("DateTime", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Clock") {
|
if (ns == "Clock") {
|
||||||
if is_clock_ns(meth) { return emit_clock_ns(meth, e) }
|
if is_clock_ns(meth) { return emit_clock_ns(meth, e) }
|
||||||
perr(`unknown builtin Clock.{meth}`)
|
return emit_alias_or_fail("Clock", meth, e)
|
||||||
}
|
}
|
||||||
# #80 — entity pool stats. The ECS allocator already recycles freed entity slots
|
# #80 — entity pool stats. The ECS allocator already recycles freed entity slots
|
||||||
# through a freelist (L_alloc pops @L_freen before growing @L_entc), and component
|
# through a freelist (L_alloc pops @L_freen before growing @L_entc), and component
|
||||||
|
|
@ -287,7 +294,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
if g_windowed { emit(` call void @app_{meth}()\n`) }
|
if g_windowed { emit(` call void @app_{meth}()\n`) }
|
||||||
return val("0", "void")
|
return val("0", "void")
|
||||||
}
|
}
|
||||||
perr(`unknown builtin App.{meth}`)
|
return emit_alias_or_fail("App", meth, e)
|
||||||
}
|
}
|
||||||
if (ns == "Pool") {
|
if (ns == "Pool") {
|
||||||
if (meth == "capacity") { return val(itoa(MAX_ENT), "int") } # max entities
|
if (meth == "capacity") { return val(itoa(MAX_ENT), "int") } # max entities
|
||||||
|
|
@ -298,7 +305,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
let fr = emit_bind("load i32, ptr @L_freen")
|
let fr = emit_bind("load i32, ptr @L_freen")
|
||||||
return val(emit_bind(`sub i32 {ec}, {fr}`), "int")
|
return val(emit_bind(`sub i32 {ec}, {fr}`), "int")
|
||||||
}
|
}
|
||||||
perr(`unknown builtin Pool.{meth}`)
|
return emit_alias_or_fail("Pool", meth, e)
|
||||||
}
|
}
|
||||||
# L6: a method declared by `alias` in a namespace block - the engine's own in
|
# L6: a method declared by `alias` in a namespace block - the engine's own in
|
||||||
# runtime/native/namespaces.ludic, a package's in its files - is a call to its target
|
# runtime/native/namespaces.ludic, a package's in its files - is a call to its target
|
||||||
|
|
@ -341,7 +348,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
let nm = emit_expr(e.kids[0])
|
let nm = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call i32 @L_spawn_prefab(ptr {nm.code})`), "entity")
|
return val(emit_bind(`call i32 @L_spawn_prefab(ptr {nm.code})`), "entity")
|
||||||
}
|
}
|
||||||
perr(`unknown builtin Prefab.{meth}`)
|
return emit_alias_or_fail("Prefab", meth, e)
|
||||||
}
|
}
|
||||||
# Camera.* — the world-space camera: a draw offset threaded through the render
|
# Camera.* — the world-space camera: a draw offset threaded through the render
|
||||||
# path (runtime/native/core.ludic). set/follow move it; shake jitters it from
|
# path (runtime/native/core.ludic). set/follow move it; shake jitters it from
|
||||||
|
|
@ -613,11 +620,17 @@ function emit_call(e: Node) -> Val {
|
||||||
let w = emit_bind(`zext i32 {n.code} to i64`)
|
let w = emit_bind(`zext i32 {n.code} to i64`)
|
||||||
return val(emit_bind(`call ptr @malloc(i64 {w})`), "pointer")
|
return val(emit_bind(`call ptr @malloc(i64 {w})`), "pointer")
|
||||||
}
|
}
|
||||||
if (name == "words") { # words(n): allocate n 32-bit words
|
if (name == "words") { return emit_sized_slice("int", emit_expr(e.kids[0])) } # words(n): n zeroed ints
|
||||||
let n = emit_expr(e.kids[0])
|
if (name == "buffer") and (find_fn(name) == null) { return emit_sized_slice("byte", emit_expr(e.kids[0])) } # buffer(n): n zeroed bytes (L7)
|
||||||
let by = emit_bind(`mul i32 {n.code}, 4`)
|
if (name == "fixeds") and (find_fn(name) == null) { return emit_sized_slice("fixed", emit_expr(e.kids[0])) }
|
||||||
let w = emit_bind(`zext i32 {by} to i64`)
|
if (name == "pointers") and (find_fn(name) == null) { return emit_sized_slice("pointer", emit_expr(e.kids[0])) }
|
||||||
return val(emit_bind(`call ptr @malloc(i64 {w})`), "words")
|
# view(xs, start, count): `count` elements of xs from `start`, sharing its storage - checked
|
||||||
|
# against xs's length once, when it is made, and bounds-checked like any slice after (L7)
|
||||||
|
if (name == "view") { return emit_view(e) }
|
||||||
|
# data_of(xs): the address of a slice's first element, for handing to C (unsafe, L7)
|
||||||
|
if (name == "data_of") {
|
||||||
|
let sv = emit_expr(e.kids[0])
|
||||||
|
return val(emit_bind(`load ptr, ptr {slice_field(sv.code, 0)}`), "pointer")
|
||||||
}
|
}
|
||||||
if (name == "fixed") {
|
if (name == "fixed") {
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
|
|
@ -648,7 +661,7 @@ function emit_call(e: Node) -> Val {
|
||||||
if ((name == "floats") or (name == "doubles")) and (find_fn(name) == null) {
|
if ((name == "floats") or (name == "doubles")) and (find_fn(name) == null) {
|
||||||
var ft = "float"
|
var ft = "float"
|
||||||
if (name == "doubles") { ft = "double" }
|
if (name == "doubles") { ft = "double" }
|
||||||
return emit_fp_buffer(ft, emit_expr(e.kids[0]))
|
return emit_sized_slice(ft, emit_expr(e.kids[0]))
|
||||||
}
|
}
|
||||||
if (name == "floor") { let a = emit_expr(e.kids[0]); return val(emit_bind(`ashr i32 {a.code}, 16`), "int") }
|
if (name == "floor") { let a = emit_expr(e.kids[0]); return val(emit_bind(`ashr i32 {a.code}, 16`), "int") }
|
||||||
# --- the testing framework's assertions (see emit_test_runner) --------------
|
# --- the testing framework's assertions (see emit_test_runner) --------------
|
||||||
|
|
@ -854,9 +867,12 @@ function emit_call(e: Node) -> Val {
|
||||||
let eatys = new []pointer
|
let eatys = new []pointer
|
||||||
var ei = 0
|
var ei = 0
|
||||||
while ei < len(e.kids) {
|
while ei < len(e.kids) {
|
||||||
let v = emit_expr(e.kids[ei])
|
var v = emit_expr(e.kids[ei])
|
||||||
|
# a C function wants a buffer's elements, never a Ludic slice's header
|
||||||
|
if is_slice_ty(v.ty) { v = val(emit_bind(`load ptr, ptr {slice_field(v.code, 0)}`), "pointer") }
|
||||||
var pty = v.ty
|
var pty = v.ty
|
||||||
if ei < len(eptys) { pty = eptys[ei] }
|
if ei < len(eptys) { pty = eptys[ei] }
|
||||||
|
if is_slice_ty(pty) { pty = "pointer" }
|
||||||
push(eargs, coerce_code(v, pty))
|
push(eargs, coerce_code(v, pty))
|
||||||
push(eatys, pty)
|
push(eatys, pty)
|
||||||
ei += 1
|
ei += 1
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ function emit_header() -> void {
|
||||||
emith("; Ludic (self-hosted) -> LLVM IR\n")
|
emith("; Ludic (self-hosted) -> LLVM IR\n")
|
||||||
emith("declare i32 @printf(ptr, ...)\n")
|
emith("declare i32 @printf(ptr, ...)\n")
|
||||||
emith("declare ptr @malloc(i64)\n")
|
emith("declare ptr @malloc(i64)\n")
|
||||||
|
emith("declare ptr @calloc(i64, i64)\n")
|
||||||
emith("declare ptr @realloc(ptr, i64)\n")
|
emith("declare ptr @realloc(ptr, i64)\n")
|
||||||
emith("declare void @free(ptr)\n")
|
emith("declare void @free(ptr)\n")
|
||||||
emith("declare i64 @fread(ptr, i64, i64, ptr)\n")
|
emith("declare i64 @fread(ptr, i64, i64, ptr)\n")
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ function emit_intrinsic(name: pointer, e: Node) -> Val {
|
||||||
return val(emit_bind(`call ptr @lp_pak_open(ptr {p}, ptr {m})`), "pointer")
|
return val(emit_bind(`call ptr @lp_pak_open(ptr {p}, ptr {m})`), "pointer")
|
||||||
}
|
}
|
||||||
if (name == "file_read") or (name == "file_write") {
|
if (name == "file_read") or (name == "file_write") {
|
||||||
let f = arg_code(e, 0); let b = arg_code(e, 1); let n = arg_code(e, 2)
|
let f = arg_code(e, 0); let b = raw_expr(e.kids[1]).code; let n = arg_code(e, 2)
|
||||||
let w = emit_bind(`zext i32 {n} to i64`)
|
let w = emit_bind(`zext i32 {n} to i64`)
|
||||||
var fn2 = "@fread"
|
var fn2 = "@fread"
|
||||||
if (name == "file_write") { fn2 = "@fwrite" }
|
if (name == "file_write") { fn2 = "@fwrite" }
|
||||||
|
|
|
||||||
|
|
@ -35,16 +35,23 @@ function is_intrinsic2(name: pointer) -> bool {
|
||||||
|
|
||||||
function emit_intrinsic2(name: pointer, e: Node) -> Val {
|
function emit_intrinsic2(name: pointer, e: Node) -> Val {
|
||||||
if (name == "free") {
|
if (name == "free") {
|
||||||
let p = arg_code(e, 0); emit(" call void @free(ptr "); emit(p); emit(")\n"); return val("0", "void")
|
let fv = emit_expr(e.kids[0])
|
||||||
|
# a slice is its elements and its header: both go (L7)
|
||||||
|
if is_slice_ty(fv.ty) {
|
||||||
|
let fd = emit_bind(`load ptr, ptr {slice_field(fv.code, 0)}`)
|
||||||
|
emit(` call void @free(ptr {fd})\n`)
|
||||||
|
}
|
||||||
|
emit(` call void @free(ptr {fv.code})\n`)
|
||||||
|
return val("0", "void")
|
||||||
}
|
}
|
||||||
if (name == "fill") {
|
if (name == "fill") {
|
||||||
let p = arg_code(e, 0); let v = arg_code(e, 1); let n = arg_code(e, 2)
|
let p = raw_expr(e.kids[0]).code; let v = arg_code(e, 1); let n = arg_code(e, 2)
|
||||||
let w = emit_bind(`zext i32 {n} to i64`)
|
let w = emit_bind(`zext i32 {n} to i64`)
|
||||||
emit(" call ptr @memset(ptr "); emit(p); emit(", i32 "); emit(v); emit(", i64 "); emit(w); emit(")\n")
|
emit(" call ptr @memset(ptr "); emit(p); emit(", i32 "); emit(v); emit(", i64 "); emit(w); emit(")\n")
|
||||||
return val("0", "void")
|
return val("0", "void")
|
||||||
}
|
}
|
||||||
if (name == "offset") {
|
if (name == "offset") {
|
||||||
let p = arg_code(e, 0); let n = arg_code(e, 1)
|
let p = raw_expr(e.kids[0]).code; let n = arg_code(e, 1)
|
||||||
let g = nreg()
|
let g = nreg()
|
||||||
emit(" "); emit(g); emit(" = getelementptr inbounds i8, ptr "); emit(p); emit(", i32 "); emit(n); emit("\n")
|
emit(" "); emit(g); emit(" = getelementptr inbounds i8, ptr "); emit(p); emit(", i32 "); emit(n); emit("\n")
|
||||||
return val(g, "pointer")
|
return val(g, "pointer")
|
||||||
|
|
|
||||||
|
|
@ -19,28 +19,28 @@ function emit_mem_ns(meth: pointer, e: Node) -> Val {
|
||||||
let n = emit_expr(e.kids[0])
|
let n = emit_expr(e.kids[0])
|
||||||
let by = emit_bind(`mul i32 {n.code}, 4`)
|
let by = emit_bind(`mul i32 {n.code}, 4`)
|
||||||
let w = emit_bind(`zext i32 {by} to i64`)
|
let w = emit_bind(`zext i32 {by} to i64`)
|
||||||
return val(emit_bind(`call ptr @malloc(i64 {w})`), "words")
|
return emit_sized_slice("int", n)
|
||||||
}
|
}
|
||||||
if (meth == "copy") { # copy n bytes src -> dst
|
if (meth == "copy") { # copy n bytes src -> dst
|
||||||
let dst = emit_expr(e.kids[0]); let src = emit_expr(e.kids[1]); let n = emit_expr(e.kids[2])
|
let dst = raw_expr(e.kids[0]); let src = raw_expr(e.kids[1]); let n = emit_expr(e.kids[2])
|
||||||
let w = emit_bind(`zext i32 {n.code} to i64`)
|
let w = emit_bind(`zext i32 {n.code} to i64`)
|
||||||
emit(" call ptr @memcpy(ptr "); emit(dst.code); emit(", ptr "); emit(src.code); emit(", i64 "); emit(w); emit(")\n")
|
emit(" call ptr @memcpy(ptr "); emit(dst.code); emit(", ptr "); emit(src.code); emit(", i64 "); emit(w); emit(")\n")
|
||||||
return val("0", "void")
|
return val("0", "void")
|
||||||
}
|
}
|
||||||
if (meth == "fill") { # set n bytes of buf to value v
|
if (meth == "fill") { # set n bytes of buf to value v
|
||||||
let buf = emit_expr(e.kids[0]); let v = emit_expr(e.kids[1]); let n = emit_expr(e.kids[2])
|
let buf = raw_expr(e.kids[0]); let v = emit_expr(e.kids[1]); let n = emit_expr(e.kids[2])
|
||||||
let w = emit_bind(`zext i32 {n.code} to i64`)
|
let w = emit_bind(`zext i32 {n.code} to i64`)
|
||||||
emit(" call ptr @memset(ptr "); emit(buf.code); emit(", i32 "); emit(v.code); emit(", i64 "); emit(w); emit(")\n")
|
emit(" call ptr @memset(ptr "); emit(buf.code); emit(", i32 "); emit(v.code); emit(", i64 "); emit(w); emit(")\n")
|
||||||
return val("0", "void")
|
return val("0", "void")
|
||||||
}
|
}
|
||||||
if (meth == "peek") { # read one byte at buf[i], 0..255
|
if (meth == "peek") { # read one byte at buf[i], 0..255
|
||||||
let buf = emit_expr(e.kids[0]); let i = emit_expr(e.kids[1])
|
let buf = raw_expr(e.kids[0]); let i = emit_expr(e.kids[1])
|
||||||
let p = emit_bind(`getelementptr inbounds i8, ptr {buf.code}, i32 {i.code}`)
|
let p = emit_bind(`getelementptr inbounds i8, ptr {buf.code}, i32 {i.code}`)
|
||||||
let c = emit_bind(`load i8, ptr {p}`)
|
let c = emit_bind(`load i8, ptr {p}`)
|
||||||
return val(emit_bind(`zext i8 {c} to i32`), "int")
|
return val(emit_bind(`zext i8 {c} to i32`), "int")
|
||||||
}
|
}
|
||||||
# poke: write the low byte of v at buf[i]
|
# poke: write the low byte of v at buf[i]
|
||||||
let buf = emit_expr(e.kids[0]); let i = emit_expr(e.kids[1]); let v = emit_expr(e.kids[2])
|
let buf = raw_expr(e.kids[0]); let i = emit_expr(e.kids[1]); let v = emit_expr(e.kids[2])
|
||||||
let p = emit_bind(`getelementptr inbounds i8, ptr {buf.code}, i32 {i.code}`)
|
let p = emit_bind(`getelementptr inbounds i8, ptr {buf.code}, i32 {i.code}`)
|
||||||
let b = emit_bind(`trunc i32 {v.code} to i8`)
|
let b = emit_bind(`trunc i32 {v.code} to i8`)
|
||||||
emit(" store i8 "); emit(b); emit(", ptr "); emit(p); emit("\n")
|
emit(" store i8 "); emit(b); emit(", ptr "); emit(p); emit("\n")
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,54 @@ function emit_new_slice(ty: pointer) -> Val {
|
||||||
return val(h, ty)
|
return val(h, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# words(n), floats(n), doubles(n): a slice of n zeroed elements, its length n (L7)
|
||||||
|
function emit_sized_slice(el: pointer, n: Val) -> Val {
|
||||||
|
let h = emit_new_slice("[]" + el)
|
||||||
|
let esz = emit_sizeof(llty(el))
|
||||||
|
let nn = emit_bind(`zext i32 {n.code} to i64`)
|
||||||
|
let data = emit_bind(`call ptr @calloc(i64 {nn}, i64 {esz})`)
|
||||||
|
emit(` store ptr {data}, ptr {slice_field(h.code, 0)}\n`)
|
||||||
|
emit(` store i32 {n.code}, ptr {slice_field(h.code, 1)}\n`)
|
||||||
|
emit(` store i32 {n.code}, ptr {slice_field(h.code, 2)}\n`)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
function emit_view(e: Node) -> Val {
|
||||||
|
let xs = emit_expr(e.kids[0])
|
||||||
|
if not is_slice_ty(xs.ty) { perr(`view takes a slice, and this is {xs.ty}`) }
|
||||||
|
let st = emit_expr(e.kids[1])
|
||||||
|
let ct = emit_expr(e.kids[2])
|
||||||
|
let el = slice_elem(xs.ty)
|
||||||
|
let ln = emit_bind(`load i32, ptr {slice_field(xs.code, 1)}`)
|
||||||
|
let endv = emit_bind(`add i32 {st.code}, {ct.code}`)
|
||||||
|
let ok1 = emit_bind(`icmp ule i32 {endv}, {ln}`)
|
||||||
|
let ok2 = emit_bind(`icmp sge i32 {st.code}, 0`)
|
||||||
|
let ok3 = emit_bind(`icmp sge i32 {ct.code}, 0`)
|
||||||
|
let ok12 = emit_bind(`and i1 {ok1}, {ok2}`)
|
||||||
|
let ok = emit_bind(`and i1 {ok12}, {ok3}`)
|
||||||
|
let lok = lbl("vwok")
|
||||||
|
let lbad = lbl("vwbad")
|
||||||
|
emit(` br i1 {ok}, label %{lok}, label %{lbad}\n`)
|
||||||
|
emit(`{lbad}:\n`)
|
||||||
|
g_uses_bounds = true
|
||||||
|
let bmsg = emit_str_const(`{g_src_name}:{itoa(e.line)}: view past the end: from `)
|
||||||
|
let bse = emit_bind(stdstream_rhs(2))
|
||||||
|
emit(` call i32 (ptr, ptr, ...) @fprintf(ptr {bse}, ptr @.fmt_bounds, ptr {bmsg}, i32 {endv}, i32 {ln})\n`)
|
||||||
|
emit(" call void @exit(i32 1)\n unreachable\n")
|
||||||
|
emit(`{lok}:\n`)
|
||||||
|
let d0 = emit_bind(`load ptr, ptr {slice_field(xs.code, 0)}`)
|
||||||
|
let d1 = emit_bind(`getelementptr inbounds {llty(el)}, ptr {d0}, i32 {st.code}`)
|
||||||
|
let h = emit_new_slice(xs.ty)
|
||||||
|
emit(` store ptr {d1}, ptr {slice_field(h.code, 0)}\n`)
|
||||||
|
emit(` store i32 {ct.code}, ptr {slice_field(h.code, 1)}\n`)
|
||||||
|
emit(` store i32 {ct.code}, ptr {slice_field(h.code, 2)}\n`)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
# an expression that a raw-memory intrinsic reads as an address: a slice gives its elements (L7)
|
||||||
|
function raw_expr(n: Node) -> Val {
|
||||||
|
let v = emit_expr(n)
|
||||||
|
if is_slice_ty(v.ty) { return val(emit_bind(`load ptr, ptr {slice_field(v.code, 0)}`), "pointer") }
|
||||||
|
return v
|
||||||
|
}
|
||||||
function slice_field(h: pointer, i: int) -> pointer {
|
function slice_field(h: pointer, i: int) -> pointer {
|
||||||
let r = nreg()
|
let r = nreg()
|
||||||
emit(" "); emit(r); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h)
|
emit(" "); emit(r); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h)
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ function block_ends(b: Node) -> bool {
|
||||||
}
|
}
|
||||||
function stmt_ends(st: Node) -> bool {
|
function stmt_ends(st: Node) -> bool {
|
||||||
if st.kind == S_RETURN { return true }
|
if st.kind == S_RETURN { return true }
|
||||||
|
if st.kind == S_UNSAFE { return block_ends(st.a) }
|
||||||
if st.kind == S_IF {
|
if st.kind == S_IF {
|
||||||
if st.c == null or not block_ends(st.b) { return false }
|
if st.c == null or not block_ends(st.b) { return false }
|
||||||
if st.c.kind == S_IF { return stmt_ends(st.c) }
|
if st.c.kind == S_IF { return stmt_ends(st.c) }
|
||||||
|
|
@ -377,6 +378,7 @@ function emit_stmt(st: Node) -> void {
|
||||||
if st.kind == S_SPAWN { let se = emit_spawn(st); return }
|
if st.kind == S_SPAWN { let se = emit_spawn(st); return }
|
||||||
if st.kind == S_DESPAWN { emit_despawn(st); return }
|
if st.kind == S_DESPAWN { emit_despawn(st); return }
|
||||||
if st.kind == S_TOGGLE { emit_toggle(st); return }
|
if st.kind == S_TOGGLE { emit_toggle(st); return }
|
||||||
|
if st.kind == S_UNSAFE { emit_block(st.a); return }
|
||||||
if st.kind == S_ATTACH { emit_attach(st); return }
|
if st.kind == S_ATTACH { emit_attach(st); return }
|
||||||
if st.kind == S_DETACH { emit_detach(st); return }
|
if st.kind == S_DETACH { emit_detach(st); return }
|
||||||
if st.kind == S_EMIT { let v = emit_emit(st); return } # statement form: discard the flag
|
if st.kind == S_EMIT { let v = emit_emit(st); return } # statement form: discard the flag
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,37 @@ function ck_builtin(e: Node, name: pointer) -> pointer {
|
||||||
ck_walk_args(e)
|
ck_walk_args(e)
|
||||||
return "int"
|
return "int"
|
||||||
}
|
}
|
||||||
|
if (name == "view") and len(e.kids) == 3 {
|
||||||
|
let vt = ck_expr(e.kids[0])
|
||||||
|
ck_give("int", ck_expr(e.kids[1]), e.kids[1], "the start of a view")
|
||||||
|
ck_give("int", ck_expr(e.kids[2]), e.kids[2], "the length of a view")
|
||||||
|
if not ck_unknown(vt) and not is_slice_ty(vt) { ck_err("kind", e, `view takes a slice, and this is {ck_a(vt)}`) }
|
||||||
|
return vt
|
||||||
|
}
|
||||||
|
if (name == "bytes") or (name == "offset") {
|
||||||
|
ck_walk_args(e)
|
||||||
|
return "pointer"
|
||||||
|
}
|
||||||
|
if (name == "data_of") {
|
||||||
|
ck_walk_args(e)
|
||||||
|
return "pointer"
|
||||||
|
}
|
||||||
|
if (name == "words") {
|
||||||
|
ck_walk_args(e)
|
||||||
|
return "[]int"
|
||||||
|
}
|
||||||
|
if (name == "buffer") {
|
||||||
|
ck_walk_args(e)
|
||||||
|
return "[]byte"
|
||||||
|
}
|
||||||
|
if (name == "fixeds") or (name == "pointers") {
|
||||||
|
ck_walk_args(e)
|
||||||
|
return "[]" + name[0 .. len(name) - 1]
|
||||||
|
}
|
||||||
|
if (name == "floats") or (name == "doubles") {
|
||||||
|
ck_walk_args(e)
|
||||||
|
return "[]" + name[0 .. len(name) - 1]
|
||||||
|
}
|
||||||
if (name == "float_from_bits") {
|
if (name == "float_from_bits") {
|
||||||
ck_walk_args(e)
|
ck_walk_args(e)
|
||||||
return "float"
|
return "float"
|
||||||
|
|
@ -107,11 +138,20 @@ function ck_call(e: Node) -> pointer {
|
||||||
let b = c.a
|
let b = c.a
|
||||||
if b.kind == E_ID and ck_local(b.s) < 0 and ck_global(b.s) == null {
|
if b.kind == E_ID and ck_local(b.s) < 0 and ck_global(b.s) == null {
|
||||||
# an alias (L6) is its target, labels and all, so its arguments are checked in full
|
# an alias (L6) is its target, labels and all, so its arguments are checked in full
|
||||||
|
if (b.s == "Memory") { ck_raw(e, `Memory.{c.s}`) }
|
||||||
let al = ns_alias_find(b.s, c.s)
|
let al = ns_alias_find(b.s, c.s)
|
||||||
if al >= 0 {
|
if al >= 0 {
|
||||||
var tf = ck_fn(g_al_target[al])
|
var tf = ck_fn(g_al_target[al])
|
||||||
if tf == null { tf = ck_extern(g_al_target[al]) }
|
if tf == null {
|
||||||
if tf != null { return ck_call_alias(e, `{b.s}.{c.s}`, al, tf) }
|
tf = ck_extern(g_al_target[al])
|
||||||
|
if tf != null { ck_raw(e, `{b.s}.{c.s}, a C function`) }
|
||||||
|
}
|
||||||
|
if tf != null {
|
||||||
|
ck_extern_arg = ck_extern(g_al_target[al]) != null
|
||||||
|
let at = ck_call_alias(e, `{b.s}.{c.s}`, al, tf)
|
||||||
|
ck_extern_arg = false
|
||||||
|
return at
|
||||||
|
}
|
||||||
ck_walk_args(e)
|
ck_walk_args(e)
|
||||||
return "?"
|
return "?"
|
||||||
}
|
}
|
||||||
|
|
@ -141,6 +181,7 @@ function ck_call_named(e: Node, name: pointer) -> pointer {
|
||||||
ck_walk_args(e)
|
ck_walk_args(e)
|
||||||
return "?"
|
return "?"
|
||||||
}
|
}
|
||||||
|
if ck_raw_builtin(name) { ck_raw(e, `{name}()`) }
|
||||||
let bt = ck_builtin(e, name)
|
let bt = ck_builtin(e, name)
|
||||||
if bt != null { return bt }
|
if bt != null { return bt }
|
||||||
let f = ck_fn(name)
|
let f = ck_fn(name)
|
||||||
|
|
@ -148,7 +189,13 @@ function ck_call_named(e: Node, name: pointer) -> pointer {
|
||||||
let gt = gen_template(g_gen_fns, name)
|
let gt = gen_template(g_gen_fns, name)
|
||||||
if gt != null { return gen_call(e, name, gt) }
|
if gt != null { return gen_call(e, name, gt) }
|
||||||
let x = ck_extern(name)
|
let x = ck_extern(name)
|
||||||
if x != null { return ck_call_fn(e, name, x) }
|
if x != null {
|
||||||
|
ck_raw(e, `the C function {name}`)
|
||||||
|
ck_extern_arg = true
|
||||||
|
let xt = ck_call_fn(e, name, x)
|
||||||
|
ck_extern_arg = false
|
||||||
|
return xt
|
||||||
|
}
|
||||||
let g = ck_global(name)
|
let g = ck_global(name)
|
||||||
if g != null and is_fn_type(g.ty) { return ck_call_sig(e, name, g.ty) }
|
if g != null and is_fn_type(g.ty) { return ck_call_sig(e, name, g.ty) }
|
||||||
ck_walk_args(e)
|
ck_walk_args(e)
|
||||||
|
|
|
||||||
|
|
@ -53,5 +53,13 @@ function ck_err(cat: pointer, n: Node, msg: pointer) -> void {
|
||||||
function ck_give(to: pointer, from: pointer, e: Node, what: pointer) -> void {
|
function ck_give(to: pointer, from: pointer, e: Node, what: pointer) -> void {
|
||||||
let cat = ck_mismatch(to, from, e)
|
let cat = ck_mismatch(to, from, e)
|
||||||
if cat == null { return }
|
if cat == null { return }
|
||||||
|
if (cat == "slice-pointer") {
|
||||||
|
ck_err(cat, e, `{what} wants a pointer and this is {ck_a(from)}: take the slice itself, or data_of(xs) in unsafe code`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (cat == "pointer-slice") {
|
||||||
|
ck_err(cat, e, `{what} wants {ck_a(to)} and this is a pointer: a slice is made with words(n), floats(n) or new, not from an address`)
|
||||||
|
return
|
||||||
|
}
|
||||||
ck_err(cat, e, `{what} wants {ck_a(to)} and this is {ck_a(from)}`)
|
ck_err(cat, e, `{what} wants {ck_a(to)} and this is {ck_a(from)}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ function ck_any(n: Node) -> void {
|
||||||
if n == null { return }
|
if n == null { return }
|
||||||
if n.kind == N_BLOCK { ck_block(n); return }
|
if n.kind == N_BLOCK { ck_block(n); return }
|
||||||
if n.kind == E_FINIT { ck_expr(n.a); return }
|
if n.kind == E_FINIT { ck_expr(n.a); return }
|
||||||
if (n.kind >= S_LET and n.kind <= S_BECOME) or n.kind == S_EMIT { ck_stmt(n); return }
|
if (n.kind >= S_LET and n.kind <= S_BECOME) or n.kind == S_EMIT or n.kind == S_UNSAFE { ck_stmt(n); return }
|
||||||
ck_expr(n)
|
ck_expr(n)
|
||||||
}
|
}
|
||||||
function ck_expr(e: Node) -> pointer {
|
function ck_expr(e: Node) -> pointer {
|
||||||
|
|
@ -125,6 +125,7 @@ function ck_member(e: Node) -> pointer {
|
||||||
function ck_index_of(e: Node) -> pointer {
|
function ck_index_of(e: Node) -> pointer {
|
||||||
let bt = ck_expr(e.a)
|
let bt = ck_expr(e.a)
|
||||||
let it = ck_expr(e.b)
|
let it = ck_expr(e.b)
|
||||||
|
if ck_is_raw(bt) { ck_raw(e, `indexing {ck_a(bt)}`) }
|
||||||
if not ck_unknown(it) and not ck_is_int(it) { ck_err("index", e.b, `an index wants an int and this is {ck_a(it)}`) }
|
if not ck_unknown(it) and not ck_is_int(it) { ck_err("index", e.b, `an index wants an int and this is {ck_a(it)}`) }
|
||||||
if ck_unknown(bt) { return "?" }
|
if ck_unknown(bt) { return "?" }
|
||||||
if is_slice_ty(bt) { return slice_elem(bt) }
|
if is_slice_ty(bt) { return slice_elem(bt) }
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,13 @@ function ck_stmt(s: Node) -> void {
|
||||||
if k == S_EXPR { ck_expr(s.a); return }
|
if k == S_EXPR { ck_expr(s.a); return }
|
||||||
if k == S_MATCH { ck_match(s); return }
|
if k == S_MATCH { ck_match(s); return }
|
||||||
if k == S_EMIT { ck_emit(s); return }
|
if k == S_EMIT { ck_emit(s); return }
|
||||||
|
if k == S_UNSAFE {
|
||||||
|
ck_unsafe_here(s)
|
||||||
|
ck_unsafe += 1
|
||||||
|
ck_block(s.a)
|
||||||
|
ck_unsafe -= 1
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function ck_fn_body(d: Node) -> void {
|
function ck_fn_body(d: Node) -> void {
|
||||||
let m = ck_mark()
|
let m = ck_mark()
|
||||||
|
|
@ -117,7 +124,12 @@ function ck_fn_body(d: Node) -> void {
|
||||||
}
|
}
|
||||||
ck_ret = d.ty
|
ck_ret = d.ty
|
||||||
if ck_ret == null { ck_ret = "void" }
|
if ck_ret == null { ck_ret = "void" }
|
||||||
|
if d.uns == 1 {
|
||||||
|
ck_unsafe_here(d)
|
||||||
|
ck_unsafe += 1
|
||||||
|
}
|
||||||
ck_block(d.a)
|
ck_block(d.a)
|
||||||
|
if d.uns == 1 { ck_unsafe -= 1 }
|
||||||
ck_ret = "void"
|
ck_ret = "void"
|
||||||
ck_pop(m)
|
ck_pop(m)
|
||||||
}
|
}
|
||||||
|
|
@ -140,7 +152,7 @@ function check_program() -> void {
|
||||||
gen_collect()
|
gen_collect()
|
||||||
ck_index()
|
ck_index()
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < g_prog_user_end {
|
while i < len(prog) {
|
||||||
let d = prog[i]
|
let d = prog[i]
|
||||||
if d.kind == N_FN { ck_fn_body(d) }
|
if d.kind == N_FN { ck_fn_body(d) }
|
||||||
if d.kind == N_MAIN or d.kind == N_TEST { ck_block(d.a) }
|
if d.kind == N_MAIN or d.kind == N_TEST { ck_block(d.a) }
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
# agrees with everything, so the checker only ever reports a mix-up it can prove. "null" is the
|
# agrees with everything, so the checker only ever reports a mix-up it can prove. "null" is the
|
||||||
# type of the null literal, and `pointer` is untyped: numbers, bools, text, records, slices and
|
# type of the null literal, and `pointer` is untyped: numbers, bools, text, records, slices and
|
||||||
# functions are kept apart, and a pointer is trusted to be whatever it is given as.
|
# functions are kept apart, and a pointer is trusted to be whatever it is given as.
|
||||||
|
var ck_extern_arg: bool = false # an extern's arguments are being given: a slice goes as its data
|
||||||
function ck_unknown(t: pointer) -> bool { return t == null or (t == "?") }
|
function ck_unknown(t: pointer) -> bool { return t == null or (t == "?") }
|
||||||
function ck_is_int(t: pointer) -> bool {
|
function ck_is_int(t: pointer) -> bool {
|
||||||
if (t == "int") or (t == "long") or (t == "byte") or (t == "i64") or (t == "u8") { return true }
|
if (t == "int") or (t == "long") or (t == "byte") or (t == "i64") or (t == "u8") { return true }
|
||||||
|
|
@ -72,6 +73,17 @@ function ck_mismatch(to: pointer, from: pointer, e: Node) -> pointer {
|
||||||
if ck_is_num(from) { return "kind" }
|
if ck_is_num(from) { return "kind" }
|
||||||
# `pointer` is the untyped reference, C's void *: it goes anywhere a reference does, and a
|
# `pointer` is the untyped reference, C's void *: it goes anywhere a reference does, and a
|
||||||
# reference goes into it. What it may hold is L7's question (unsafe), not this pass's.
|
# reference goes into it. What it may hold is L7's question (unsafe), not this pass's.
|
||||||
|
# `bytes` is a raw buffer, a pointer by another name: the same rules
|
||||||
|
if (to == "bytes") and is_slice_ty(from) {
|
||||||
|
if ck_extern_arg { return null }
|
||||||
|
return "slice-pointer"
|
||||||
|
}
|
||||||
|
if (from == "bytes") and is_slice_ty(to) { return "pointer-slice" }
|
||||||
|
if (to == "pointer") and is_slice_ty(from) {
|
||||||
|
if ck_extern_arg { return null }
|
||||||
|
return "slice-pointer"
|
||||||
|
}
|
||||||
|
if (from == "pointer") and is_slice_ty(to) { return "pointer-slice" }
|
||||||
if (to == "pointer") or (from == "pointer") { return null }
|
if (to == "pointer") or (from == "pointer") { return null }
|
||||||
if (to == "string") { return "kind" }
|
if (to == "string") { return "kind" }
|
||||||
if ck_is_rec(to) and ck_is_rec(from) { return "record" }
|
if ck_is_rec(to) and ck_is_rec(from) { return "record" }
|
||||||
|
|
|
||||||
29
selfhost/check/check_unsafe.ludic
Normal file
29
selfhost/check/check_unsafe.ludic
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# check_unsafe.ludic — L7: raw memory is `unsafe`. A bytes() buffer, indexing a bare pointer,
|
||||||
|
# data_of(a slice), free, resize, the raw file calls, Memory.* and calling a C function (an
|
||||||
|
# `extern`) are refused outside an `unsafe { }` block or an `unsafe function`. The typed buffers -
|
||||||
|
# words(n), floats(n) - are slices, bounds-checked, and need none of it.
|
||||||
|
# And `unsafe` itself is only for the files that are the platform: the runtime, a package the
|
||||||
|
# toolchain or ludic_modules provides, and what they import beside them - a project's own files
|
||||||
|
# may write it only when the build says --unsafe. A game is written against APIs, not memory.
|
||||||
|
var ck_unsafe: int = 0 # how many unsafe blocks and functions enclose this point
|
||||||
|
|
||||||
|
# words(n), floats(n) and the rest are slices now - bounds-checked, safe - so what is raw is a
|
||||||
|
# bare pointer indexed, and the builtins that hand out or take back addresses
|
||||||
|
function ck_is_raw(t: pointer) -> bool { return (t == "pointer") or (t == "bytes") }
|
||||||
|
function ck_raw_builtin(name: pointer) -> bool {
|
||||||
|
if (name == "bytes") or (name == "data_of") or (name == "free") or (name == "resize") { return true }
|
||||||
|
return (name == "file_read") or (name == "file_write")
|
||||||
|
}
|
||||||
|
# the platform - the runtime and the packages - is raw memory by trade: its files are unsafe
|
||||||
|
# throughout, and the rule is for a project's own code
|
||||||
|
function ck_raw(n: Node, what: pointer) -> void {
|
||||||
|
if ck_unsafe > 0 { return }
|
||||||
|
if n != null and n.file != null and unsafe_trusted(n.file) { return }
|
||||||
|
ck_err("unsafe", n, `{what} is raw memory: it belongs inside unsafe {{ }}, and a game reaches it through an API`)
|
||||||
|
}
|
||||||
|
# an unsafe block or function where the file may not have one
|
||||||
|
function ck_unsafe_here(n: Node) -> void {
|
||||||
|
if n == null or n.file == null { return }
|
||||||
|
if unsafe_trusted(n.file) { return }
|
||||||
|
ck_err("unsafe-block", n, "unsafe is for the runtime and packages; this file may use it only when the build says --unsafe")
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,7 @@ const E_TRY: int = 52 # try EXPR else { ... } — recover a fallible
|
||||||
# a=the fallible (result-typed) expression b=else block (its
|
# a=the fallible (result-typed) expression b=else block (its
|
||||||
# trailing expression is the fallback) line=source line
|
# trailing expression is the fallback) line=source line
|
||||||
const E_LIST: int = 54 # [a, b, c] — a slice literal; kids=the elements, all of one type
|
const E_LIST: int = 54 # [a, b, c] — a slice literal; kids=the elements, all of one type
|
||||||
|
const S_UNSAFE: int = 56 # unsafe { ... } — raw memory allowed inside (L7); a = the block
|
||||||
const E_FNREF: int = 55 # fn name — a top-level function as a value (s=the name); a worker entry point
|
const E_FNREF: int = 55 # fn name — a top-level function as a value (s=the name); a worker entry point
|
||||||
# statements
|
# statements
|
||||||
const S_LET: int = 10
|
const S_LET: int = 10
|
||||||
|
|
@ -87,6 +88,7 @@ property Node {
|
||||||
file: pointer = null # the source file the node was parsed from (for diagnostics)
|
file: pointer = null # the source file the node was parsed from (for diagnostics)
|
||||||
vis: int = 0 # L3: 1 when the declaration is `export`ed from its module
|
vis: int = 0 # L3: 1 when the declaration is `export`ed from its module
|
||||||
tps: pointer = null # L5: a generic declaration's type parameters, "T|U"; null when not generic
|
tps: pointer = null # L5: a generic declaration's type parameters, "T|U"; null when not generic
|
||||||
|
uns: int = 0 # L7: 1 on an `unsafe function`
|
||||||
}
|
}
|
||||||
|
|
||||||
# every node remembers where it was parsed (file + the line of the token the
|
# every node remembers where it was parsed (file + the line of the token the
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,21 @@ var g_float_files: []pointer = new []pointer
|
||||||
# L3 modules: `module NAME` at the top of a file names the module it and everything it imports
|
# L3 modules: `module NAME` at the top of a file names the module it and everything it imports
|
||||||
# belong to, until an import names its own; `friend module NAME` may see every module's private
|
# belong to, until an import names its own; `friend module NAME` may see every module's private
|
||||||
# names (a test harness). A file in no module - the runtime, a program's root - is public.
|
# names (a test harness). A file in no module - the runtime, a program's root - is public.
|
||||||
|
# L7: the files that may write `unsafe` - the runtime, a package from the toolchain or
|
||||||
|
# ludic_modules, and what those import from beside them. A project's own files may only
|
||||||
|
# with --unsafe (g_unsafe_all).
|
||||||
|
var g_uses_bytes: bool = false # text_of / Fs.read_bytes / Fs.write_bytes: splice bytes.ludic
|
||||||
|
var g_trusted_files: []pointer = new []pointer
|
||||||
|
var g_unsafe_all: bool = false
|
||||||
|
function unsafe_trusted(f: pointer) -> bool {
|
||||||
|
if g_unsafe_all { return true }
|
||||||
|
var i = 0
|
||||||
|
while i < len(g_trusted_files) {
|
||||||
|
if (g_trusted_files[i] == f) { return true }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
var g_mod_file: []pointer = new []pointer
|
var g_mod_file: []pointer = new []pointer
|
||||||
var g_mod_name: []pointer = new []pointer
|
var g_mod_name: []pointer = new []pointer
|
||||||
var g_mod_friends: []pointer = new []pointer
|
var g_mod_friends: []pointer = new []pointer
|
||||||
|
|
@ -92,6 +107,16 @@ function eat_id() -> pointer {
|
||||||
function skipnl() -> void { while toks[pi].kind == TK_NL { pi += 1 } }
|
function skipnl() -> void { while toks[pi].kind == TK_NL { pi += 1 } }
|
||||||
|
|
||||||
# a type: `[]T` slice, `fn(T, U) -> R` function, or a plain name (int/ptr/str/bool/struct)
|
# a type: `[]T` slice, `fn(T, U) -> R` function, or a plain name (int/ptr/str/bool/struct)
|
||||||
|
# L7: the typed buffers are slices - a length, a bounds check on every index, and a place in the
|
||||||
|
# checker - so `floats` is `[]float`, `words` is `[]int`, and so on. `bytes()` stays raw.
|
||||||
|
function buffer_slice_ty(t: pointer) -> pointer {
|
||||||
|
if (t == "floats") { return "[]float" }
|
||||||
|
if (t == "words") { return "[]int" }
|
||||||
|
if (t == "fixeds") { return "[]fixed" }
|
||||||
|
if (t == "doubles") { return "[]double" }
|
||||||
|
if (t == "pointers") { return "[]pointer" }
|
||||||
|
return t
|
||||||
|
}
|
||||||
function ptype() -> pointer {
|
function ptype() -> pointer {
|
||||||
if (toks[pi].text == "fn") and (toks[pi + 1].text == "(") {
|
if (toks[pi].text == "fn") and (toks[pi + 1].text == "(") {
|
||||||
pi += 1
|
pi += 1
|
||||||
|
|
@ -125,7 +150,7 @@ function ptype() -> pointer {
|
||||||
}
|
}
|
||||||
let tn = eat_id()
|
let tn = eat_id()
|
||||||
if is_op("<") { return gen_type_args(tn) }
|
if is_op("<") { return gen_type_args(tn) }
|
||||||
return tn
|
return buffer_slice_ty(tn)
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- expressions -----------------------------------------------------------
|
# ---- expressions -----------------------------------------------------------
|
||||||
|
|
@ -267,6 +292,7 @@ function p_primary() -> Node {
|
||||||
n.b = block()
|
n.b = block()
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
if (t.text == "text_of") { g_uses_bytes = true }
|
||||||
let n = node(E_ID); n.s = t.text; pi += 1; return n
|
let n = node(E_ID); n.s = t.text; pi += 1; return n
|
||||||
}
|
}
|
||||||
if is_op("(") { pi += 1; skipnl(); let e = expr(); skipnl(); eat_op(")"); return e }
|
if is_op("(") { pi += 1; skipnl(); let e = expr(); skipnl(); eat_op(")"); return e }
|
||||||
|
|
@ -279,6 +305,7 @@ function p_postfix() -> Node {
|
||||||
while true {
|
while true {
|
||||||
if is_op(".") { pi += 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m
|
if is_op(".") { pi += 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m
|
||||||
if e.a.kind == E_ID and e.a.s == "Regex" { g_uses_regex = true } # splice the regex runtime on demand
|
if e.a.kind == E_ID and e.a.s == "Regex" { g_uses_regex = true } # splice the regex runtime on demand
|
||||||
|
if e.a.kind == E_ID and e.a.s == "Fs" and (e.s == "read_bytes" or e.s == "write_bytes") { g_uses_bytes = true }
|
||||||
if e.a.kind == E_ID and (e.a.s == "BigInt" or e.a.s == "Decimal") { g_uses_bignum = true } # splice the bignum runtime on demand
|
if e.a.kind == E_ID and (e.a.s == "BigInt" or e.a.s == "Decimal") { g_uses_bignum = true } # splice the bignum runtime on demand
|
||||||
if e.a.kind == E_ID and (e.a.s == "Dict" or e.a.s == "Set") { g_uses_dict = true } # splice the hash-table runtime on demand
|
if e.a.kind == E_ID and (e.a.s == "Dict" or e.a.s == "Set") { g_uses_dict = true } # splice the hash-table runtime on demand
|
||||||
if e.a.kind == E_ID and (e.a.s == "Huge" or e.a.s == "Angle" or e.a.s == "Percent") { g_uses_numeric = true } # splice the huge/angle/percent runtime on demand
|
if e.a.kind == E_ID and (e.a.s == "Huge" or e.a.s == "Angle" or e.a.s == "Percent") { g_uses_numeric = true } # splice the huge/angle/percent runtime on demand
|
||||||
|
|
@ -488,6 +515,12 @@ function stmt_body() -> Node {
|
||||||
pi += 1; n.a = expr()
|
pi += 1; n.a = expr()
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
if (t.text == "unsafe") and (toks[pi + 1].text == "{") { # L7: raw memory allowed inside
|
||||||
|
let un = node(S_UNSAFE)
|
||||||
|
pi += 1
|
||||||
|
un.a = block()
|
||||||
|
return un
|
||||||
|
}
|
||||||
if (t.text == "break") { pi += 1; return node(S_BREAK) }
|
if (t.text == "break") { pi += 1; return node(S_BREAK) }
|
||||||
if (t.text == "continue") { pi += 1; return node(S_CONTINUE) }
|
if (t.text == "continue") { pi += 1; return node(S_CONTINUE) }
|
||||||
if (t.text == "cancel") { pi += 1; return node(S_CANCEL) } # veto a cancellable event
|
if (t.text == "cancel") { pi += 1; return node(S_CANCEL) } # veto a cancellable event
|
||||||
|
|
@ -980,8 +1013,14 @@ function parse_one_decl() -> void {
|
||||||
if is_id("namespace") { parse_namespace(); return } # #76 namespace block
|
if is_id("namespace") { parse_namespace(); return } # #76 namespace block
|
||||||
if is_id("var") { push(prog, parse_var()); return }
|
if is_id("var") { push(prog, parse_var()); return }
|
||||||
if is_id("const") { push(prog, parse_const()); return }
|
if is_id("const") { push(prog, parse_const()); return }
|
||||||
|
var is_unsafe = false
|
||||||
|
if is_id("unsafe") and (toks[pi + 1].text == "function") { # L7: an unsafe function
|
||||||
|
pi += 1
|
||||||
|
is_unsafe = true
|
||||||
|
}
|
||||||
if is_id("function") {
|
if is_id("function") {
|
||||||
let f = parse_fn()
|
let f = parse_fn()
|
||||||
|
if is_unsafe { f.uns = 1 }
|
||||||
if is_det { push(g_det_names, f.s) }
|
if is_det { push(g_det_names, f.s) }
|
||||||
if is_export { f.ival = 1 }
|
if is_export { f.ival = 1 }
|
||||||
if (sys_phase != null) { push(g_mod_sys_fn, f.s); push(g_mod_sys_phase, sys_phase) } # #64: register at load
|
if (sys_phase != null) { push(g_mod_sys_fn, f.s); push(g_mod_sys_phase, sys_phase) } # #64: register at load
|
||||||
|
|
@ -1111,6 +1150,7 @@ function do_import(rel: pointer) -> void {
|
||||||
# a module reaches as far as its own files: a package found through $LUDIC_HOME or
|
# a module reaches as far as its own files: a package found through $LUDIC_HOME or
|
||||||
# ludic_modules is not beside its importer and keeps its own module (or none)
|
# ludic_modules is not beside its importer and keeps its own module (or none)
|
||||||
let beside = full == join_path(cur_dir, rel)
|
let beside = full == join_path(cur_dir, rel)
|
||||||
|
if is_runtime_path(rel) or not beside or (beside and unsafe_trusted(g_parse_file)) { push(g_trusted_files, full) }
|
||||||
if beside and not is_runtime_path(rel) and not (module_of(g_parse_file) == "") { module_set(full, module_of(g_parse_file)) }
|
if beside and not is_runtime_path(rel) and not (module_of(g_parse_file) == "") { module_set(full, module_of(g_parse_file)) }
|
||||||
if (src == null) { perr(`cannot open import {full}`) }
|
if (src == null) { perr(`cannot open import {full}`) }
|
||||||
# the audio runtime can arrive through atlas.ludic's own import or the Assets
|
# the audio runtime can arrive through atlas.ludic's own import or the Assets
|
||||||
|
|
@ -1138,6 +1178,9 @@ function maybe_splice_runtime() -> void {
|
||||||
# L6: the engine's namespaces that are aliases of runtime functions, declared in Ludic
|
# L6: the engine's namespaces that are aliases of runtime functions, declared in Ludic
|
||||||
cur_dir = ""
|
cur_dir = ""
|
||||||
do_import("runtime/native/namespaces.ludic")
|
do_import("runtime/native/namespaces.ludic")
|
||||||
|
# L7: the byte buffer's API, when a program reads or writes bytes (it opens files through
|
||||||
|
# the asset pack, and a program that does not should not carry the pack's machinery)
|
||||||
|
if g_uses_bytes { do_import("runtime/native/bytes.ludic") }
|
||||||
cur_dir = saved
|
cur_dir = saved
|
||||||
# a game (has systems/components) links the Ludic runtime.
|
# a game (has systems/components) links the Ludic runtime.
|
||||||
if has_ecs() {
|
if has_ecs() {
|
||||||
|
|
|
||||||
66406
selfhost/ludicc.seed.ll
66406
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -147,6 +147,7 @@ entry {
|
||||||
else if a == "--windowed" { want = 1 }
|
else if a == "--windowed" { want = 1 }
|
||||||
else if a == "--headless" { want = 2 }
|
else if a == "--headless" { want = 2 }
|
||||||
else if a == "--emit-llvm" { emit_ir = true }
|
else if a == "--emit-llvm" { emit_ir = true }
|
||||||
|
else if a == "--unsafe" { g_unsafe_all = true } # L7: this program's own files may write `unsafe`
|
||||||
else if a == "--emit-module" { g_emit_module = true; emit_ir = true } # issue #64: prebuilt binary module IR
|
else if a == "--emit-module" { g_emit_module = true; emit_ir = true } # issue #64: prebuilt binary module IR
|
||||||
else if a == "--fmt" { fmt = true }
|
else if a == "--fmt" { fmt = true }
|
||||||
else if a == "--save-temps" { save = true }
|
else if a == "--save-temps" { save = true }
|
||||||
|
|
|
||||||
|
|
@ -47,17 +47,17 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
||||||
var flags = "--windowed"
|
var flags = "--windowed"
|
||||||
if mode == 2 { flags = "--headless" }
|
if mode == 2 { flags = "--headless" }
|
||||||
if save { flags = flags + " --save-temps" }
|
if save { flags = flags + " --save-temps" }
|
||||||
return shq(`{ludicc()} {flags}{title_flag()} {src} -o {out}`)
|
return shq(`{ludicc()} {flags}{title_flag()}{unsafe_flag()} {src} -o {out}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode == 2 {
|
if mode == 2 {
|
||||||
if not shq(`{ludicc()} --headless{title_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
if not shq(`{ludicc()} --headless{title_flag()}{unsafe_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||||
if not shq(`{cc()} -O2 {ll}{gl_link_flags(ll)}{vk_link_flags(ll)}{http_link_flags(ll)}{udp_link_flags(ll)}{process_link_flags(ll)}{threads_link_flags(ll)}{pbf} -o {out}`) { return false }
|
if not shq(`{cc()} -O2 {ll}{gl_link_flags(ll)}{vk_link_flags(ll)}{http_link_flags(ll)}{udp_link_flags(ll)}{process_link_flags(ll)}{threads_link_flags(ll)}{pbf} -o {out}`) { return false }
|
||||||
if not save { shell(`rm -f {ll}`) }
|
if not save { shell(`rm -f {ll}`) }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if not shq(`{ludicc()} --windowed{title_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
if not shq(`{ludicc()} --windowed{title_flag()}{unsafe_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||||
# audio.ll (#22) is always linked here — unused snd_* are dead-stripped; the
|
# audio.ll (#22) is always linked here — unused snd_* are dead-stripped; the
|
||||||
# canonical `ludicc -o` path links it only when Audio.* is used.
|
# canonical `ludicc -o` path links it only when Audio.* is used.
|
||||||
let cocoa = `{home}runtime/native/cocoa.ll`
|
let cocoa = `{home}runtime/native/cocoa.ll`
|
||||||
|
|
@ -70,6 +70,12 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
||||||
# ` --title "<app name>"` when the project's package.ludic names the app, so the window
|
# ` --title "<app name>"` when the project's package.ludic names the app, so the window
|
||||||
# opens under that name rather than the `program` name; "" otherwise, and for a name the
|
# opens under that name rather than the `program` name; "" otherwise, and for a name the
|
||||||
# shell line could not carry safely.
|
# shell line could not carry safely.
|
||||||
|
# ` --unsafe` when the build said so: the project's own files may write `unsafe` (L7)
|
||||||
|
var g_unsafe_build: bool = false
|
||||||
|
function unsafe_flag() -> pointer {
|
||||||
|
if g_unsafe_build { return " --unsafe" }
|
||||||
|
return ""
|
||||||
|
}
|
||||||
function title_flag() -> pointer {
|
function title_flag() -> pointer {
|
||||||
let name = manifest_app(read_root_manifest(), "name")
|
let name = manifest_app(read_root_manifest(), "name")
|
||||||
if name == "" { return "" }
|
if name == "" { return "" }
|
||||||
|
|
|
||||||
|
|
@ -275,6 +275,7 @@ function parse_build_args(start: int) -> pointer {
|
||||||
if a == "--headless" { g_mode = 2 }
|
if a == "--headless" { g_mode = 2 }
|
||||||
else if a == "--windowed" { g_mode = 1 }
|
else if a == "--windowed" { g_mode = 1 }
|
||||||
else if a == "--save-temps" { g_save = true }
|
else if a == "--save-temps" { g_save = true }
|
||||||
|
else if a == "--unsafe" { g_unsafe_build = true }
|
||||||
else if a == "-o" {
|
else if a == "-o" {
|
||||||
ai += 1
|
ai += 1
|
||||||
if ai < arg_count() { g_out = arg(ai) }
|
if ai < arg_count() { g_out = arg(ai) }
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ function selfhost_frags() -> []pointer {
|
||||||
push(f, "selfhost/check/check_stmt.ludic")
|
push(f, "selfhost/check/check_stmt.ludic")
|
||||||
push(f, "selfhost/check/check_gen.ludic")
|
push(f, "selfhost/check/check_gen.ludic")
|
||||||
push(f, "selfhost/check/check_gen_call.ludic")
|
push(f, "selfhost/check/check_gen_call.ludic")
|
||||||
|
push(f, "selfhost/check/check_unsafe.ludic")
|
||||||
push(f, "selfhost/backend/emit_stmt.ludic")
|
push(f, "selfhost/backend/emit_stmt.ludic")
|
||||||
push(f, "selfhost/backend/game/emit_ecs.ludic")
|
push(f, "selfhost/backend/game/emit_ecs.ludic")
|
||||||
push(f, "selfhost/backend/game/emit_query.ludic")
|
push(f, "selfhost/backend/game/emit_query.ludic")
|
||||||
|
|
@ -116,7 +117,7 @@ function cmd_selfhost_build(lc: pointer, outbin: pointer) -> int {
|
||||||
if not write_selfhost_src(src) { err("ludic-dev: cannot write build/selfhost.ludic\n"); return 1 }
|
if not write_selfhost_src(src) { err("ludic-dev: cannot write build/selfhost.ludic\n"); return 1 }
|
||||||
let ll = `{outbin}.ll`
|
let ll = `{outbin}.ll`
|
||||||
# say why when it fails: it used to exit 1 with nothing on the screen
|
# say why when it fails: it used to exit 1 with nothing on the screen
|
||||||
if not shq(`{lc} {src} > {ll} 2>{tmp_dir()}/shb.err`) {
|
if not shq(`{lc} --unsafe {src} > {ll} 2>{tmp_dir()}/shb.err`) {
|
||||||
shell(`rm -f {ll}`)
|
shell(`rm -f {ll}`)
|
||||||
err(capture(`grep -i error {tmp_dir()}/shb.err | head -5`))
|
err(capture(`grep -i error {tmp_dir()}/shb.err | head -5`))
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -142,9 +143,16 @@ function cmd_sh_compile(shbin: pointer, in: pointer, outbin: pointer) -> int {
|
||||||
|
|
||||||
# the quiet core, reused by the test suites; returns true on success. On failure
|
# the quiet core, reused by the test suites; returns true on success. On failure
|
||||||
# the self-host/link diagnostics are left in tmp_path("gb.err").
|
# the self-host/link diagnostics are left in tmp_path("gb.err").
|
||||||
|
# an example that is a raw-memory program on purpose (a decoder's ABI, a Vulkan demo) is built
|
||||||
|
# with --unsafe (L7); the suite sets this around those cases only
|
||||||
|
var g_case_unsafe: bool = false
|
||||||
|
function case_unsafe_flag() -> pointer {
|
||||||
|
if g_case_unsafe { return " --unsafe" }
|
||||||
|
return ""
|
||||||
|
}
|
||||||
function game_build_ok(shbin: pointer, game: pointer, outbin: pointer) -> bool {
|
function game_build_ok(shbin: pointer, game: pointer, outbin: pointer) -> bool {
|
||||||
let ll = `{outbin}.ll`
|
let ll = `{outbin}.ll`
|
||||||
if not shq(`{shbin} {game} > {ll} 2>{tmp_dir()}/gb.err`) { return false }
|
if not shq(`{shbin}{case_unsafe_flag()} {game} > {ll} 2>{tmp_dir()}/gb.err`) { return false }
|
||||||
if not shq(`{cc()} -O2 {ll}{threads_link_flags(ll)} -o {outbin} 2>{tmp_dir()}/gb.err`) { return false }
|
if not shq(`{cc()} -O2 {ll}{threads_link_flags(ll)} -o {outbin} 2>{tmp_dir()}/gb.err`) { return false }
|
||||||
shell(`rm -f {ll}`)
|
shell(`rm -f {ll}`)
|
||||||
return true
|
return true
|
||||||
|
|
@ -168,11 +176,11 @@ function cmd_bootstrap() -> int {
|
||||||
if cmd_selfhost_build("bin/ludicc", "build/boot/gen1") != 0 { print("FAIL: stage0 build"); return 1 }
|
if cmd_selfhost_build("bin/ludicc", "build/boot/gen1") != 0 { print("FAIL: stage0 build"); return 1 }
|
||||||
print(" stage0: bin/ludicc -> gen1 (self-host compiler)")
|
print(" stage0: bin/ludicc -> gen1 (self-host compiler)")
|
||||||
|
|
||||||
if not shq("build/boot/gen1 build/selfhost.ludic > build/boot/gen2.ll 2>/dev/null") { print("FAIL: gen1 self-compile"); return 1 }
|
if not shq("build/boot/gen1 --unsafe build/selfhost.ludic > build/boot/gen2.ll 2>/dev/null") { print("FAIL: gen1 self-compile"); return 1 }
|
||||||
if not shq(`{cc()} build/boot/gen2.ll -o build/boot/gen2 2>/dev/null`) { print("FAIL: gen2 assemble"); return 1 }
|
if not shq(`{cc()} build/boot/gen2.ll -o build/boot/gen2 2>/dev/null`) { print("FAIL: gen2 assemble"); return 1 }
|
||||||
print(` stage1: gen1 -> gen2.ll ({line_count("build/boot/gen2.ll")} lines) -> gen2`)
|
print(` stage1: gen1 -> gen2.ll ({line_count("build/boot/gen2.ll")} lines) -> gen2`)
|
||||||
|
|
||||||
if not shq("build/boot/gen2 build/selfhost.ludic > build/boot/gen3.ll 2>/dev/null") { print("FAIL: gen2 self-compile"); return 1 }
|
if not shq("build/boot/gen2 --unsafe build/selfhost.ludic > build/boot/gen3.ll 2>/dev/null") { print("FAIL: gen2 self-compile"); return 1 }
|
||||||
print(` stage2: gen2 -> gen3.ll ({line_count("build/boot/gen3.ll")} lines)`)
|
print(` stage2: gen2 -> gen3.ll ({line_count("build/boot/gen3.ll")} lines)`)
|
||||||
|
|
||||||
if shq("cmp -s build/boot/gen2.ll build/boot/gen3.ll") {
|
if shq("cmp -s build/boot/gen2.ll build/boot/gen3.ll") {
|
||||||
|
|
@ -194,7 +202,7 @@ function cmd_bootstrap_cfree() -> int {
|
||||||
if not shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_seed 2>/dev/null`) { print("FAIL: assemble seed"); return 1 }
|
if not shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_seed 2>/dev/null`) { print("FAIL: assemble seed"); return 1 }
|
||||||
print(" seed.ll --clang--> sh_seed (no C compiler used)")
|
print(" seed.ll --clang--> sh_seed (no C compiler used)")
|
||||||
if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 }
|
if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 }
|
||||||
if not shq("build/cfree/sh_seed build/cfree/selfhost.ludic > build/cfree/out.ll 2>/dev/null") { print("FAIL: seed compiler self-compile"); return 1 }
|
if not shq("build/cfree/sh_seed --unsafe build/cfree/selfhost.ludic > build/cfree/out.ll 2>/dev/null") { print("FAIL: seed compiler self-compile"); return 1 }
|
||||||
print(` sh_seed compiles selfhost.ludic -> out.ll ({line_count("build/cfree/out.ll")} lines)`)
|
print(` sh_seed compiles selfhost.ludic -> out.ll ({line_count("build/cfree/out.ll")} lines)`)
|
||||||
if shq("cmp -s build/cfree/out.ll selfhost/ludicc.seed.ll") {
|
if shq("cmp -s build/cfree/out.ll selfhost/ludicc.seed.ll") {
|
||||||
print(" out.ll == seed.ll — the compiler rebuilds itself with no C compiler")
|
print(" out.ll == seed.ll — the compiler rebuilds itself with no C compiler")
|
||||||
|
|
@ -214,17 +222,17 @@ function cmd_reseed() -> int {
|
||||||
shell("mkdir -p build/cfree")
|
shell("mkdir -p build/cfree")
|
||||||
if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 }
|
if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 }
|
||||||
if shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_old 2>/dev/null`) {
|
if shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_old 2>/dev/null`) {
|
||||||
if not shq("build/cfree/sh_old build/cfree/selfhost.ludic > build/cfree/step1.ll") { print("FAIL: step1"); return 1 }
|
if not shq("build/cfree/sh_old --unsafe build/cfree/selfhost.ludic > build/cfree/step1.ll") { print("FAIL: step1"); return 1 }
|
||||||
if not shq(`{cc()} build/cfree/step1.ll -o build/cfree/sh_new`) { print("FAIL: assemble sh_new"); return 1 }
|
if not shq(`{cc()} build/cfree/step1.ll -o build/cfree/sh_new`) { print("FAIL: assemble sh_new"); return 1 }
|
||||||
if not shq("build/cfree/sh_new build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
if not shq("build/cfree/sh_new --unsafe build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||||
# and the Windows seed, from the same compiler: a Windows checkout bootstraps from it
|
# and the Windows seed, from the same compiler: a Windows checkout bootstraps from it
|
||||||
if not shq("build/cfree/sh_new --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
if not shq("build/cfree/sh_new --unsafe --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||||
} else {
|
} else {
|
||||||
print("seed does not build; reseeding from bin/ludicc")
|
print("seed does not build; reseeding from bin/ludicc")
|
||||||
ensure_ludicc()
|
ensure_ludicc()
|
||||||
if cmd_selfhost_build("bin/ludicc", "build/cfree/sh_c") != 0 { print("FAIL: build from bin/ludicc"); return 1 }
|
if cmd_selfhost_build("bin/ludicc", "build/cfree/sh_c") != 0 { print("FAIL: build from bin/ludicc"); return 1 }
|
||||||
if not shq("build/cfree/sh_c build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
if not shq("build/cfree/sh_c --unsafe build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||||
if not shq("build/cfree/sh_c --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
if not shq("build/cfree/sh_c --unsafe --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||||
}
|
}
|
||||||
print(`reseeded: {line_count("selfhost/ludicc.seed.ll")} lines`)
|
print(`reseeded: {line_count("selfhost/ludicc.seed.ll")} lines`)
|
||||||
return 0
|
return 0
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ function sh_case(name: pointer, exp: pointer) -> void {
|
||||||
let ll = `{tmp_dir()}/sh_{name}.ll`
|
let ll = `{tmp_dir()}/sh_{name}.ll`
|
||||||
let er = `{tmp_dir()}/sh_{name}.err`
|
let er = `{tmp_dir()}/sh_{name}.err`
|
||||||
let bn = `{tmp_dir()}/sh_{name}`
|
let bn = `{tmp_dir()}/sh_{name}`
|
||||||
if not shq(`bin/ludicc selfhost/tests/{name}.ludic > {ll} 2>{er}`) {
|
if not shq(`bin/ludicc --unsafe selfhost/tests/{name}.ludic > {ll} 2>{er}`) {
|
||||||
bad2(`{name}: self-host errored`, capture_line(`head -1 {er}`)); return
|
bad2(`{name}: self-host errored`, capture_line(`head -1 {er}`)); return
|
||||||
}
|
}
|
||||||
if not shq(`{cc()} {ll} -o {bn} 2>/dev/null`) { bad(`{name}: IR did not assemble`); return }
|
if not shq(`{cc()} {ll} -o {bn} 2>/dev/null`) { bad(`{name}: IR did not assemble`); return }
|
||||||
|
|
|
||||||
|
|
@ -477,7 +477,7 @@ function net_case(path: pointer, exp: pointer) -> void {
|
||||||
let nm = flat(path)
|
let nm = flat(path)
|
||||||
let ll = `{tmp_dir()}/n_{nm}.ll`
|
let ll = `{tmp_dir()}/n_{nm}.ll`
|
||||||
let log = `{tmp_dir()}/n_{nm}.out`
|
let log = `{tmp_dir()}/n_{nm}.out`
|
||||||
if not shq(`bin/ludicc --headless examples/{path}.ludic --emit-llvm -o {ll} > {log} 2>&1`) {
|
if not shq(`bin/ludicc{case_unsafe_flag()} --headless examples/{path}.ludic --emit-llvm -o {ll} > {log} 2>&1`) {
|
||||||
let t = capture_line(`tail -1 {log}`)
|
let t = capture_line(`tail -1 {log}`)
|
||||||
bad2(path, `build ({t})`); return
|
bad2(path, `build ({t})`); return
|
||||||
}
|
}
|
||||||
|
|
@ -647,7 +647,9 @@ function cmd_dev_test() -> int {
|
||||||
feat_case("library/query", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18", "query.ludic (Query count/first/nearest/within — ECS spatial queries over the reflection ABI)")
|
feat_case("library/query", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18", "query.ludic (Query count/first/nearest/within — ECS spatial queries over the reflection ABI)")
|
||||||
feat_case("library/reflect", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20", "reflect.ludic (Reflect prop/field enumeration + type + get/set/has/kind — runtime reflection over the world schema)")
|
feat_case("library/reflect", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20", "reflect.ludic (Reflect prop/field enumeration + type + get/set/has/kind — runtime reflection over the world schema)")
|
||||||
feat_case("library/serialize", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16", "serialize.ludic (Value tree + Json encode/parse + Reflect.serialize/apply — bit-exact save/load; issue #44)")
|
feat_case("library/serialize", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16", "serialize.ludic (Value tree + Json encode/parse + Reflect.serialize/apply — bit-exact save/load; issue #44)")
|
||||||
|
g_case_unsafe = true # it drives the decoders' raw buffers
|
||||||
feat_case("library/tiled_p0", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21", "tiled_p0.ludic (Tiled P0: Xml reader + Base64 decode/encode + gzip framing over inflate; issue #67)")
|
feat_case("library/tiled_p0", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21", "tiled_p0.ludic (Tiled P0: Xml reader + Base64 decode/encode + gzip framing over inflate; issue #67)")
|
||||||
|
g_case_unsafe = false
|
||||||
feat_case("library/tiled_p05", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30", "tiled_p05.ludic (Tiled P0.5: TMX/TSX reader -> same Value-tree intermediate as TMJ; Kenney sampleMap.tmx; issue #68)")
|
feat_case("library/tiled_p05", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30", "tiled_p05.ludic (Tiled P0.5: TMX/TSX reader -> same Value-tree intermediate as TMJ; Kenney sampleMap.tmx; issue #68)")
|
||||||
feat_case("library/tiled_p1", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "tiled_p1.ludic (Tiled P1: rt_tmap model + GID resolver/flip decode + render; loads+draws Kenney map identically from .tmx and .tmj; issue #69)")
|
feat_case("library/tiled_p1", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "tiled_p1.ludic (Tiled P1: rt_tmap model + GID resolver/flip decode + render; loads+draws Kenney map identically from .tmx and .tmj; issue #69)")
|
||||||
feat_case("library/tiled_p2", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "tiled_p2.ludic (Tiled P2: per-tile objectgroup / property-convention / collision-layer -> Solids feed; A* baseline; issue #70)")
|
feat_case("library/tiled_p2", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "tiled_p2.ludic (Tiled P2: per-tile objectgroup / property-convention / collision-layer -> Solids feed; A* baseline; issue #70)")
|
||||||
|
|
@ -700,8 +702,10 @@ function cmd_dev_test() -> int {
|
||||||
# asserted from its own `entry`. The transport is the compiler's built-in
|
# asserted from its own `entry`. The transport is the compiler's built-in
|
||||||
# loopback, so a networked game runs with zero foreign code.
|
# loopback, so a networked game runs with zero foreign code.
|
||||||
net_case("events/mod_events", "10 32 42")
|
net_case("events/mod_events", "10 32 42")
|
||||||
|
g_case_unsafe = true # the transport ABI takes raw bytes
|
||||||
net_case("networking/net_echo", "4 10 20 30 42")
|
net_case("networking/net_echo", "4 10 20 30 42")
|
||||||
net_case("networking/net_snapshot", "50 7 50")
|
net_case("networking/net_snapshot", "50 7 50")
|
||||||
|
g_case_unsafe = false
|
||||||
net_case("networking/net_sync", "12 3 4 50 999")
|
net_case("networking/net_sync", "12 3 4 50 999")
|
||||||
net_case("networking/net_owner", "-1 7 0 1")
|
net_case("networking/net_owner", "-1 7 0 1")
|
||||||
net_case("networking/net_rpc", "0 8")
|
net_case("networking/net_rpc", "0 8")
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
function build_tool(name: pointer, src: pointer) -> bool {
|
function build_tool(name: pointer, src: pointer) -> bool {
|
||||||
let ll = `build/{name}.ll`
|
let ll = `build/{name}.ll`
|
||||||
let out = `bin/{exe_name(name)}`
|
let out = `bin/{exe_name(name)}`
|
||||||
if not shq(`bin/{exe_name("ludicc")} {src} --emit-llvm -o {ll} 2>/dev/null`) { print(`build failed: {name} (compile)`); return false }
|
if not shq(`bin/{exe_name("ludicc")} --unsafe {src} --emit-llvm -o {ll} 2>/dev/null`) { print(`build failed: {name} (compile)`); return false }
|
||||||
# write to a temp then move, so a running bin/ludic can rebuild itself in place
|
# write to a temp then move, so a running bin/ludic can rebuild itself in place
|
||||||
# (Windows lets a running executable be renamed, never replaced, so the old one
|
# (Windows lets a running executable be renamed, never replaced, so the old one
|
||||||
# steps aside first)
|
# steps aside first)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ property VkMap { keys: pointers, vals: words, cap: int = 0, count: int = 0 }
|
||||||
function vkm_new() -> VkMap {
|
function vkm_new() -> VkMap {
|
||||||
let m = new VkMap
|
let m = new VkMap
|
||||||
m.cap = 16384
|
m.cap = 16384
|
||||||
m.keys = bytes(m.cap * 8)
|
m.keys = pointers(m.cap)
|
||||||
for i in 0 .. m.cap { m.keys[i] = null }
|
for i in 0 .. m.cap { m.keys[i] = null }
|
||||||
m.vals = words(m.cap)
|
m.vals = words(m.cap)
|
||||||
return m
|
return m
|
||||||
|
|
@ -45,7 +45,7 @@ function vkm_slot(m: VkMap, key: pointer) -> int {
|
||||||
function vkm_grow(m: VkMap) -> void {
|
function vkm_grow(m: VkMap) -> void {
|
||||||
let ok = m.keys; let ov = m.vals; let oc = m.cap
|
let ok = m.keys; let ov = m.vals; let oc = m.cap
|
||||||
m.cap = oc * 2
|
m.cap = oc * 2
|
||||||
m.keys = bytes(m.cap * 8)
|
m.keys = pointers(m.cap)
|
||||||
for i in 0 .. m.cap { m.keys[i] = null }
|
for i in 0 .. m.cap { m.keys[i] = null }
|
||||||
m.vals = words(m.cap)
|
m.vals = words(m.cap)
|
||||||
m.count = 0
|
m.count = 0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue