Merge branch 'lang/foundations' into lang/nav
This commit is contained in:
commit
d9b20f6eec
76 changed files with 29862 additions and 26177 deletions
9
changes/ecs-growable-stores.md
Normal file
9
changes/ecs-growable-stores.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
bump: minor
|
||||
type: feature
|
||||
**The built-in ECS grows.** Every component was a fixed array of 1024 slots, so a game past 1024
|
||||
entities could not have them (and until the last release silently corrupted memory trying). The
|
||||
per-entity stores are heap blocks now, doubled by `L_grow` as entities outgrow them, the new slots
|
||||
zero: 100 000 entities spawn and query. `Prop.has` bounds against the live capacity and
|
||||
`Pool.capacity` answers it. A snapshot (`save`/`load`, `world_save`/`world_load`) records its slot
|
||||
count first and a load grows to it before reading the stores back, so a snapshot's size follows the
|
||||
world's instead of a fixed 1024. A mod's registered components grow with the rest.
|
||||
15
changes/vulkan-leaks.md
Normal file
15
changes/vulkan-leaks.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
bump: patch
|
||||
type: fix
|
||||
**The Vulkan renderer no longer leaks memory as it loads and draws.** Every texture upload
|
||||
malloc'd a CPU copy of its pixels and never freed it (236 MB over the valley's boot, and again on
|
||||
every model loaded later); the pixels are now converted straight into the mapped staging buffer.
|
||||
The per-level and per-layer views made for mipmapped and layered targets outlived their texture,
|
||||
and on MoltenVK a view keeps its Metal texture alive, so every rebuild of the screen-sized targets
|
||||
left the last set in memory; a released texture now takes its views with it, and the cache is
|
||||
keyed by numbers rather than a string built on every call. The Vulkan structs filled for a draw, a
|
||||
pass, a barrier, a descriptor set or a buffer (about sixty call sites, several per draw) come from
|
||||
a reused 1 MB scratch ring instead of a malloc each; the descriptor-set cache and the retired
|
||||
buffer list are emptied and refilled rather than replaced; and on macOS the frame drains an
|
||||
autorelease pool (`Vk.frame_pool()`), which MoltenVK and the layer rely on. `R3D_VK_PROF` now
|
||||
also prints, every 120 frames, the Vulkan objects made and destroyed by kind and the length of
|
||||
every cache, so a kind made each frame and never destroyed shows at once.
|
||||
9
changes/worker-reads-states.md
Normal file
9
changes/worker-reads-states.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
bump: minor
|
||||
type: feature
|
||||
**A `Job.parallel_for` worker may read a state but not change one.** Every pool thread runs the
|
||||
worker at once, and the runtime hands each call the same state, so a worker that took a state as
|
||||
`mut` raced on it with nothing to say so. The compiler now refuses it (`fn bump: a worker runs on
|
||||
every core at once, so it may read CountState but not change it`): a result goes into `ctx`, a
|
||||
shared count through a `Sync` handle kept in the state. A function's signature already says what it
|
||||
touches, so this is the whole check - the same one a parallel scheduler needs to run two systems at
|
||||
once. `examples/library/threads.ludic` keeps its total in the words it hands the worker.
|
||||
|
|
@ -19,7 +19,9 @@ started on first use) and the calling thread, so each index runs exactly once, i
|
|||
on (`words`, `bytes` or a `pointer`). A worker computes on what it was handed and writes only its own
|
||||
index's results: it must not `spawn`, `despawn`, `push` onto a list another thread can see, or use
|
||||
Http or Audio. `spawn` and `despawn` on a worker stop the program with a located message. Shared
|
||||
counters go through `Sync.add`, shared totals behind a `Sync.mutex`.
|
||||
counters go through `Sync.add`, shared totals behind a `Sync.mutex`. A worker may take states, but
|
||||
only to read them: every thread runs it at once, so a worker that takes one as `mut` is refused at
|
||||
compile time (a result goes into `ctx`, a count through a `Sync` handle kept in the state).
|
||||
|
||||
```ludic
|
||||
program Squares {
|
||||
|
|
|
|||
|
|
@ -9,17 +9,18 @@ program Threads {
|
|||
state ThreadsState {
|
||||
calls: int = 0 # an atomic handle, made on the main thread before any work
|
||||
lock: int = 0 # a mutex handle
|
||||
total: long = 0 # guarded by `lock`
|
||||
}
|
||||
|
||||
# out[i] = i * i: every index written exactly once, by whichever thread claimed it
|
||||
function square(i: int, out: words) -> void { out[i] = i * i }
|
||||
|
||||
# count the call atomically, and add i to a shared total under the mutex
|
||||
function tally(threads_st: mut ThreadsState, i: int, ctx: pointer) -> void {
|
||||
# count the call atomically, and add i to a shared total under the mutex. A worker may read a
|
||||
# state (the handles in it) but never change one - every thread runs it at once - so the total
|
||||
# lives in what the worker is handed
|
||||
function tally(threads_st: ThreadsState, i: int, total: words) -> void {
|
||||
Sync.add(threads_st.calls, 1)
|
||||
Sync.lock(threads_st.lock)
|
||||
threads_st.total = threads_st.total + i
|
||||
total[0] = total[0] + i
|
||||
Sync.unlock(threads_st.lock)
|
||||
}
|
||||
|
||||
|
|
@ -43,10 +44,11 @@ program Threads {
|
|||
|
||||
threads_st.calls = Sync.atomic()
|
||||
threads_st.lock = Sync.mutex()
|
||||
Job.parallel_for(N, fn tally, null)
|
||||
let total = words(1)
|
||||
Job.parallel_for(N, fn tally, total)
|
||||
if Sync.get(threads_st.calls) == N { print(4) }
|
||||
let want: long = N * (N - 1) / 2
|
||||
if threads_st.total == want { print(5) }
|
||||
let want = N * (N - 1) / 2
|
||||
if total[0] == want { print(5) }
|
||||
|
||||
let ran = words(N)
|
||||
Job.parallel_for(N, fn placed, ran)
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ program Smooth {
|
|||
handler Draw(render3d_st: mut Render3dState, smooth_st: mut SmoothState) phase Render {
|
||||
r3d_frame(render3d_st, float(Time.elapsed()))
|
||||
smooth_st.frame += 1
|
||||
if smooth_st.frame == smooth_st.shot_at { Gl.screenshot(path: "build/smooth.ppm") }
|
||||
Gl.swap()
|
||||
if smooth_st.frame == smooth_st.shot_at { r3d_screenshot(render3d_st, "build/smooth.ppm") }
|
||||
r3d_present(render3d_st)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,60 @@ import "ludic.base"
|
|||
| `def Systems key { ... }`, `SYS_<KEY>`, `SYS_COUNT` | a system declared from any module; the list starts from these |
|
||||
| `core_init_all()`, `core_reset_all()`, `core_tick_all(t)`, `core_save_all() -> Val`, `core_load_all(v)` | the runner: in the order added, tick phase by phase |
|
||||
|
||||
## Entities: `Table<T>` and its indexes
|
||||
|
||||
A mechanic that keeps many of something (Things on the ground, animals, drops) keeps them as rows
|
||||
of a `Table<T>` in its state rather than as a list it scans. The table is data-oriented and
|
||||
allocates nothing per query:
|
||||
|
||||
- **Rows are dense.** Hot data is columns - `tb.f[c]` (floats) and `tb.i[c]` (ints), one value per
|
||||
row - and `tb.rec[row]` is a record `T` for everything cold. A removal moves the last row into
|
||||
the gap (`tb_remove`), so a sweep over `0 .. tb_len(tb)` touches contiguous memory.
|
||||
- **Handles go stale.** `tb_add` returns a handle: a 22-bit slot and a 9-bit generation. `tb_row(tb,
|
||||
h)` is -1 once the entity is removed, even after the slot is reused. Keep handles, never rows.
|
||||
- **Indexes are kept by the setters.** `tb_set_f`, `tb_set_xz`, `tb_set_i` (or writing `tb.f` /
|
||||
`tb.i` directly and then `tb_refile`) stamp the row's change tick and refile it in every index
|
||||
that reads that column, in O(1). An index is never a frame behind.
|
||||
- `tb_grid(tb, cx, cz, gate, cell)` - a spatial hash over two float columns, gated by an int
|
||||
column (a row is filed while it is non-zero: "active"). `tb_nearest(tb, g, x, z, maxr, mc, mv)`
|
||||
searches rings outward and stops at the first ring that cannot hold anything nearer;
|
||||
`tb_within(..., out)` fills a caller's `words`. Buckets double as the rows grow.
|
||||
- `tb_index(tb, col, gate)` - a cached query: the rows holding each value of an int column (a
|
||||
kind). `ix_rows(ix, v)`, `ix_count(ix, v)`, `ix_first(ix, v)`.
|
||||
- `tb_nearest_of` / `tb_within_of` plan between the two: a rare kind is scanned from its own
|
||||
list, a common one searched by rings.
|
||||
- **Change detection.** `tb_advance(tb)` moves the table to its next tick; `tb_changed_since(tb,
|
||||
tick, out)` and `tb_added_since` name the rows written since - what a save or a message needs to
|
||||
send a delta instead of everything.
|
||||
- **Observers.** `tb_on_add(tb, fn f)` / `tb_on_remove(tb, fn f)`: `f(handle)` (its states supplied,
|
||||
like a system's) is told each handle as its row is made and before it goes.
|
||||
- **Groups and chunks.** `tb_remove_all(tb, ix, v)` removes every row an index files under one
|
||||
value - a kind, or a chunk of the world a stream lets go (`chunk_of(x, z, size)` packs a chunk's
|
||||
cell into an int). An index keeps a small value (below 1024) in its own slot and gives a larger,
|
||||
sparse one (a chunk's packed cell) a slot through a map, so each value costs one list.
|
||||
- **Change detection is per 64-row block too**: `tb_changed_since` skips a block nobody wrote
|
||||
since. A row moved into a removal's gap keeps its own tick; the removal is the observer's.
|
||||
- **Parallel work reads, never writes, a state.** `Job.parallel_for(n, fn work, ctx)` over a
|
||||
table's columns runs on every core; the compiler refuses a worker that takes a state as `mut`
|
||||
(a result goes into `ctx`, a shared count through a `Sync` handle kept in the state).
|
||||
- **Stable ids.** `IntMap` (`imap_new`, `imap_put`, `imap_get(m, k, none)`, `imap_del`) maps an
|
||||
id kept in a save or a message to a handle without a scan.
|
||||
|
||||
Ludic frees nothing a safe program allocates, so a query that built a list per call leaked every
|
||||
frame. Every question here writes into a buffer the caller keeps. What each costs, against a
|
||||
`[]Record` list scanned (`M4 Pro`, one thread):
|
||||
|
||||
| | 10 000 | 100 000 | 1 000 000 |
|
||||
| --- | --- | --- | --- |
|
||||
| nearest, any | 0.37 us (list 30) | 1.5 us (list 307) | 7.2 us (list 3075) |
|
||||
| nearest of a kind (1 in 40) | 1.4 us (list 5.8) | 3.3 us (list 56) | 12 us (list 864) |
|
||||
| by id | 0.1 us (list 1.7) | 0.09 us (list 17) | 1.5 us (list 324) |
|
||||
| within 30 m | 0.9 us | 1.5 us | 6.9 us |
|
||||
| a move, refiled | 11 ns | 14 ns | 44 ns |
|
||||
|
||||
`tests/ecs_fuzz_test.ludic` holds the grid and the kind index against a scan through thousands of
|
||||
random adds, removes, moves, kind changes and gate flips.
|
||||
|
||||
## A toy mechanic
|
||||
|
||||
```ludic
|
||||
|
|
|
|||
79
packages/ludic.base/ecs_grid.ludic
Normal file
79
packages/ludic.base/ecs_grid.ludic
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# ludic.base/ecs_grid.ludic - a spatial hash over two float columns: square cells hashed into
|
||||
# buckets, each a doubly linked list of rows, so filing, moving and unfiling a row is O(1)
|
||||
export property Grid {
|
||||
cx: int = 0
|
||||
cz: int = 1
|
||||
gate: int = -1 # an int column: a row is filed only while it is non-zero (-1: always)
|
||||
inv: float = 0.0625 # 1 / the cell's side
|
||||
mask: int = 255 # buckets - 1, a power of two less one
|
||||
head: words = null # bucket -> row + 1, 0 empty
|
||||
next: words = null # row -> the next row in its bucket + 1
|
||||
prev: words = null # row -> the previous row + 1
|
||||
key: words = null # row -> its packed cell, -1 while not filed
|
||||
count: int = 0
|
||||
}
|
||||
|
||||
function grid_cell(g: Grid, v: float) -> int { return int(Math.floor(v * g.inv)) }
|
||||
function grid_key(ix: int, iz: int) -> int { return ((ix & 32767) << 15) | (iz & 32767) }
|
||||
function grid_bucket(g: Grid, ix: int, iz: int) -> int { return ((ix * 73856093) ^ (iz * 19349663)) & g.mask }
|
||||
|
||||
function grid_make(cx: int, cz: int, gate: int, cell: float, rows: int) -> Grid {
|
||||
let g = new Grid
|
||||
g.cx = cx
|
||||
g.cz = cz
|
||||
g.gate = gate
|
||||
g.inv = 1.0 / cell
|
||||
g.head = words(g.mask + 1)
|
||||
g.next = words(0)
|
||||
g.prev = words(0)
|
||||
g.key = words(0)
|
||||
for r in 0 .. rows { grid_grow(g) }
|
||||
return g
|
||||
}
|
||||
|
||||
# index a table spatially by columns cx and cz, in cells `cell` wide; its rows are filed at once
|
||||
export function tb_grid<T>(tb: Table<T>, cx: int, cz: int, gate: int, cell: float) -> Grid {
|
||||
let g = grid_make(cx, cz, gate, cell, tb.n)
|
||||
push(tb.grids, g)
|
||||
for r in 0 .. tb.n { tb_grid_refile(tb, g, r) }
|
||||
return g
|
||||
}
|
||||
|
||||
export function grid_count(g: Grid) -> int { return g.count }
|
||||
|
||||
function grid_grow(g: Grid) -> void {
|
||||
push(g.next, 0)
|
||||
push(g.prev, 0)
|
||||
push(g.key, -1)
|
||||
}
|
||||
|
||||
function grid_shrink(g: Grid) -> void {
|
||||
List.pop(g.next)
|
||||
List.pop(g.prev)
|
||||
List.pop(g.key)
|
||||
}
|
||||
|
||||
function grid_link(g: Grid, r: int, k: int, b: int) -> void {
|
||||
let h = g.head[b]
|
||||
g.next[r] = h
|
||||
g.prev[r] = 0
|
||||
if h > 0 { g.prev[h - 1] = r + 1 }
|
||||
g.head[b] = r + 1
|
||||
g.key[r] = k
|
||||
g.count += 1
|
||||
}
|
||||
|
||||
function grid_unlink(g: Grid, r: int) -> void {
|
||||
let k = g.key[r]
|
||||
if k < 0 { return }
|
||||
let p = g.prev[r]
|
||||
let n = g.next[r]
|
||||
if p > 0 { g.next[p - 1] = n } else { g.head[grid_bucket(g, grid_key_x(k), grid_key_z(k))] = n }
|
||||
if n > 0 { g.prev[n - 1] = p }
|
||||
g.key[r] = -1
|
||||
g.count -= 1
|
||||
}
|
||||
|
||||
# a packed key back to its cell (sign-extended from 15 bits)
|
||||
function grid_key_x(k: int) -> int { return ((k >> 15) & 32767) - (((k >> 29) & 1) * 32768) }
|
||||
function grid_key_z(k: int) -> int { return (k & 32767) - (((k >> 14) & 1) * 32768) }
|
||||
40
packages/ludic.base/ecs_grid_file.ludic
Normal file
40
packages/ludic.base/ecs_grid_file.ludic
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# ludic.base/ecs_grid_file.ludic - where a row is filed follows its columns: a row that has not
|
||||
# left its cell is not touched, a moved one is relinked, and the buckets double as the rows grow
|
||||
function grid_refile(g: Grid, r: int, x: float, z: float, on: bool) -> void {
|
||||
if not on {
|
||||
grid_unlink(g, r)
|
||||
return
|
||||
}
|
||||
let ix = grid_cell(g, x)
|
||||
let iz = grid_cell(g, z)
|
||||
let k = grid_key(ix, iz)
|
||||
if g.key[r] == k { return }
|
||||
grid_unlink(g, r)
|
||||
grid_link(g, r, k, grid_bucket(g, ix, iz))
|
||||
if g.count > (g.mask + 1) * 2 { grid_rehash(g) }
|
||||
}
|
||||
|
||||
# row `from` becomes row `to` (which is not filed): its neighbours and its bucket follow it
|
||||
function grid_moved(g: Grid, from: int, to: int) -> void {
|
||||
let k = g.key[from]
|
||||
g.key[to] = k
|
||||
if k < 0 { return }
|
||||
let p = g.prev[from]
|
||||
let n = g.next[from]
|
||||
g.prev[to] = p
|
||||
g.next[to] = n
|
||||
if p > 0 { g.next[p - 1] = to + 1 } else { g.head[grid_bucket(g, grid_key_x(k), grid_key_z(k))] = to + 1 }
|
||||
if n > 0 { g.prev[n - 1] = to + 1 }
|
||||
g.key[from] = -1
|
||||
}
|
||||
|
||||
# twice the buckets, every filed row linked again where its key now hashes
|
||||
function grid_rehash(g: Grid) -> void {
|
||||
g.mask = g.mask * 2 + 1
|
||||
g.head = words(g.mask + 1)
|
||||
g.count = 0
|
||||
for r in 0 .. len(g.key) {
|
||||
let k = g.key[r]
|
||||
if k >= 0 { grid_link(g, r, k, grid_bucket(g, grid_key_x(k), grid_key_z(k))) }
|
||||
}
|
||||
}
|
||||
93
packages/ludic.base/ecs_grid_query.ludic
Normal file
93
packages/ludic.base/ecs_grid_query.ludic
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# ludic.base/ecs_grid_query.ludic - the nearest row: rings of cells outward from the point, stopping
|
||||
# at the first ring that cannot hold anything nearer. A match is an int column equal to a value
|
||||
# (column -1: any), or a predicate on the row's record. A question: it writes nothing but locals.
|
||||
const GRID_RINGS: int = 24 # past this many rings a scan of the filed rows is cheaper
|
||||
const GRID_FAR: float = 1000000000000.0
|
||||
|
||||
# the nearest filed row to (x, z) within maxr (0: any distance) matching (mc, mv), or -1
|
||||
export function tb_nearest<T>(tb: Table<T>, g: Grid, x: float, z: float, maxr: float, mc: int, mv: int) -> int {
|
||||
if g.count == 0 { return -1 }
|
||||
var mcol: words = null
|
||||
if mc >= 0 { mcol = tb.i[mc] }
|
||||
let xs = tb.f[g.cx]
|
||||
let zs = tb.f[g.cz]
|
||||
var best = -1
|
||||
var bd = grid_limit(maxr)
|
||||
let cs = 1.0 / g.inv
|
||||
let ix0 = grid_cell(g, x)
|
||||
let iz0 = grid_cell(g, z)
|
||||
var ring = 0
|
||||
while ring <= GRID_RINGS {
|
||||
if ring > 0 and grid_ring_past(ring, cs, bd) { return best }
|
||||
let span = grid_ring_n(ring)
|
||||
for k in 0 .. span {
|
||||
best = grid_visit(g, xs, zs, mcol, mv, x, z, grid_ring_x(ix0, ring, k), grid_ring_z(iz0, ring, k), best, bd)
|
||||
if best >= 0 { bd = grid_d2(xs, zs, best, x, z) }
|
||||
}
|
||||
ring += 1
|
||||
}
|
||||
for row in 0 .. len(g.key) {
|
||||
if g.key[row] >= 0 and (mcol == null or mcol[row] == mv) and grid_d2(xs, zs, row, x, z) < bd {
|
||||
best = row
|
||||
bd = grid_d2(xs, zs, row, x, z)
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
function grid_limit(maxr: float) -> float {
|
||||
if maxr > 0.0 { return maxr * maxr }
|
||||
return GRID_FAR
|
||||
}
|
||||
# the nearest a point of ring `ring` can be is (ring - 1) cells: past the best, nothing there wins
|
||||
function grid_ring_past(ring: int, cs: float, bd: float) -> bool {
|
||||
let near = float(ring - 1) * cs
|
||||
return near * near > bd
|
||||
}
|
||||
function grid_d2(xs: floats, zs: floats, row: int, x: float, z: float) -> float {
|
||||
let dx = xs[row] - x
|
||||
let dz = zs[row] - z
|
||||
return dx * dx + dz * dz
|
||||
}
|
||||
|
||||
# a ring's cells in a fixed order: 1 for ring 0, else 8 * ring round the square's edge
|
||||
function grid_ring_n(ring: int) -> int {
|
||||
if ring == 0 { return 1 }
|
||||
return 8 * ring
|
||||
}
|
||||
function grid_ring_x(ix0: int, ring: int, k: int) -> int {
|
||||
if ring == 0 { return ix0 }
|
||||
let side = 2 * ring
|
||||
if k < side { return ix0 - ring + k }
|
||||
if k < 2 * side { return ix0 + ring }
|
||||
if k < 3 * side { return ix0 + ring - (k - 2 * side) }
|
||||
return ix0 - ring
|
||||
}
|
||||
function grid_ring_z(iz0: int, ring: int, k: int) -> int {
|
||||
if ring == 0 { return iz0 }
|
||||
let side = 2 * ring
|
||||
if k < side { return iz0 - ring }
|
||||
if k < 2 * side { return iz0 - ring + (k - side) }
|
||||
if k < 3 * side { return iz0 + ring }
|
||||
return iz0 + ring - (k - 3 * side)
|
||||
}
|
||||
|
||||
# one cell: its bucket's rows that are really in it and match; the better of them and `best`
|
||||
function grid_visit(g: Grid, xs: floats, zs: floats, mcol: words, mv: int, x: float, z: float, ix: int, iz: int, best: int, bd: float) -> int {
|
||||
let k = grid_key(ix, iz)
|
||||
var b = best
|
||||
var d = bd
|
||||
var r = g.head[grid_bucket(g, ix, iz)]
|
||||
while r > 0 {
|
||||
let row = r - 1
|
||||
if g.key[row] == k and (mcol == null or mcol[row] == mv) {
|
||||
let d2 = grid_d2(xs, zs, row, x, z)
|
||||
if d2 < d {
|
||||
d = d2
|
||||
b = row
|
||||
}
|
||||
}
|
||||
r = g.next[row]
|
||||
}
|
||||
return b
|
||||
}
|
||||
101
packages/ludic.base/ecs_grid_where.ludic
Normal file
101
packages/ludic.base/ecs_grid_where.ludic
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# ludic.base/ecs_grid_where.ludic - the grid's questions put to the rows' records: the nearest one a
|
||||
# predicate keeps, and every record within a radius into the caller's list. The predicate is called
|
||||
# with the record only; it may read anything, and must change nothing the grid indexes.
|
||||
export function tb_nearest_where<T>(tb: Table<T>, g: Grid, x: float, z: float, maxr: float, keep: fn(T) -> bool) -> int {
|
||||
if g.count == 0 { return -1 }
|
||||
let xs = tb.f[g.cx]
|
||||
let zs = tb.f[g.cz]
|
||||
var best = -1
|
||||
var bd = grid_limit(maxr)
|
||||
let cs = 1.0 / g.inv
|
||||
let ix0 = grid_cell(g, x)
|
||||
let iz0 = grid_cell(g, z)
|
||||
var ring = 0
|
||||
while ring <= GRID_RINGS {
|
||||
if ring > 0 and grid_ring_past(ring, cs, bd) { return best }
|
||||
for k in 0 .. grid_ring_n(ring) {
|
||||
best = grid_visit_where(tb, g, xs, zs, x, z, grid_ring_x(ix0, ring, k), grid_ring_z(iz0, ring, k), best, bd, keep)
|
||||
if best >= 0 { bd = grid_d2(xs, zs, best, x, z) }
|
||||
}
|
||||
ring += 1
|
||||
}
|
||||
for row in 0 .. len(g.key) {
|
||||
if g.key[row] >= 0 and grid_d2(xs, zs, row, x, z) < bd and keep(tb.rec[row]) {
|
||||
best = row
|
||||
bd = grid_d2(xs, zs, row, x, z)
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
function grid_visit_where<T>(tb: Table<T>, g: Grid, xs: floats, zs: floats, x: float, z: float, ix: int, iz: int, best: int, bd: float, keep: fn(T) -> bool) -> int {
|
||||
let k = grid_key(ix, iz)
|
||||
var b = best
|
||||
var d = bd
|
||||
var r = g.head[grid_bucket(g, ix, iz)]
|
||||
while r > 0 {
|
||||
let row = r - 1
|
||||
if g.key[row] == k {
|
||||
let d2 = grid_d2(xs, zs, row, x, z)
|
||||
if d2 < d and keep(tb.rec[row]) {
|
||||
d = d2
|
||||
b = row
|
||||
}
|
||||
}
|
||||
r = g.next[row]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
# every filed record within r of (x, z) matching (mc, mv), appended to `out` (cleared first) - the
|
||||
# caller's list, kept in the caller's state and reused, so a frame's question allocates nothing
|
||||
export function tb_within_recs<T>(tb: Table<T>, g: Grid, x: float, z: float, r: float, mc: int, mv: int, out: []T) -> int {
|
||||
List.clear(out)
|
||||
if g.count == 0 { return 0 }
|
||||
var mcol: words = null
|
||||
if mc >= 0 { mcol = tb.i[mc] }
|
||||
let xs = tb.f[g.cx]
|
||||
let zs = tb.f[g.cz]
|
||||
let ix0 = grid_cell(g, x - r)
|
||||
let ix1 = grid_cell(g, x + r)
|
||||
let iz0 = grid_cell(g, z - r)
|
||||
let iz1 = grid_cell(g, z + r)
|
||||
let cells = (ix1 - ix0 + 1) * (iz1 - iz0 + 1)
|
||||
if cells > g.count or cells < 0 {
|
||||
for row in 0 .. len(g.key) {
|
||||
if g.key[row] >= 0 and (mcol == null or mcol[row] == mv) and grid_d2(xs, zs, row, x, z) <= r * r { push(out, tb.rec[row]) }
|
||||
}
|
||||
return len(out)
|
||||
}
|
||||
for ix in ix0 .. ix1 + 1 {
|
||||
for iz in iz0 .. iz1 + 1 { grid_recs_cell(tb, g, xs, zs, mcol, mv, x, z, r * r, ix, iz, out) }
|
||||
}
|
||||
return len(out)
|
||||
}
|
||||
|
||||
function grid_recs_cell<T>(tb: Table<T>, g: Grid, xs: floats, zs: floats, mcol: words, mv: int, x: float, z: float, r2: float, ix: int, iz: int, out: []T) -> void {
|
||||
let k = grid_key(ix, iz)
|
||||
var r = g.head[grid_bucket(g, ix, iz)]
|
||||
while r > 0 {
|
||||
let row = r - 1
|
||||
if g.key[row] == k and (mcol == null or mcol[row] == mv) and grid_d2(xs, zs, row, x, z) <= r2 { push(out, tb.rec[row]) }
|
||||
r = g.next[row]
|
||||
}
|
||||
}
|
||||
|
||||
# every record of kind v (an index over column ix.col) within r, into `out`: a rare kind from its list
|
||||
export function tb_within_of_recs<T>(tb: Table<T>, g: Grid, ix: IntIndex, x: float, z: float, r: float, v: int, out: []T) -> int {
|
||||
let n = ix_count(ix, v)
|
||||
if n > PLAN_LIST_MAX { return tb_within_recs(tb, g, x, z, r, ix.col, v, out) }
|
||||
List.clear(out)
|
||||
if n == 0 { return 0 }
|
||||
let rows = ix.lists[v]
|
||||
let xs = tb.f[g.cx]
|
||||
let zs = tb.f[g.cz]
|
||||
for k in 0 .. n {
|
||||
let row = rows[k]
|
||||
if g.gate >= 0 and tb.i[g.gate][row] == 0 { continue }
|
||||
if grid_d2(xs, zs, row, x, z) <= r * r { push(out, tb.rec[row]) }
|
||||
}
|
||||
return len(out)
|
||||
}
|
||||
48
packages/ludic.base/ecs_grid_within.ludic
Normal file
48
packages/ludic.base/ecs_grid_within.ludic
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# ludic.base/ecs_grid_within.ludic - every filed row within r of (x, z) matching (mc, mv), into
|
||||
# `out` (cleared first), in no order; the count is returned. A radius wider than the rows are many
|
||||
# is answered by one scan of them instead of by cells.
|
||||
export function tb_within<T>(tb: Table<T>, g: Grid, x: float, z: float, r: float, mc: int, mv: int, out: words) -> int {
|
||||
List.clear(out)
|
||||
if g.count == 0 { return 0 }
|
||||
var mcol: words = null
|
||||
if mc >= 0 { mcol = tb.i[mc] }
|
||||
let xs = tb.f[g.cx]
|
||||
let zs = tb.f[g.cz]
|
||||
let ix0 = grid_cell(g, x - r)
|
||||
let ix1 = grid_cell(g, x + r)
|
||||
let iz0 = grid_cell(g, z - r)
|
||||
let iz1 = grid_cell(g, z + r)
|
||||
let cells = (ix1 - ix0 + 1) * (iz1 - iz0 + 1)
|
||||
if cells > g.count or cells < 0 {
|
||||
grid_within_scan(g, xs, zs, mcol, mv, x, z, r * r, out)
|
||||
return len(out)
|
||||
}
|
||||
for ix in ix0 .. ix1 + 1 {
|
||||
for iz in iz0 .. iz1 + 1 { grid_within_cell(g, xs, zs, mcol, mv, x, z, r * r, ix, iz, out) }
|
||||
}
|
||||
return len(out)
|
||||
}
|
||||
|
||||
function grid_within_cell(g: Grid, xs: floats, zs: floats, mcol: words, mv: int, x: float, z: float, r2: float, ix: int, iz: int, out: words) -> void {
|
||||
let k = grid_key(ix, iz)
|
||||
var r = g.head[grid_bucket(g, ix, iz)]
|
||||
while r > 0 {
|
||||
let row = r - 1
|
||||
if g.key[row] == k and (mcol == null or mcol[row] == mv) {
|
||||
let dx = xs[row] - x
|
||||
let dz = zs[row] - z
|
||||
if dx * dx + dz * dz <= r2 { push(out, row) }
|
||||
}
|
||||
r = g.next[row]
|
||||
}
|
||||
}
|
||||
|
||||
function grid_within_scan(g: Grid, xs: floats, zs: floats, mcol: words, mv: int, x: float, z: float, r2: float, out: words) -> void {
|
||||
for row in 0 .. len(g.key) {
|
||||
if g.key[row] >= 0 and (mcol == null or mcol[row] == mv) {
|
||||
let dx = xs[row] - x
|
||||
let dz = zs[row] - z
|
||||
if dx * dx + dz * dz <= r2 { push(out, row) }
|
||||
}
|
||||
}
|
||||
}
|
||||
20
packages/ludic.base/ecs_group.ludic
Normal file
20
packages/ludic.base/ecs_group.ludic
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# ludic.base/ecs_group.ludic - a group is every row an index files under one value: a kind, or a
|
||||
# chunk of the world, loaded and let go together. A chunk's number packs its two cell coordinates.
|
||||
export function chunk_of(x: float, z: float, size: float) -> int {
|
||||
let cx = int(Math.floor(x / size))
|
||||
let cz = int(Math.floor(z / size))
|
||||
return ((cx & 32767) << 15) | (cz & 32767)
|
||||
}
|
||||
|
||||
# every row filed under v in ix removed (the observers told of each); how many went. The index's
|
||||
# own list shrinks as each goes, so it is read from its end until it is empty.
|
||||
export function tb_remove_all<T>(tb: Table<T>, ix: IntIndex, v: int) -> int {
|
||||
var n = 0
|
||||
let rows = ix_rows(ix, v)
|
||||
while len(rows) > 0 {
|
||||
let h = tb.ent[rows[len(rows) - 1]]
|
||||
if not tb_remove(tb, h) { return n }
|
||||
n += 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
18
packages/ludic.base/ecs_hooks.ludic
Normal file
18
packages/ludic.base/ecs_hooks.ludic
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# ludic.base/ecs_hooks.ludic - observers on a table: told each handle as its row is made, and before
|
||||
# it is removed, so what depends on a table (a drawing, a message) follows it without a scan
|
||||
# an observer: a function handed an entity's handle (its states are supplied, like a system's)
|
||||
export property TbHook {
|
||||
run: fn(int) -> void = null
|
||||
}
|
||||
|
||||
# f is told every handle added from here on, and every handle about to be removed
|
||||
export function tb_on_add<T>(tb: Table<T>, f: fn(int) -> void) -> void {
|
||||
let hk = new TbHook
|
||||
hk.run = f
|
||||
push(tb.on_add, hk)
|
||||
}
|
||||
export function tb_on_remove<T>(tb: Table<T>, f: fn(int) -> void) -> void {
|
||||
let hk = new TbHook
|
||||
hk.run = f
|
||||
push(tb.on_remove, hk)
|
||||
}
|
||||
83
packages/ludic.base/ecs_index.ludic
Normal file
83
packages/ludic.base/ecs_index.ludic
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# ludic.base/ecs_index.ludic - a cached query: for each value of an int column (a kind), the rows
|
||||
# holding it, kept current by the table's setters, so "every fire" is a list and "how many" a length
|
||||
export property IntIndex {
|
||||
col: int = 0
|
||||
gate: int = -1 # an int column: a row is filed only while it is non-zero (-1: always)
|
||||
lists: []words = null # value -> its rows
|
||||
pos: words = null # row -> where it stands in its value's list, -1 not filed
|
||||
val: words = null # row -> the slot of the value it is filed under
|
||||
empty: words = null # what a value with no rows answers
|
||||
sparse: IntMap = null # a value past IX_DENSE (a chunk's packed cell) to its list's slot
|
||||
}
|
||||
export function tb_index<T>(tb: Table<T>, col: int, gate: int) -> IntIndex {
|
||||
let ix = new IntIndex
|
||||
ix.col = col
|
||||
ix.gate = gate
|
||||
ix.lists = new []words
|
||||
ix.pos = words(0)
|
||||
ix.val = words(0)
|
||||
ix.empty = words(0)
|
||||
ix.sparse = imap_new()
|
||||
for r in 0 .. tb.n { ix_grow(ix) }
|
||||
push(tb.idx, ix)
|
||||
for r in 0 .. tb.n { tb_ix_refile(tb, ix, r) }
|
||||
return ix
|
||||
}
|
||||
|
||||
# the rows filed under v - the index's own list: read it, never push to it
|
||||
export function ix_rows(ix: IntIndex, v: int) -> words {
|
||||
let s = ix_slot(ix, v, false)
|
||||
if s < 0 { return ix.empty }
|
||||
return ix.lists[s]
|
||||
}
|
||||
export function ix_count(ix: IntIndex, v: int) -> int { return len(ix_rows(ix, v)) }
|
||||
# the first row filed under v, or -1
|
||||
export function ix_first(ix: IntIndex, v: int) -> int {
|
||||
let rows = ix_rows(ix, v)
|
||||
if len(rows) == 0 { return -1 }
|
||||
return rows[0]
|
||||
}
|
||||
|
||||
function ix_grow(ix: IntIndex) -> void {
|
||||
push(ix.pos, -1)
|
||||
push(ix.val, -1)
|
||||
}
|
||||
|
||||
function ix_shrink(ix: IntIndex) -> void {
|
||||
List.pop(ix.pos)
|
||||
List.pop(ix.val)
|
||||
}
|
||||
|
||||
function ix_file(ix: IntIndex, r: int, v: int) -> void {
|
||||
let s = ix_slot(ix, v, true)
|
||||
if ix.pos[r] >= 0 and ix.val[r] == s { return }
|
||||
ix_unfile(ix, r)
|
||||
if s < 0 { return }
|
||||
ix.pos[r] = len(ix.lists[s])
|
||||
ix.val[r] = s
|
||||
push(ix.lists[s], r)
|
||||
}
|
||||
|
||||
# out of its list: the list's last row takes its place
|
||||
function ix_unfile(ix: IntIndex, r: int) -> void {
|
||||
let p = ix.pos[r]
|
||||
if p < 0 { return }
|
||||
let l = ix.lists[ix.val[r]]
|
||||
let last = List.pop(l)
|
||||
if last != r {
|
||||
l[p] = last
|
||||
ix.pos[last] = p
|
||||
}
|
||||
ix.pos[r] = -1
|
||||
ix.val[r] = -1
|
||||
}
|
||||
|
||||
# row `from` becomes row `to` (which is not filed)
|
||||
function ix_moved(ix: IntIndex, from: int, to: int) -> void {
|
||||
let p = ix.pos[from]
|
||||
ix.pos[to] = p
|
||||
ix.val[to] = ix.val[from]
|
||||
if p >= 0 { ix.lists[ix.val[from]][p] = to }
|
||||
ix.pos[from] = -1
|
||||
ix.val[from] = -1
|
||||
}
|
||||
24
packages/ludic.base/ecs_index_slot.ludic
Normal file
24
packages/ludic.base/ecs_index_slot.ludic
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# ludic.base/ecs_index_slot.ludic - where a value's list lives: a small value (a kind) is its own
|
||||
# slot; a large one (a chunk's packed cell) is given the next slot through a map, so a sparse
|
||||
# value costs one list and not every list up to it
|
||||
const IX_DENSE: int = 1024 # a value below this is its own slot; above, one is given it
|
||||
|
||||
# the slot value v's list is in, -1 when it has none (and `make` did not ask for one)
|
||||
function ix_slot(ix: IntIndex, v: int, make: bool) -> int {
|
||||
if v < 0 { return -1 }
|
||||
if v < IX_DENSE {
|
||||
if v >= len(ix.lists) {
|
||||
if not make { return -1 }
|
||||
while len(ix.lists) <= v { push(ix.lists, words(0)) }
|
||||
}
|
||||
return v
|
||||
}
|
||||
var s = imap_get(ix.sparse, v, -1)
|
||||
if s < 0 and make {
|
||||
while len(ix.lists) < IX_DENSE { push(ix.lists, words(0)) }
|
||||
s = len(ix.lists)
|
||||
push(ix.lists, words(0))
|
||||
imap_put(ix.sparse, v, s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
79
packages/ludic.base/ecs_map.ludic
Normal file
79
packages/ludic.base/ecs_map.ludic
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# ludic.base/ecs_map.ludic - IntMap: a key (0 or more) to a value, open addressing with linear
|
||||
# probing; what turns a stable id kept in a save or a message into a handle without a scan
|
||||
export property IntMap {
|
||||
keys: words = null # -1 empty, -2 a key that was deleted
|
||||
vals: words = null
|
||||
mask: int = 63
|
||||
n: int = 0
|
||||
used: int = 0 # live and deleted slots: what makes probing long
|
||||
}
|
||||
|
||||
export function imap_new() -> IntMap {
|
||||
let m = new IntMap
|
||||
imap_alloc(m, 64)
|
||||
return m
|
||||
}
|
||||
|
||||
function imap_alloc(m: IntMap, cap: int) -> void {
|
||||
m.keys = words(cap)
|
||||
m.vals = words(cap)
|
||||
for i in 0 .. cap { m.keys[i] = -1 }
|
||||
m.mask = cap - 1
|
||||
m.n = 0
|
||||
m.used = 0
|
||||
}
|
||||
|
||||
function imap_slot(m: IntMap, k: int) -> int { return ((k * -1640531535) >> 7) & m.mask }
|
||||
|
||||
# the value under k, or `none` when there is none
|
||||
export function imap_get(m: IntMap, k: int, none: int) -> int {
|
||||
var s = imap_slot(m, k)
|
||||
while m.keys[s] != -1 {
|
||||
if m.keys[s] == k { return m.vals[s] }
|
||||
s = (s + 1) & m.mask
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
export function imap_put(m: IntMap, k: int, v: int) -> void {
|
||||
if (m.used + 1) * 4 > (m.mask + 1) * 3 { imap_grow(m) }
|
||||
var s = imap_slot(m, k)
|
||||
var tomb = -1
|
||||
while m.keys[s] != -1 {
|
||||
if m.keys[s] == k {
|
||||
m.vals[s] = v
|
||||
return
|
||||
}
|
||||
if m.keys[s] == -2 and tomb < 0 { tomb = s }
|
||||
s = (s + 1) & m.mask
|
||||
}
|
||||
if tomb >= 0 { s = tomb } else { m.used += 1 }
|
||||
m.keys[s] = k
|
||||
m.vals[s] = v
|
||||
m.n += 1
|
||||
}
|
||||
|
||||
export function imap_del(m: IntMap, k: int) -> bool {
|
||||
var s = imap_slot(m, k)
|
||||
while m.keys[s] != -1 {
|
||||
if m.keys[s] == k {
|
||||
m.keys[s] = -2
|
||||
m.n -= 1
|
||||
return true
|
||||
}
|
||||
s = (s + 1) & m.mask
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function imap_len(m: IntMap) -> int { return m.n }
|
||||
export function imap_clear(m: IntMap) -> void { imap_alloc(m, m.mask + 1) }
|
||||
|
||||
function imap_grow(m: IntMap) -> void {
|
||||
let ok = m.keys
|
||||
let ov = m.vals
|
||||
var cap = m.mask + 1
|
||||
if m.n * 2 > cap { cap = cap * 2 }
|
||||
imap_alloc(m, cap)
|
||||
for i in 0 .. len(ok) { if ok[i] >= 0 { imap_put(m, ok[i], ov[i]) } }
|
||||
}
|
||||
46
packages/ludic.base/ecs_plan.ludic
Normal file
46
packages/ludic.base/ecs_plan.ludic
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# ludic.base/ecs_plan.ludic - a query that has both a grid and a kind index picks the cheaper way:
|
||||
# a rare kind's own list scanned, a common one found by rings with the kind as the match
|
||||
const PLAN_LIST_MAX: int = 192 # a kind with at most this many rows is scanned from its list
|
||||
|
||||
export function tb_nearest_of<T>(tb: Table<T>, g: Grid, ix: IntIndex, x: float, z: float, maxr: float, v: int) -> int {
|
||||
let n = ix_count(ix, v)
|
||||
if n == 0 { return -1 }
|
||||
if n > PLAN_LIST_MAX { return tb_nearest(tb, g, x, z, maxr, ix.col, v) }
|
||||
let rows = ix.lists[v]
|
||||
let xs = tb.f[g.cx]
|
||||
let zs = tb.f[g.cz]
|
||||
var best = -1
|
||||
var bd = 1000000000000.0
|
||||
if maxr > 0.0 { bd = maxr * maxr }
|
||||
for k in 0 .. n {
|
||||
let r = rows[k]
|
||||
if g.gate >= 0 and tb.i[g.gate][r] == 0 { continue }
|
||||
let dx = xs[r] - x
|
||||
let dz = zs[r] - z
|
||||
let d2 = dx * dx + dz * dz
|
||||
if d2 < bd or (d2 == bd and best < 0) {
|
||||
bd = d2
|
||||
best = r
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
# within r of (x, z), of kind v, into `out`
|
||||
export function tb_within_of<T>(tb: Table<T>, g: Grid, ix: IntIndex, x: float, z: float, r: float, v: int, out: words) -> int {
|
||||
let n = ix_count(ix, v)
|
||||
if n > PLAN_LIST_MAX { return tb_within(tb, g, x, z, r, ix.col, v, out) }
|
||||
List.clear(out)
|
||||
if n == 0 { return 0 }
|
||||
let rows = ix.lists[v]
|
||||
let xs = tb.f[g.cx]
|
||||
let zs = tb.f[g.cz]
|
||||
for k in 0 .. n {
|
||||
let row = rows[k]
|
||||
if g.gate >= 0 and tb.i[g.gate][row] == 0 { continue }
|
||||
let dx = xs[row] - x
|
||||
let dz = zs[row] - z
|
||||
if dx * dx + dz * dz <= r * r { push(out, row) }
|
||||
}
|
||||
return len(out)
|
||||
}
|
||||
96
packages/ludic.base/ecs_table.ludic
Normal file
96
packages/ludic.base/ecs_table.ludic
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# ludic.base/ecs_table.ludic - a Table<T>: entities as dense rows of hot columns (floats, words)
|
||||
# and one record each for what is cold. A handle is a slot and a generation, so a stale one misses.
|
||||
export const ECS_SLOT: int = 4194303 # the low 22 bits of a handle: its slot
|
||||
export const ECS_GEN: int = 511 # the next 9: how many times the slot was reused
|
||||
export const ECS_NONE: int = -1
|
||||
|
||||
export property Table<T> {
|
||||
n: int = 0
|
||||
rec: []T = null
|
||||
ent: words = null # row -> handle
|
||||
f: []floats = null # hot float columns, a value per row each
|
||||
i: []words = null # hot int columns
|
||||
added: words = null # row -> the tick it was added at
|
||||
changed: words = null # row -> the tick a setter last wrote it at
|
||||
blk: words = null # 64-row block -> the newest tick any row in it was added or written at
|
||||
tick: int = 1
|
||||
slot_gen: words = null # slot -> generation
|
||||
slot_row: words = null # slot -> row, -1 while free
|
||||
free: words = null
|
||||
grids: []Grid = null
|
||||
idx: []IntIndex = null
|
||||
on_add: []TbHook = null # observers: told a handle as its row is made, and before it goes
|
||||
on_remove: []TbHook = null
|
||||
}
|
||||
|
||||
|
||||
# a table of nf float and ni int columns, every value 0 in a new row
|
||||
export function table_new<T>(nf: int, ni: int) -> Table<T> {
|
||||
let tb = new Table<T>
|
||||
tb.rec = new []T
|
||||
tb.ent = words(0)
|
||||
tb.f = new []floats
|
||||
tb.i = new []words
|
||||
for c in 0 .. nf { push(tb.f, floats(0)) }
|
||||
for c in 0 .. ni { push(tb.i, words(0)) }
|
||||
tb.added = words(0)
|
||||
tb.changed = words(0)
|
||||
tb.blk = words(0)
|
||||
tb.slot_gen = words(0)
|
||||
tb.slot_row = words(0)
|
||||
tb.free = words(0)
|
||||
tb.grids = new []Grid
|
||||
tb.idx = new []IntIndex
|
||||
tb.on_add = new []TbHook
|
||||
tb.on_remove = new []TbHook
|
||||
return tb
|
||||
}
|
||||
|
||||
export function tb_len<T>(tb: Table<T>) -> int { return tb.n }
|
||||
export function tb_rec<T>(tb: Table<T>, row: int) -> T { return tb.rec[row] }
|
||||
export function tb_handle<T>(tb: Table<T>, row: int) -> int { return tb.ent[row] }
|
||||
export function tb_tick<T>(tb: Table<T>) -> int { return tb.tick }
|
||||
# the next tick: what is written from here on is newer than anything before
|
||||
export function tb_advance<T>(tb: Table<T>) -> void { tb.tick += 1 }
|
||||
|
||||
# the row a handle names, or -1 when it was removed (or the slot has been reused since)
|
||||
export function tb_row<T>(tb: Table<T>, h: int) -> int {
|
||||
if h < 0 { return -1 }
|
||||
let s = h & ECS_SLOT
|
||||
if s >= len(tb.slot_gen) or tb.slot_gen[s] != ((h >> 22) & ECS_GEN) { return -1 }
|
||||
return tb.slot_row[s]
|
||||
}
|
||||
|
||||
export function tb_alive<T>(tb: Table<T>, h: int) -> bool { return tb_row(tb, h) >= 0 }
|
||||
|
||||
# a new row holding `r`, every column 0 and filed in no index until a setter says where it is
|
||||
export function tb_add<T>(tb: Table<T>, r: T) -> int {
|
||||
var s = 0
|
||||
if len(tb.free) > 0 { s = List.pop(tb.free) } else {
|
||||
s = len(tb.slot_gen)
|
||||
push(tb.slot_gen, 0)
|
||||
push(tb.slot_row, -1)
|
||||
}
|
||||
tb.slot_row[s] = tb.n
|
||||
let h = s | (tb.slot_gen[s] << 22)
|
||||
push(tb.rec, r)
|
||||
push(tb.ent, h)
|
||||
push(tb.added, tb.tick)
|
||||
push(tb.changed, tb.tick)
|
||||
for c in 0 .. len(tb.f) { push(tb.f[c], 0.0) }
|
||||
for c in 0 .. len(tb.i) { push(tb.i[c], 0) }
|
||||
tb.n += 1
|
||||
for g in 0 .. len(tb.grids) { grid_grow(tb.grids[g]) }
|
||||
for k in 0 .. len(tb.idx) { ix_grow(tb.idx[k]) }
|
||||
tb_stamp(tb, tb.n - 1)
|
||||
for k in 0 .. len(tb.on_add) { tb.on_add[k].run(h) }
|
||||
return h
|
||||
}
|
||||
|
||||
# the row's tick, and its block's: what change detection reads
|
||||
function tb_stamp<T>(tb: Table<T>, row: int) -> void {
|
||||
tb.changed[row] = tb.tick
|
||||
let b = row >> 6
|
||||
while len(tb.blk) <= b { push(tb.blk, 0) }
|
||||
tb.blk[b] = tb.tick
|
||||
}
|
||||
49
packages/ludic.base/ecs_table_remove.ludic
Normal file
49
packages/ludic.base/ecs_table_remove.ludic
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# ludic.base/ecs_table_remove.ludic - removing a row moves the last one into its place, so the
|
||||
# rows stay dense; every index is told, and the slot's generation moves on
|
||||
export function tb_remove<T>(tb: Table<T>, h: int) -> bool {
|
||||
if tb_row(tb, h) < 0 { return false }
|
||||
for k in 0 .. len(tb.on_remove) { tb.on_remove[k].run(h) }
|
||||
let r = tb_row(tb, h)
|
||||
if r < 0 { return false }
|
||||
for g in 0 .. len(tb.grids) { grid_unlink(tb.grids[g], r) }
|
||||
for k in 0 .. len(tb.idx) { ix_unfile(tb.idx[k], r) }
|
||||
let last = tb.n - 1
|
||||
if r != last { tb_move(tb, last, r) }
|
||||
tb_pop(tb)
|
||||
let s = h & ECS_SLOT
|
||||
tb.slot_row[s] = -1
|
||||
tb.slot_gen[s] = (tb.slot_gen[s] + 1) & ECS_GEN
|
||||
push(tb.free, s)
|
||||
return true
|
||||
}
|
||||
|
||||
function tb_move<T>(tb: Table<T>, from: int, to: int) -> void {
|
||||
tb.rec[to] = tb.rec[from]
|
||||
tb.ent[to] = tb.ent[from]
|
||||
tb.added[to] = tb.added[from]
|
||||
tb.changed[to] = tb.changed[from]
|
||||
for c in 0 .. len(tb.f) { tb.f[c][to] = tb.f[c][from] }
|
||||
for c in 0 .. len(tb.i) { tb.i[c][to] = tb.i[c][from] }
|
||||
tb.slot_row[tb.ent[to] & ECS_SLOT] = to
|
||||
let b = to >> 6
|
||||
if tb.blk[b] < tb.changed[to] { tb.blk[b] = tb.changed[to] }
|
||||
for g in 0 .. len(tb.grids) { grid_moved(tb.grids[g], from, to) }
|
||||
for k in 0 .. len(tb.idx) { ix_moved(tb.idx[k], from, to) }
|
||||
}
|
||||
|
||||
function tb_pop<T>(tb: Table<T>) -> void {
|
||||
List.pop(tb.rec)
|
||||
List.pop(tb.ent)
|
||||
List.pop(tb.added)
|
||||
List.pop(tb.changed)
|
||||
for c in 0 .. len(tb.f) { List.pop(tb.f[c]) }
|
||||
for c in 0 .. len(tb.i) { List.pop(tb.i[c]) }
|
||||
for g in 0 .. len(tb.grids) { grid_shrink(tb.grids[g]) }
|
||||
for k in 0 .. len(tb.idx) { ix_shrink(tb.idx[k]) }
|
||||
tb.n -= 1
|
||||
}
|
||||
|
||||
# every row gone; the slots keep their generations, so an old handle still misses
|
||||
export function tb_clear<T>(tb: Table<T>) -> void {
|
||||
while tb.n > 0 { tb_remove(tb, tb.ent[tb.n - 1]) }
|
||||
}
|
||||
76
packages/ludic.base/ecs_table_set.ludic
Normal file
76
packages/ludic.base/ecs_table_set.ludic
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# ludic.base/ecs_table_set.ludic - the setters: a write stamps the row's tick and refiles it in
|
||||
# every index that reads that column, so an index is never a frame behind what it indexes
|
||||
export function tb_f<T>(tb: Table<T>, c: int, row: int) -> float { return tb.f[c][row] }
|
||||
export function tb_i<T>(tb: Table<T>, c: int, row: int) -> int { return tb.i[c][row] }
|
||||
|
||||
export function tb_set_f<T>(tb: Table<T>, c: int, row: int, v: float) -> void {
|
||||
tb.f[c][row] = v
|
||||
tb_stamp(tb, row)
|
||||
for g in 0 .. len(tb.grids) {
|
||||
let gr = tb.grids[g]
|
||||
if gr.cx == c or gr.cz == c { tb_grid_refile(tb, gr, row) }
|
||||
}
|
||||
}
|
||||
|
||||
# two float columns at once, filed once: a move
|
||||
export function tb_set_xz<T>(tb: Table<T>, cx: int, cz: int, row: int, x: float, z: float) -> void {
|
||||
tb.f[cx][row] = x
|
||||
tb.f[cz][row] = z
|
||||
tb_stamp(tb, row)
|
||||
for g in 0 .. len(tb.grids) {
|
||||
let gr = tb.grids[g]
|
||||
if gr.cx == cx or gr.cz == cz or gr.cx == cz or gr.cz == cx { tb_grid_refile(tb, gr, row) }
|
||||
}
|
||||
}
|
||||
|
||||
export function tb_set_i<T>(tb: Table<T>, c: int, row: int, v: int) -> void {
|
||||
tb.i[c][row] = v
|
||||
tb_stamp(tb, row)
|
||||
for g in 0 .. len(tb.grids) { if tb.grids[g].gate == c { tb_grid_refile(tb, tb.grids[g], row) } }
|
||||
for k in 0 .. len(tb.idx) {
|
||||
let ix = tb.idx[k]
|
||||
if ix.col == c or ix.gate == c { tb_ix_refile(tb, ix, row) }
|
||||
}
|
||||
}
|
||||
|
||||
# a row written without a setter (tb.f / tb.i directly, for speed) is refiled everywhere by this
|
||||
export function tb_refile<T>(tb: Table<T>, row: int) -> void {
|
||||
tb_stamp(tb, row)
|
||||
for g in 0 .. len(tb.grids) { tb_grid_refile(tb, tb.grids[g], row) }
|
||||
for k in 0 .. len(tb.idx) { tb_ix_refile(tb, tb.idx[k], row) }
|
||||
}
|
||||
|
||||
export function tb_mark<T>(tb: Table<T>, row: int) -> void { tb_stamp(tb, row) }
|
||||
|
||||
function tb_gate_on<T>(tb: Table<T>, gate: int, row: int) -> bool { return gate < 0 or tb.i[gate][row] != 0 }
|
||||
|
||||
function tb_grid_refile<T>(tb: Table<T>, g: Grid, row: int) -> void {
|
||||
grid_refile(g, row, tb.f[g.cx][row], tb.f[g.cz][row], tb_gate_on(tb, g.gate, row))
|
||||
}
|
||||
|
||||
function tb_ix_refile<T>(tb: Table<T>, ix: IntIndex, row: int) -> void {
|
||||
if tb_gate_on(tb, ix.gate, row) { ix_file(ix, row, tb.i[ix.col][row]) } else { ix_unfile(ix, row) }
|
||||
}
|
||||
|
||||
# change detection: the rows a setter wrote after `since`, into `out` (cleared first); no allocation
|
||||
export function tb_changed_since<T>(tb: Table<T>, since: int, out: words) -> int {
|
||||
List.clear(out)
|
||||
for b in 0 .. len(tb.blk) {
|
||||
if tb.blk[b] <= since { continue }
|
||||
var end = b * 64 + 64
|
||||
if end > tb.n { end = tb.n }
|
||||
for r in b * 64 .. end { if tb.changed[r] > since { push(out, r) } }
|
||||
}
|
||||
return len(out)
|
||||
}
|
||||
|
||||
export function tb_added_since<T>(tb: Table<T>, since: int, out: words) -> int {
|
||||
List.clear(out)
|
||||
for b in 0 .. len(tb.blk) {
|
||||
if tb.blk[b] <= since { continue }
|
||||
var end = b * 64 + 64
|
||||
if end > tb.n { end = tb.n }
|
||||
for r in b * 64 .. end { if tb.added[r] > since { push(out, r) } }
|
||||
}
|
||||
return len(out)
|
||||
}
|
||||
|
|
@ -8,3 +8,17 @@ import "rng.ludic"
|
|||
import "save.ludic"
|
||||
import "save_fields.ludic"
|
||||
import "system.ludic"
|
||||
import "ecs_table.ludic"
|
||||
import "ecs_hooks.ludic"
|
||||
import "ecs_table_remove.ludic"
|
||||
import "ecs_table_set.ludic"
|
||||
import "ecs_grid.ludic"
|
||||
import "ecs_grid_file.ludic"
|
||||
import "ecs_grid_query.ludic"
|
||||
import "ecs_grid_within.ludic"
|
||||
import "ecs_grid_where.ludic"
|
||||
import "ecs_index.ludic"
|
||||
import "ecs_index_slot.ludic"
|
||||
import "ecs_map.ludic"
|
||||
import "ecs_plan.ludic"
|
||||
import "ecs_group.ludic"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
export property Queue<T> {
|
||||
items: []T = null
|
||||
tag: QueueTag = null
|
||||
none: []T = null # what draining an empty queue hands back, never pushed to
|
||||
spare: []T = null # the list the last drain handed out: cleared and reused by the next
|
||||
}
|
||||
# what a queue says about itself, whatever it holds: its name and how many facts wait in it
|
||||
export property QueueTag {
|
||||
|
|
@ -22,6 +24,8 @@ export function queue_new<T>(name: string) -> Queue<T> {
|
|||
q.items = new []T
|
||||
q.tag = new QueueTag
|
||||
q.tag.name = name
|
||||
q.none = new []T
|
||||
q.spare = new []T
|
||||
return q
|
||||
}
|
||||
|
||||
|
|
@ -30,18 +34,25 @@ export function q_push<T>(q: Queue<T>, v: T) -> void {
|
|||
q.tag.pending = len(q.items)
|
||||
}
|
||||
|
||||
# the facts in the order they were pushed; the queue is empty afterwards
|
||||
# the facts in the order they were pushed; the queue is empty afterwards. Ludic frees nothing, so
|
||||
# a drain allocates nothing: the queue keeps two lists and hands one out while the other fills, and
|
||||
# an empty drain hands back one shared empty list. What a drain returns is good until the NEXT
|
||||
# drain of the same queue - read it, never push to it, never keep it.
|
||||
export function q_drain<T>(q: Queue<T>) -> []T {
|
||||
if len(q.items) == 0 { return q.none }
|
||||
let out = q.items
|
||||
q.items = new []T
|
||||
List.clear(q.spare)
|
||||
q.items = q.spare
|
||||
q.spare = out
|
||||
q.tag.pending = 0
|
||||
return out
|
||||
}
|
||||
|
||||
export function q_len<T>(q: Queue<T>) -> int { return len(q.items) }
|
||||
|
||||
# in place: the live list was never handed out (a drain gives its list away and starts another)
|
||||
export function q_clear<T>(q: Queue<T>) -> void {
|
||||
q.items = new []T
|
||||
List.clear(q.items)
|
||||
q.tag.pending = 0
|
||||
}
|
||||
|
||||
|
|
|
|||
130
packages/ludic.base/tests/ecs_fuzz_test.ludic
Normal file
130
packages/ludic.base/tests/ecs_fuzz_test.ludic
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# ecs_fuzz_test.ludic - thousands of random adds, removes, moves, kind changes and gate flips, and
|
||||
# after every burst the grid and the kind index answer exactly what a scan of the rows answers
|
||||
import "ludic.base"
|
||||
program EcsFuzzTest {
|
||||
numbers float
|
||||
property Toy { n: int = 0 }
|
||||
const CX: int = 0
|
||||
const CZ: int = 1
|
||||
const KIND: int = 0
|
||||
const ON: int = 1
|
||||
const KINDS: int = 6
|
||||
|
||||
state FuzzState {
|
||||
seed: int = 12345
|
||||
}
|
||||
|
||||
function rnd(fz: mut FuzzState, n: int) -> int {
|
||||
fz.seed = (fz.seed * 1103515245 + 12345) & 2147483647
|
||||
return (fz.seed >> 8) % n
|
||||
}
|
||||
function rpos(fz: mut FuzzState) -> float { return float(rnd(fz, 40000)) / 10.0 - 2000.0 }
|
||||
|
||||
function step(fz: mut FuzzState, tb: Table<Toy>) -> void {
|
||||
let op = rnd(fz, 10)
|
||||
if op < 4 or tb_len(tb) == 0 {
|
||||
let toy = new Toy
|
||||
toy.n = rnd(fz, 5)
|
||||
let r = tb_row(tb, tb_add(tb, toy))
|
||||
tb_set_xz(tb, CX, CZ, r, rpos(fz), rpos(fz))
|
||||
tb_set_i(tb, KIND, r, rnd(fz, KINDS))
|
||||
tb_set_i(tb, ON, r, rnd(fz, 4) % 3)
|
||||
return
|
||||
}
|
||||
let r = rnd(fz, tb_len(tb))
|
||||
if op < 6 { tb_remove(tb, tb_handle(tb, r)) } else if op < 8 {
|
||||
tb_set_xz(tb, CX, CZ, r, tb_f(tb, CX, r) + float(rnd(fz, 200)) - 100.0, tb_f(tb, CZ, r) + float(rnd(fz, 200)) - 100.0)
|
||||
} else if op < 9 { tb_set_i(tb, KIND, r, rnd(fz, KINDS)) } else { tb_set_i(tb, ON, r, 1 - tb_i(tb, ON, r)) }
|
||||
}
|
||||
|
||||
# the scan's answer: the smallest squared distance among on rows matching, or -1
|
||||
function brute_d2(tb: Table<Toy>, x: float, z: float, maxr: float, kind: int) -> float {
|
||||
var best = -1.0
|
||||
for r in 0 .. tb_len(tb) {
|
||||
if tb_i(tb, ON, r) == 0 or (kind >= 0 and tb_i(tb, KIND, r) != kind) { continue }
|
||||
let dx = tb_f(tb, CX, r) - x
|
||||
let dz = tb_f(tb, CZ, r) - z
|
||||
let d2 = dx * dx + dz * dz
|
||||
if (maxr <= 0.0 or d2 <= maxr * maxr) and (best < 0.0 or d2 < best) { best = d2 }
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
function keep_even(t: Toy) -> bool { return t.n % 2 == 0 }
|
||||
|
||||
function brute_even(tb: Table<Toy>, x: float, z: float, maxr: float) -> float {
|
||||
var best = -1.0
|
||||
for r in 0 .. tb_len(tb) {
|
||||
if tb_i(tb, ON, r) == 0 or tb_rec(tb, r).n % 2 != 0 { continue }
|
||||
let dx = tb_f(tb, CX, r) - x
|
||||
let dz = tb_f(tb, CZ, r) - z
|
||||
let d2 = dx * dx + dz * dz
|
||||
if (maxr <= 0.0 or d2 < maxr * maxr) and (best < 0.0 or d2 < best) { best = d2 }
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
function where_check(tb: Table<Toy>, g: Grid, ix: IntIndex, x: float, z: float, maxr: float, kind: int, recs: []Toy) -> void {
|
||||
let want = brute_even(tb, x, z, maxr)
|
||||
let row = tb_nearest_where(tb, g, x, z, maxr, fn keep_even)
|
||||
if want < 0.0 { expect_eq(row, -1) } else {
|
||||
let dx = tb_f(tb, CX, row) - x
|
||||
let dz = tb_f(tb, CZ, row) - z
|
||||
expect(dx * dx + dz * dz == want)
|
||||
}
|
||||
expect_eq(tb_within_recs(tb, g, x, z, 150.0, -1, 0, recs), brute_within(tb, x, z, 150.0, -1))
|
||||
if kind >= 0 { expect_eq(tb_within_of_recs(tb, g, ix, x, z, 150.0, kind, recs), brute_within(tb, x, z, 150.0, kind)) }
|
||||
}
|
||||
|
||||
function brute_within(tb: Table<Toy>, x: float, z: float, r: float, kind: int) -> int {
|
||||
var n = 0
|
||||
for i in 0 .. tb_len(tb) {
|
||||
if tb_i(tb, ON, i) == 0 or (kind >= 0 and tb_i(tb, KIND, i) != kind) { continue }
|
||||
let dx = tb_f(tb, CX, i) - x
|
||||
let dz = tb_f(tb, CZ, i) - z
|
||||
if dx * dx + dz * dz <= r * r { n += 1 }
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
function check(fz: mut FuzzState, tb: Table<Toy>, g: Grid, ix: IntIndex, out: words, recs: []Toy) -> void {
|
||||
for q in 0 .. 20 {
|
||||
let x = rpos(fz)
|
||||
let z = rpos(fz)
|
||||
let kind = rnd(fz, KINDS + 1) - 1
|
||||
var maxr = 0.0
|
||||
if q % 2 == 1 { maxr = float(rnd(fz, 600)) }
|
||||
var mc = KIND
|
||||
if kind < 0 { mc = -1 }
|
||||
let want = brute_d2(tb, x, z, maxr, kind)
|
||||
let row = tb_nearest(tb, g, x, z, maxr, mc, kind)
|
||||
if want < 0.0 { expect_eq(row, -1) } else {
|
||||
expect(row >= 0)
|
||||
let dx = tb_f(tb, CX, row) - x
|
||||
let dz = tb_f(tb, CZ, row) - z
|
||||
expect(dx * dx + dz * dz == want)
|
||||
}
|
||||
where_check(tb, g, ix, x, z, maxr, kind, recs)
|
||||
let rad = float(rnd(fz, 400))
|
||||
expect_eq(tb_within(tb, g, x, z, rad, mc, kind, out), brute_within(tb, x, z, rad, kind))
|
||||
}
|
||||
for k in 0 .. KINDS { expect_eq(ix_count(ix, k), brute_within(tb, 0.0, 0.0, 100000.0, k)) }
|
||||
}
|
||||
|
||||
function fuzz_case(fz: mut FuzzState) -> void {
|
||||
let tb: Table<Toy> = table_new(2, 2)
|
||||
let g = tb_grid(tb, CX, CZ, ON, 16.0)
|
||||
let ix = tb_index(tb, KIND, ON)
|
||||
let out = words(0)
|
||||
let recs = new []Toy
|
||||
for burst in 0 .. 60 {
|
||||
for s in 0 .. 150 { step(fz, tb) }
|
||||
check(fz, tb, g, ix, out, recs)
|
||||
}
|
||||
tb_clear(tb)
|
||||
expect_eq(grid_count(g), 0)
|
||||
expect_eq(ix_count(ix, 1), 0)
|
||||
}
|
||||
|
||||
test "the grid and the kind index agree with a scan through 9000 random changes" (fz: mut FuzzState) { fuzz_case(fz) }
|
||||
}
|
||||
186
packages/ludic.base/tests/ecs_test.ludic
Normal file
186
packages/ludic.base/tests/ecs_test.ludic
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
# ecs_test.ludic - Table<T>: handles that go stale, dense rows after a removal, the setters keeping
|
||||
# the grid and the kind index current, change ticks, and IntMap. Each case is a function because a
|
||||
# generic call is not resolved inside a `test` body yet.
|
||||
import "ludic.base"
|
||||
program EcsTest {
|
||||
numbers float
|
||||
property Toy { name: string = "" }
|
||||
const CX: int = 0
|
||||
const CZ: int = 1
|
||||
const KIND: int = 0
|
||||
const ON: int = 1
|
||||
|
||||
state HookState {
|
||||
added: int = 0
|
||||
gone: int = 0
|
||||
last: int = -1
|
||||
}
|
||||
function seen_add(hook_st: mut HookState, h: int) -> void {
|
||||
hook_st.added += 1
|
||||
hook_st.last = h
|
||||
}
|
||||
function seen_gone(hook_st: mut HookState, h: int) -> void { hook_st.gone += 1 }
|
||||
|
||||
function toy(name: string) -> Toy {
|
||||
let t = new Toy
|
||||
t.name = name
|
||||
return t
|
||||
}
|
||||
|
||||
function put(tb: Table<Toy>, name: string, x: float, z: float, kind: int) -> int {
|
||||
let h = tb_add(tb, toy(name))
|
||||
let r = tb_row(tb, h)
|
||||
tb_set_xz(tb, CX, CZ, r, x, z)
|
||||
tb_set_i(tb, KIND, r, kind)
|
||||
tb_set_i(tb, ON, r, 1)
|
||||
return h
|
||||
}
|
||||
|
||||
function handles_case() -> void {
|
||||
let tb: Table<Toy> = table_new(2, 2)
|
||||
let a = put(tb, "a", 0.0, 0.0, 1)
|
||||
let b = put(tb, "b", 1.0, 0.0, 1)
|
||||
let c = put(tb, "c", 2.0, 0.0, 2)
|
||||
expect_eq(tb_len(tb), 3)
|
||||
expect(tb_remove(tb, a))
|
||||
expect(not tb_alive(tb, a))
|
||||
expect(not tb_remove(tb, a))
|
||||
expect_eq(tb_len(tb), 2)
|
||||
expect_eq(tb_rec(tb, tb_row(tb, c)).name, "c")
|
||||
expect_eq(tb_rec(tb, tb_row(tb, b)).name, "b")
|
||||
let d = put(tb, "d", 3.0, 0.0, 1)
|
||||
expect(d != a)
|
||||
expect_eq(d & ECS_SLOT, a & ECS_SLOT)
|
||||
expect(not tb_alive(tb, a))
|
||||
expect_eq(tb_rec(tb, tb_row(tb, d)).name, "d")
|
||||
}
|
||||
|
||||
function grid_case() -> void {
|
||||
let tb: Table<Toy> = table_new(2, 2)
|
||||
let g = tb_grid(tb, CX, CZ, ON, 16.0)
|
||||
let near = put(tb, "near", 5.0, 5.0, 1)
|
||||
put(tb, "far", 500.0, -300.0, 1)
|
||||
let fire = put(tb, "fire", -40.0, 2.0, 2)
|
||||
expect_eq(tb_handle(tb, tb_nearest(tb, g, 0.0, 0.0, 0.0, -1, 0)), near)
|
||||
expect_eq(tb_handle(tb, tb_nearest(tb, g, 0.0, 0.0, 0.0, KIND, 2)), fire)
|
||||
expect_eq(tb_nearest(tb, g, 0.0, 0.0, 10.0, KIND, 2), -1)
|
||||
tb_set_i(tb, ON, tb_row(tb, near), 0)
|
||||
expect_eq(tb_handle(tb, tb_nearest(tb, g, 0.0, 0.0, 0.0, KIND, 1)), tb_handle(tb, tb_nearest(tb, g, 499.0, -300.0, 0.0, -1, 0)))
|
||||
let out = words(0)
|
||||
expect_eq(tb_within(tb, g, 0.0, 0.0, 50.0, -1, 0, out), 1)
|
||||
tb_set_i(tb, ON, tb_row(tb, near), 1)
|
||||
expect_eq(tb_within(tb, g, 0.0, 0.0, 50.0, -1, 0, out), 2)
|
||||
tb_set_xz(tb, CX, CZ, tb_row(tb, fire), 900.0, 900.0)
|
||||
expect_eq(tb_within(tb, g, 0.0, 0.0, 50.0, -1, 0, out), 1)
|
||||
}
|
||||
|
||||
function index_case() -> void {
|
||||
let tb: Table<Toy> = table_new(2, 2)
|
||||
let ix = tb_index(tb, KIND, ON)
|
||||
let a = put(tb, "a", 0.0, 0.0, 3)
|
||||
put(tb, "b", 0.0, 0.0, 3)
|
||||
put(tb, "c", 0.0, 0.0, 5)
|
||||
expect_eq(ix_count(ix, 3), 2)
|
||||
expect_eq(ix_count(ix, 5), 1)
|
||||
expect_eq(ix_count(ix, 99), 0)
|
||||
tb_set_i(tb, ON, tb_row(tb, a), 0)
|
||||
expect_eq(ix_count(ix, 3), 1)
|
||||
tb_remove(tb, a)
|
||||
tb_set_i(tb, KIND, ix_first(ix, 3), 5)
|
||||
expect_eq(ix_count(ix, 3), 0)
|
||||
expect_eq(ix_count(ix, 5), 2)
|
||||
}
|
||||
|
||||
function ticks_case() -> void {
|
||||
let tb: Table<Toy> = table_new(2, 2)
|
||||
let a = put(tb, "a", 0.0, 0.0, 1)
|
||||
put(tb, "b", 0.0, 0.0, 1)
|
||||
let seen = tb_tick(tb)
|
||||
tb_advance(tb)
|
||||
let out = words(0)
|
||||
expect_eq(tb_changed_since(tb, seen, out), 0)
|
||||
tb_set_f(tb, CX, tb_row(tb, a), 4.0)
|
||||
expect_eq(tb_changed_since(tb, seen, out), 1)
|
||||
expect_eq(out[0], tb_row(tb, a))
|
||||
expect_eq(tb_added_since(tb, seen, out), 0)
|
||||
}
|
||||
|
||||
function map_case() -> void {
|
||||
let m = imap_new()
|
||||
for k in 0 .. 5000 { imap_put(m, k * 7, k) }
|
||||
expect_eq(imap_len(m), 5000)
|
||||
expect_eq(imap_get(m, 700, -1), 100)
|
||||
expect_eq(imap_get(m, 701, -1), -1)
|
||||
for k in 0 .. 2500 { imap_del(m, k * 14) }
|
||||
expect_eq(imap_len(m), 2500)
|
||||
expect_eq(imap_get(m, 14, -1), -1)
|
||||
expect_eq(imap_get(m, 21, -1), 3)
|
||||
imap_put(m, 14, 9)
|
||||
expect_eq(imap_get(m, 14, -1), 9)
|
||||
}
|
||||
|
||||
function hooks_case(hook_st: HookState) -> void {
|
||||
let tb: Table<Toy> = table_new(2, 2)
|
||||
tb_on_add(tb, fn seen_add)
|
||||
tb_on_remove(tb, fn seen_gone)
|
||||
let a = put(tb, "a", 0.0, 0.0, 1)
|
||||
expect_eq(hook_st.last, a)
|
||||
put(tb, "b", 0.0, 0.0, 1)
|
||||
tb_remove(tb, a)
|
||||
tb_remove(tb, a)
|
||||
expect_eq(hook_st.added, 2)
|
||||
expect_eq(hook_st.gone, 1)
|
||||
}
|
||||
|
||||
# 300 rows over five blocks; a change in one block is found, the untouched blocks skipped
|
||||
function blocks_case() -> void {
|
||||
let tb: Table<Toy> = table_new(2, 2)
|
||||
for i in 0 .. 300 { put(tb, "x", float(i), 0.0, 1) }
|
||||
let seen = tb_tick(tb)
|
||||
tb_advance(tb)
|
||||
let out = words(0)
|
||||
expect_eq(tb_changed_since(tb, seen, out), 0)
|
||||
tb_set_f(tb, CX, 200, 9.0)
|
||||
tb_set_f(tb, CX, 7, 9.0)
|
||||
expect_eq(tb_changed_since(tb, seen, out), 2)
|
||||
expect_eq(out[0], 7)
|
||||
expect_eq(out[1], 200)
|
||||
let before = tb_tick(tb)
|
||||
tb_advance(tb)
|
||||
tb_remove(tb, tb_handle(tb, 3))
|
||||
expect_eq(tb_changed_since(tb, before, out), 0) # a row moved into a gap was not written
|
||||
expect_eq(tb_changed_since(tb, seen, out), 2) # a removal is told by tb_on_remove
|
||||
}
|
||||
|
||||
# values far apart (a chunk's packed cell) each take one list, not every list up to them
|
||||
function sparse_case() -> void {
|
||||
let tb: Table<Toy> = table_new(2, 2)
|
||||
let ix = tb_index(tb, KIND, -1)
|
||||
let a = chunk_of(9000.0, -9000.0, 256.0)
|
||||
let b = chunk_of(-512.0, 700.0, 256.0)
|
||||
expect(a > 100000 and b > 100000)
|
||||
put(tb, "a", 0.0, 0.0, a)
|
||||
put(tb, "b", 0.0, 0.0, b)
|
||||
let c = put(tb, "c", 0.0, 0.0, a)
|
||||
put(tb, "d", 0.0, 0.0, 3)
|
||||
expect_eq(ix_count(ix, a), 2)
|
||||
expect_eq(ix_count(ix, b), 1)
|
||||
expect_eq(ix_count(ix, 3), 1)
|
||||
expect(len(ix.lists) < 1100)
|
||||
tb_set_i(tb, KIND, tb_row(tb, c), b)
|
||||
expect_eq(ix_count(ix, a), 1)
|
||||
expect_eq(ix_count(ix, b), 2)
|
||||
expect_eq(tb_remove_all(tb, ix, b), 2)
|
||||
expect_eq(ix_count(ix, b), 0)
|
||||
expect_eq(tb_len(tb), 2)
|
||||
}
|
||||
|
||||
test "a sparse value takes one list" () { sparse_case() }
|
||||
test "observers hear each add, and a remove once" (hook_st: HookState) { hooks_case(hook_st) }
|
||||
test "change detection skips the blocks nobody wrote" () { blocks_case() }
|
||||
test "a removed handle misses, rows stay dense, a reused slot is a new handle" () { handles_case() }
|
||||
test "the grid answers nearest and within, gated, after moves" () { grid_case() }
|
||||
test "the kind index follows the setters, the gate and removals" () { index_case() }
|
||||
test "change ticks name the rows written since" () { ticks_case() }
|
||||
test "IntMap puts, finds, deletes and grows" () { map_case() }
|
||||
}
|
||||
|
|
@ -67,7 +67,44 @@ program QueueTest {
|
|||
expect_eq(len(core_undrained(tags)), 0)
|
||||
}
|
||||
|
||||
# an empty drain allocates nothing and hands back the same empty list; a drained list is the
|
||||
# caller's, untouched by what is pushed or cleared after it
|
||||
function empty_case() -> void {
|
||||
let q: Queue<int> = queue_new("quiet")
|
||||
let a = q_drain(q)
|
||||
let b = q_drain(q)
|
||||
expect_eq(len(a), 0)
|
||||
expect(a == b)
|
||||
q_push(q, 5)
|
||||
expect_eq(len(a), 0)
|
||||
let got = q_drain(q)
|
||||
q_push(q, 6)
|
||||
q_clear(q)
|
||||
expect_eq(len(got), 1)
|
||||
expect_eq(got[0], 5)
|
||||
expect_eq(q_len(q), 0)
|
||||
expect_eq(len(q_drain(q)), 0)
|
||||
}
|
||||
|
||||
test "a queue drains in the order it was pushed" () { fifo_case() }
|
||||
# two lists, reused: a drained list is the caller's until the next drain of that queue
|
||||
function reuse_case() -> void {
|
||||
let q: Queue<int> = queue_new("reused")
|
||||
q_push(q, 1)
|
||||
let a = q_drain(q)
|
||||
q_push(q, 2)
|
||||
expect_eq(a[0], 1)
|
||||
let b = q_drain(q)
|
||||
expect_eq(b[0], 2)
|
||||
q_push(q, 3)
|
||||
let c = q_drain(q)
|
||||
expect(c == a)
|
||||
expect_eq(len(c), 1)
|
||||
expect_eq(c[0], 3)
|
||||
}
|
||||
|
||||
test "a drain reuses the queue's two lists" () { reuse_case() }
|
||||
test "an empty drain allocates nothing, and a drained list is the caller's" () { empty_case() }
|
||||
test "a queue holds records" () { record_case() }
|
||||
test "clear drops what is waiting" () { clear_case() }
|
||||
test "a queue's element type is told by the queue alone" () { infer_case() }
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ property Actor {
|
|||
cast_hidden: bool = false,
|
||||
casts: bool = true,
|
||||
id: int = 0,
|
||||
row: int = -1, # where it stands in ac_actors, so it leaves in O(1); -1 off the stage
|
||||
cutout: bool = false, # alpha-tested (a flame's cards)
|
||||
emissive: float = 0.0, # float bits: self-lit strength
|
||||
skin: Skin, # this instance's own pose (skin_clone); null: the model's
|
||||
|
|
@ -82,11 +83,27 @@ function actor_init(render3d_st: mut Render3dState) -> void {
|
|||
render3d_st.ac_out_cut = ac_prog_new(render3d_st, "skin.vert", "outline.frag", "#define OUTLINE\n#define ALPHA_TEST\n")
|
||||
render3d_st.ac_actors = new []Actor
|
||||
}
|
||||
# off the stage: the last actor takes its row, so the list stays dense and nothing is rebuilt
|
||||
# (it was a new list of every other actor per removal, never freed)
|
||||
function actor_remove(render3d_st: mut Render3dState, a: Actor) -> void {
|
||||
if render3d_st.ac_actors == null { return }
|
||||
let keep = new []Actor
|
||||
for i in 0 .. len(render3d_st.ac_actors) { if render3d_st.ac_actors[i].id != a.id { push(keep, render3d_st.ac_actors[i]) } }
|
||||
render3d_st.ac_actors = keep
|
||||
let l = render3d_st.ac_actors
|
||||
if l == null or a == null { return }
|
||||
let r = a.row
|
||||
if r < 0 or r >= len(l) or l[r].id != a.id { return }
|
||||
let last = List.pop(l)
|
||||
if last.id != a.id {
|
||||
l[r] = last
|
||||
last.row = r
|
||||
}
|
||||
a.row = -1
|
||||
}
|
||||
|
||||
# back on the stage (an actor kept across a world swap)
|
||||
function actor_keep(render3d_st: mut Render3dState, a: Actor) -> void {
|
||||
if a == null { return }
|
||||
if render3d_st.ac_actors == null { render3d_st.ac_actors = new []Actor }
|
||||
a.row = len(render3d_st.ac_actors)
|
||||
push(render3d_st.ac_actors, a)
|
||||
}
|
||||
# colour one named part of the model (a material name from the file)
|
||||
function actor_tint_part(a: Actor, name: string, r: float, g: float, b: float) -> void {
|
||||
|
|
@ -114,8 +131,7 @@ function actor_new(render3d_st: mut Render3dState, model: Model) -> Actor {
|
|||
render3d_st.ac_next_id += 1; a.id = render3d_st.ac_next_id
|
||||
if model != null { a.radius = Math.max(model.radius, model.height) + 1.0 }
|
||||
a.cull = 450.0
|
||||
if render3d_st.ac_actors == null { render3d_st.ac_actors = new []Actor }
|
||||
push(render3d_st.ac_actors, a)
|
||||
actor_keep(render3d_st, a)
|
||||
return a
|
||||
}
|
||||
|
||||
|
|
@ -396,6 +412,8 @@ function ac_caster_count(render3d_st: mut Render3dState, a: Actor) -> void {
|
|||
# every actor off the stage at once, for a world being replaced. Actors own no GL objects;
|
||||
# their models belong to whoever loaded them.
|
||||
function actor_clear_all(render3d_st: mut Render3dState) -> void {
|
||||
render3d_st.ac_actors = new []Actor
|
||||
if render3d_st.ac_actors == null { render3d_st.ac_actors = new []Actor }
|
||||
for i in 0 .. len(render3d_st.ac_actors) { render3d_st.ac_actors[i].row = -1 }
|
||||
List.clear(render3d_st.ac_actors)
|
||||
outline_clear(render3d_st)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,6 +189,9 @@ export state Render3dState {
|
|||
gvk_pool: long = 0
|
||||
gvk_fence: bytes = null
|
||||
gvk_frame_fence: bytes = null # the presented frame still on the GPU (one frame in flight)
|
||||
gvk_tmp_buf: pointer = null # the per-call scratch ring (gvk_tmp)
|
||||
gvk_tmp_off: int = 0
|
||||
gvk_seen: words = null # gvk_draw's vertex buffers already bound, reused
|
||||
gvk_frame_pending: bool = false
|
||||
gvk_frame_pending_cb: pointer = null
|
||||
gvk_frame_pending_no: int = -1 # which frame it is (gvk_frame_no when it was submitted)
|
||||
|
|
@ -197,6 +200,20 @@ export state Render3dState {
|
|||
gvk_retired_frame: []int = null # the last frame that read each retired buffer
|
||||
gvk_labels: bool = false # R3D_VK_LABELS: each profiled pass is a debug label (Metal System Trace)
|
||||
gvk_inflight: int = -1
|
||||
gvk_nopool: bool = false
|
||||
# R3D_VK_PROF: Vulkan objects made and destroyed, reported every 120 frames
|
||||
gvk_mk_img: int = 0
|
||||
gvk_mk_view: int = 0
|
||||
gvk_mk_buf: int = 0
|
||||
gvk_mk_mem: int = 0
|
||||
gvk_mk_smp: int = 0
|
||||
gvk_mk_set: int = 0
|
||||
gvk_mk_cmd: int = 0
|
||||
gvk_mk_dpool: int = 0
|
||||
gvk_mk_x_img: int = 0
|
||||
gvk_mk_x_view: int = 0
|
||||
gvk_mk_x_buf: int = 0
|
||||
gvk_mk_x_mem: int = 0
|
||||
gvk_label_depth: int = 0 # -1 until asked: 1 leaves the presented frame in flight
|
||||
gvk_has_hqr: bool = false
|
||||
gvk_ts_period: float = 0.0 # float bits: nanoseconds per timestamp tick
|
||||
|
|
@ -269,8 +286,9 @@ export state Render3dState {
|
|||
gvk_screen_depth: int = 0
|
||||
gvk_screen_w: int = 0
|
||||
gvk_screen_h: int = 0
|
||||
gvk_layer_views: []string = null # "tex:layer" -> index into gvk_layer_view
|
||||
gvk_layer_views: []int = null # generation * 4096 + layer, beside gvk_layer_view_tex
|
||||
gvk_layer_view: []long = null
|
||||
gvk_layer_view_tex: []int = null # the texture each cached view is of, so a release drops them
|
||||
gvk_fb_ncolor: words = null # per framebuffer: colour slots drawn (draw buffers; 0 = none)
|
||||
gvk_hdr_want: bool = false # r3d_hdr: the setting
|
||||
gvk_hdr_on: bool = false # the swapchain is HDR10 now
|
||||
|
|
@ -377,6 +395,11 @@ export state Render3dState {
|
|||
gg_mesh: []Mesh = null
|
||||
gg_n: int = 0
|
||||
gg_max: int = 0
|
||||
gg_rb: words = null # the per-frame dispatch arguments, made once (grass_gpu.ludic)
|
||||
gg_cb: words = null
|
||||
gg_bufs: words = null
|
||||
gg_texs: words = null
|
||||
gg_pr: words = null
|
||||
grass_band_mesh: []Mesh = null # per band: the blade it draws (rows fall with distance)
|
||||
grass_mesh3: Mesh = null # three rows: the middle distance
|
||||
grass_mesh2: Mesh = null # two rows, one quad: far enough that the arch is under a pixel
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ function gpu_query_new(render3d_st: mut Render3dState, n: int, ids: words) -> vo
|
|||
function gpu_query_begin(render3d_st: mut Render3dState, id: int) -> void { if render3d_st.gpu_kind == GPU_VK { gvk_query_begin(render3d_st, id); return }; gl_begin_query(GL_TIME_ELAPSED, id) }
|
||||
function gpu_query_end(render3d_st: mut Render3dState) -> void { if render3d_st.gpu_kind == GPU_VK { gvk_query_end(render3d_st); return }; gl_end_query(GL_TIME_ELAPSED) }
|
||||
# true once the query has its result; the nanoseconds (low 32 bits) are then in out[0]
|
||||
function gpu_query_result(render3d_st: Render3dState, id: int, out: words) -> bool {
|
||||
function gpu_query_result(render3d_st: mut Render3dState, id: int, out: words) -> bool {
|
||||
if render3d_st.gpu_kind == GPU_VK { return gvk_query_result(render3d_st, id, out) }
|
||||
gl_get_query_objectiv(id, GL_QUERY_RESULT_AVAILABLE, out)
|
||||
if out[0] == 0 { return false }
|
||||
|
|
|
|||
|
|
@ -292,12 +292,13 @@ function gvk_alloc(render3d_st: mut Render3dState, req: bytes, want: int) -> lon
|
|||
if t < 0 and want == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT { t = gvk_mem_type(render3d_st, allowed, 0) }
|
||||
let zero: long = 0
|
||||
if t < 0 { print(`r3d: vulkan: no memory type for properties {want}`); return zero }
|
||||
let mai = bytes(VkMemoryAllocateInfo_sizeof)
|
||||
let mai = gvk_tmp(render3d_st, VkMemoryAllocateInfo_sizeof)
|
||||
Vk.zero(mai, VkMemoryAllocateInfo_sizeof)
|
||||
Vk.put_i32(mai, VkMemoryAllocateInfo_sType, VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO)
|
||||
Vk.put_i64(mai, VkMemoryAllocateInfo_allocationSize, Vk.get_i64(req, VkMemoryRequirements_size))
|
||||
Vk.put_i32(mai, VkMemoryAllocateInfo_memoryTypeIndex, t)
|
||||
let out = bytes(8)
|
||||
let out = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_mem += 1
|
||||
let r = Vk.allocate_memory(render3d_st.gvk_dev, mai, null, out)
|
||||
if r != VK_SUCCESS { print(`r3d: vulkan: vkAllocateMemory failed (VkResult {r})`); return zero }
|
||||
render3d_st.gvk_n_allocs += 1
|
||||
|
|
@ -331,20 +332,21 @@ function gvk_mem_init(render3d_st: mut Render3dState) -> void {
|
|||
# one vkAllocateMemory of `size` bytes of memory type t, mapped when host-visible; 0 on failure
|
||||
function gvk_mem_raw(render3d_st: mut Render3dState, t: int, size: int, host: bool, out_map: []pointer) -> long {
|
||||
let zero: long = 0
|
||||
let mai = bytes(VkMemoryAllocateInfo_sizeof)
|
||||
let mai = gvk_tmp(render3d_st, VkMemoryAllocateInfo_sizeof)
|
||||
Vk.zero(mai, VkMemoryAllocateInfo_sizeof)
|
||||
Vk.put_i32(mai, VkMemoryAllocateInfo_sType, VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO)
|
||||
let size_l: long = size
|
||||
Vk.put_i64(mai, VkMemoryAllocateInfo_allocationSize, size_l)
|
||||
Vk.put_i32(mai, VkMemoryAllocateInfo_memoryTypeIndex, t)
|
||||
let out = bytes(8)
|
||||
let out = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_mem += 1
|
||||
let r = Vk.allocate_memory(render3d_st.gvk_dev, mai, null, out)
|
||||
if r != VK_SUCCESS { gvk_note(render3d_st, `r3d: vulkan: vkAllocateMemory of {size} bytes failed (VkResult {r}, {render3d_st.gvk_n_allocs} allocations live)`); return zero }
|
||||
render3d_st.gvk_n_allocs += 1
|
||||
let mem = gvk_handle(out)
|
||||
out_map[0] = null
|
||||
if host {
|
||||
let pp = bytes(8)
|
||||
let pp = gvk_tmp(render3d_st, 8)
|
||||
if Vk.map_memory(render3d_st.gvk_dev, mem, zero, size_l, 0, pp) != VK_SUCCESS { gvk_note(render3d_st, "r3d: vulkan: vkMapMemory failed"); return zero }
|
||||
out_map[0] = Vk.get_ptr(pp, 0)
|
||||
}
|
||||
|
|
@ -447,6 +449,7 @@ function gvk_mem_free(render3d_st: mut Render3dState, a: int) -> void {
|
|||
let zero: long = 0
|
||||
if b < 0 {
|
||||
if render3d_st.gvk_al_map[a] != null { Vk.unmap_memory(render3d_st.gvk_dev, render3d_st.gvk_al_mem[a]) }
|
||||
render3d_st.gvk_mk_x_mem += 1
|
||||
Vk.free_memory(render3d_st.gvk_dev, render3d_st.gvk_al_mem[a], null)
|
||||
render3d_st.gvk_n_allocs -= 1
|
||||
} else {
|
||||
|
|
@ -466,6 +469,7 @@ function gvk_mem_free(render3d_st: mut Render3dState, a: int) -> void {
|
|||
if off == 0 and n == render3d_st.gvk_blk_size[b] {
|
||||
# the block is empty again: give it back, or a world swapped out keeps its memory for good
|
||||
if render3d_st.gvk_blk_map[b] != null { Vk.unmap_memory(render3d_st.gvk_dev, render3d_st.gvk_blk_mem[b]) }
|
||||
render3d_st.gvk_mk_x_mem += 1
|
||||
Vk.free_memory(render3d_st.gvk_dev, render3d_st.gvk_blk_mem[b], null)
|
||||
render3d_st.gvk_n_allocs -= 1
|
||||
render3d_st.gvk_blk_mem[b] = zero; render3d_st.gvk_blk_map[b] = null; render3d_st.gvk_blk_kind[b] = -1; render3d_st.gvk_blk_size[b] = 0
|
||||
|
|
@ -503,17 +507,35 @@ function gvk_cmd_init(render3d_st: mut Render3dState) -> bool {
|
|||
return true
|
||||
}
|
||||
# a command buffer, begun
|
||||
function gvk_once_begin(render3d_st: Render3dState) -> pointer {
|
||||
let cbai = bytes(VkCommandBufferAllocateInfo_sizeof)
|
||||
# ---- scratch -------------------------------------------------------------------------------
|
||||
# The structs a Vulkan call reads are copied before it returns, so the ones built for a draw, a
|
||||
# pass, a barrier or a submit need not outlive it. They were each a bytes() - a malloc nothing
|
||||
# freed, several per draw: about 0.5 MB a frame, and one windowed run grew past 150 GB. They
|
||||
# come from this ring instead: one block, handed out in order and wrapped when it is used up.
|
||||
const GVK_TMP_BYTES: int = 1048576
|
||||
|
||||
function gvk_tmp(render3d_st: mut Render3dState, n: int) -> pointer {
|
||||
if render3d_st.gvk_tmp_buf == null { render3d_st.gvk_tmp_buf = bytes(GVK_TMP_BYTES) }
|
||||
let sz = (n + 15) / 16 * 16
|
||||
if sz > GVK_TMP_BYTES { print(`r3d: vulkan: {n} bytes of scratch asked for at once`); return null }
|
||||
if render3d_st.gvk_tmp_off + sz > GVK_TMP_BYTES { render3d_st.gvk_tmp_off = 0 }
|
||||
let p = mem_off(render3d_st.gvk_tmp_buf, render3d_st.gvk_tmp_off)
|
||||
render3d_st.gvk_tmp_off += sz
|
||||
return p
|
||||
}
|
||||
|
||||
function gvk_once_begin(render3d_st: mut Render3dState) -> pointer {
|
||||
let cbai = gvk_tmp(render3d_st, VkCommandBufferAllocateInfo_sizeof)
|
||||
Vk.zero(cbai, VkCommandBufferAllocateInfo_sizeof)
|
||||
Vk.put_i32(cbai, VkCommandBufferAllocateInfo_sType, VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO)
|
||||
Vk.put_i64(cbai, VkCommandBufferAllocateInfo_commandPool, render3d_st.gvk_pool)
|
||||
Vk.put_i32(cbai, VkCommandBufferAllocateInfo_level, VK_COMMAND_BUFFER_LEVEL_PRIMARY)
|
||||
Vk.put_i32(cbai, VkCommandBufferAllocateInfo_commandBufferCount, 1)
|
||||
let cbs = bytes(8)
|
||||
let cbs = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_cmd += 1
|
||||
if Vk.allocate_command_buffers(render3d_st.gvk_dev, cbai, cbs) != VK_SUCCESS { return null }
|
||||
let cb = Vk.get_ptr(cbs, 0)
|
||||
let cbbi = bytes(VkCommandBufferBeginInfo_sizeof)
|
||||
let cbbi = gvk_tmp(render3d_st, VkCommandBufferBeginInfo_sizeof)
|
||||
Vk.zero(cbbi, VkCommandBufferBeginInfo_sizeof)
|
||||
Vk.put_i32(cbbi, VkCommandBufferBeginInfo_sType, VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO)
|
||||
Vk.put_i32(cbbi, VkCommandBufferBeginInfo_flags, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT)
|
||||
|
|
@ -525,10 +547,10 @@ function gvk_once_end(render3d_st: mut Render3dState, cb: pointer) -> bool {
|
|||
gvk_frame_wait(render3d_st)
|
||||
var r = Vk.end_command_buffer(cb)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkEndCommandBuffer", r) }
|
||||
let cbs = bytes(8)
|
||||
let cbs = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_ptr(cbs, 0, cb)
|
||||
Vk.reset_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_fence)
|
||||
let si = bytes(VkSubmitInfo_sizeof)
|
||||
let si = gvk_tmp(render3d_st, VkSubmitInfo_sizeof)
|
||||
Vk.zero(si, VkSubmitInfo_sizeof)
|
||||
Vk.put_i32(si, VkSubmitInfo_sType, VK_STRUCTURE_TYPE_SUBMIT_INFO)
|
||||
Vk.put_i32(si, VkSubmitInfo_commandBufferCount, 1)
|
||||
|
|
@ -556,6 +578,7 @@ function gvk_inflight_on(render3d_st: mut Render3dState) -> bool {
|
|||
if render3d_st.gvk_inflight < 0 {
|
||||
render3d_st.gvk_inflight = 0
|
||||
if Os.platform() == "macos" { render3d_st.gvk_inflight = 1 }
|
||||
render3d_st.gvk_nopool = r3d_env_has(render3d_st, "R3D_VK_NOPOOL")
|
||||
if r3d_env_has(render3d_st, "R3D_VK_INFLIGHT") { render3d_st.gvk_inflight = Text.to_int(r3d_env(render3d_st, "R3D_VK_INFLIGHT")) }
|
||||
}
|
||||
return render3d_st.gvk_inflight == 1
|
||||
|
|
@ -565,10 +588,10 @@ function gvk_frame_submit(render3d_st: mut Render3dState, cb: pointer) -> bool {
|
|||
gvk_frame_wait(render3d_st)
|
||||
var r = Vk.end_command_buffer(cb)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkEndCommandBuffer", r) }
|
||||
let cbs = bytes(8)
|
||||
let cbs = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_ptr(cbs, 0, cb)
|
||||
Vk.reset_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_frame_fence)
|
||||
let si = bytes(VkSubmitInfo_sizeof)
|
||||
let si = gvk_tmp(render3d_st, VkSubmitInfo_sizeof)
|
||||
Vk.zero(si, VkSubmitInfo_sizeof)
|
||||
Vk.put_i32(si, VkSubmitInfo_sType, VK_STRUCTURE_TYPE_SUBMIT_INFO)
|
||||
Vk.put_i32(si, VkSubmitInfo_commandBufferCount, 1)
|
||||
|
|
@ -585,7 +608,7 @@ function gvk_frame_wait(render3d_st: mut Render3dState) -> void {
|
|||
if not render3d_st.gvk_frame_pending { return }
|
||||
let forever: long = -1
|
||||
Vk.wait_for_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_frame_fence, 1, forever)
|
||||
let cbs = bytes(8)
|
||||
let cbs = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_ptr(cbs, 0, render3d_st.gvk_frame_pending_cb)
|
||||
Vk.free_command_buffers(render3d_st.gvk_dev, render3d_st.gvk_pool, 1, cbs)
|
||||
render3d_st.gvk_frame_pending = false
|
||||
|
|
@ -615,12 +638,12 @@ function gvk_shutdown(render3d_st: mut Render3dState) -> void {
|
|||
function gvk_query_new(render3d_st: mut Render3dState, n: int, ids: words) -> void {
|
||||
for i in 0 .. n { ids[i] = i }
|
||||
if not render3d_st.gvk_has_hqr or render3d_st.gvk_dev == null { return }
|
||||
let qci = bytes(VkQueryPoolCreateInfo_sizeof)
|
||||
let qci = gvk_tmp(render3d_st, VkQueryPoolCreateInfo_sizeof)
|
||||
Vk.zero(qci, VkQueryPoolCreateInfo_sizeof)
|
||||
Vk.put_i32(qci, VkQueryPoolCreateInfo_sType, VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO)
|
||||
Vk.put_i32(qci, VkQueryPoolCreateInfo_queryType, VK_QUERY_TYPE_TIMESTAMP)
|
||||
Vk.put_i32(qci, VkQueryPoolCreateInfo_queryCount, n * 2)
|
||||
let out = bytes(8)
|
||||
let out = gvk_tmp(render3d_st, 8)
|
||||
if Vk.create_query_pool(render3d_st.gvk_dev, qci, null, out) != VK_SUCCESS { return }
|
||||
render3d_st.gvk_qpool = gvk_handle(out)
|
||||
Vk.reset_query_pool(render3d_st.gvk_dev, render3d_st.gvk_qpool, 0, n * 2)
|
||||
|
|
@ -636,9 +659,9 @@ function gvk_query_end(render3d_st: mut Render3dState) -> void {
|
|||
Vk.cmd_write_timestamp(gvk_frame_cb(render3d_st), VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, render3d_st.gvk_qpool, render3d_st.gvk_q_active * 2 + 1)
|
||||
render3d_st.gvk_q_active = -1
|
||||
}
|
||||
function gvk_query_result(render3d_st: Render3dState, id: int, out: words) -> bool {
|
||||
function gvk_query_result(render3d_st: mut Render3dState, id: int, out: words) -> bool {
|
||||
if render3d_st.gvk_qpool == 0 { return false }
|
||||
let data = bytes(16)
|
||||
let data = gvk_tmp(render3d_st, 16)
|
||||
let size: long = 16
|
||||
let stride: long = 8
|
||||
if Vk.get_query_pool_results(render3d_st.gvk_dev, render3d_st.gvk_qpool, id * 2, 2, size, data, stride, VK_QUERY_RESULT_64_BIT) != VK_SUCCESS { return false }
|
||||
|
|
@ -653,7 +676,7 @@ function gvk_label_begin(render3d_st: mut Render3dState, name: pointer) -> void
|
|||
# only inside a frame: a pass the load profiles comes before the frame's pools exist
|
||||
if render3d_st.gpu_kind != GPU_VK or render3d_st.gvk_cb == null { return }
|
||||
let cb = render3d_st.gvk_cb
|
||||
let li = bytes(VkDebugUtilsLabelEXT_sizeof)
|
||||
let li = gvk_tmp(render3d_st, VkDebugUtilsLabelEXT_sizeof)
|
||||
Vk.zero(li, VkDebugUtilsLabelEXT_sizeof)
|
||||
Vk.put_i32(li, VkDebugUtilsLabelEXT_sType, VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT)
|
||||
Vk.put_ptr(li, VkDebugUtilsLabelEXT_pLabelName, name)
|
||||
|
|
|
|||
|
|
@ -195,15 +195,16 @@ function gvk_dispatch_tex(render3d_st: mut Render3dState, c: int, params: pointe
|
|||
let nb = render3d_st.gvk_cp_nbuf[c]
|
||||
var nt = 0
|
||||
if render3d_st.gvk_cp_ntex != null and c < len(render3d_st.gvk_cp_ntex) { nt = render3d_st.gvk_cp_ntex[c] }
|
||||
let dsai = bytes(VkDescriptorSetAllocateInfo_sizeof)
|
||||
let dsai = gvk_tmp(render3d_st, VkDescriptorSetAllocateInfo_sizeof)
|
||||
Vk.zero(dsai, VkDescriptorSetAllocateInfo_sizeof)
|
||||
Vk.put_i32(dsai, VkDescriptorSetAllocateInfo_sType, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO)
|
||||
Vk.put_i64(dsai, VkDescriptorSetAllocateInfo_descriptorPool, render3d_st.gvk_dpool)
|
||||
Vk.put_i32(dsai, VkDescriptorSetAllocateInfo_descriptorSetCount, 1)
|
||||
let layouts = bytes(8)
|
||||
let layouts = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_i64(layouts, 0, render3d_st.gvk_cp_dsl[c])
|
||||
Vk.put_ptr(dsai, VkDescriptorSetAllocateInfo_pSetLayouts, layouts)
|
||||
let sets = bytes(8)
|
||||
let sets = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_set += 1
|
||||
let r = Vk.allocate_descriptor_sets(render3d_st.gvk_dev, dsai, sets)
|
||||
if r != VK_SUCCESS { gvk_fail(render3d_st, "vkAllocateDescriptorSets (compute)", r); return }
|
||||
let set = Vk.get_i64(sets, 0)
|
||||
|
|
@ -212,11 +213,11 @@ function gvk_dispatch_tex(render3d_st: mut Render3dState, c: int, params: pointe
|
|||
let ww = VkWriteDescriptorSet_sizeof
|
||||
let bw = VkDescriptorBufferInfo_sizeof
|
||||
let iw = VkDescriptorImageInfo_sizeof
|
||||
let writes = bytes(ww * (nb + 1 + nt))
|
||||
let writes = gvk_tmp(render3d_st, ww * (nb + 1 + nt))
|
||||
Vk.zero(writes, ww * (nb + 1 + nt))
|
||||
let infos = bytes(bw * (nb + 1))
|
||||
let infos = gvk_tmp(render3d_st, bw * (nb + 1))
|
||||
Vk.zero(infos, bw * (nb + 1))
|
||||
let iis = bytes(iw * (nt + 1))
|
||||
let iis = gvk_tmp(render3d_st, iw * (nt + 1))
|
||||
Vk.zero(iis, iw * (nt + 1))
|
||||
for k in 0 .. nb + 1 {
|
||||
var buf = render3d_st.gvk_ring_buf
|
||||
|
|
@ -254,7 +255,7 @@ function gvk_dispatch_tex(render3d_st: mut Render3dState, c: int, params: pointe
|
|||
Vk.cmd_bind_pipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, render3d_st.gvk_cp_pipe[c])
|
||||
Vk.cmd_bind_descriptor_sets(cb, VK_PIPELINE_BIND_POINT_COMPUTE, render3d_st.gvk_cp_layout[c], 0, 1, sets, 0, null)
|
||||
Vk.cmd_dispatch(cb, gx, gy, gz)
|
||||
let mb = bytes(VkMemoryBarrier_sizeof)
|
||||
let mb = gvk_tmp(render3d_st, VkMemoryBarrier_sizeof)
|
||||
Vk.zero(mb, VkMemoryBarrier_sizeof)
|
||||
Vk.put_i32(mb, VkMemoryBarrier_sType, VK_STRUCTURE_TYPE_MEMORY_BARRIER)
|
||||
Vk.put_i32(mb, VkMemoryBarrier_srcAccessMask, VK_ACCESS_SHADER_WRITE_BIT)
|
||||
|
|
@ -704,6 +705,7 @@ function gvk_frame_init(render3d_st: mut Render3dState) -> bool {
|
|||
let out = bytes(8)
|
||||
render3d_st.gvk_dpools = new []long
|
||||
for k in 0 .. 2 {
|
||||
render3d_st.gvk_mk_dpool += 1
|
||||
let r = Vk.create_descriptor_pool(render3d_st.gvk_dev, dpci, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateDescriptorPool", r) }
|
||||
push(render3d_st.gvk_dpools, gvk_handle(out))
|
||||
|
|
@ -717,6 +719,8 @@ function gvk_frame_init(render3d_st: mut Render3dState) -> bool {
|
|||
}
|
||||
|
||||
function gvk_frame_reset(render3d_st: mut Render3dState) -> void {
|
||||
# what MoltenVK autoreleased last frame goes back here (a no-op off macOS); R3D_VK_NOPOOL=1 for an A/B
|
||||
if not render3d_st.gvk_nopool { Vk.frame_pool() }
|
||||
let slot = render3d_st.gvk_frame_no & 1
|
||||
render3d_st.gvk_ring_off = slot * GVK_RING_BYTES
|
||||
render3d_st.gvk_ring_end = (slot + 1) * GVK_RING_BYTES
|
||||
|
|
@ -756,6 +760,7 @@ function gvk_kpool_make(render3d_st: mut Render3dState) -> bool {
|
|||
Vk.put_i32(dpci, VkDescriptorPoolCreateInfo_poolSizeCount, 2)
|
||||
Vk.put_ptr(dpci, VkDescriptorPoolCreateInfo_pPoolSizes, sizes)
|
||||
let out = bytes(8)
|
||||
render3d_st.gvk_mk_dpool += 1
|
||||
let r = Vk.create_descriptor_pool(render3d_st.gvk_dev, dpci, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateDescriptorPool (kept sets)", r) }
|
||||
render3d_st.gvk_kpool = gvk_handle(out)
|
||||
|
|
@ -764,8 +769,13 @@ function gvk_kpool_make(render3d_st: mut Render3dState) -> bool {
|
|||
}
|
||||
|
||||
function gvk_sc_clear(render3d_st: mut Render3dState) -> void {
|
||||
render3d_st.gvk_sc_prog = new []int; render3d_st.gvk_sc_koff = new []int; render3d_st.gvk_sc_set = new []long; render3d_st.gvk_sc_next = new []int
|
||||
render3d_st.gvk_sc_keys = new []long
|
||||
# emptied, not replaced: five fresh lists each time the pool filled were never given back
|
||||
if render3d_st.gvk_sc_prog == null {
|
||||
render3d_st.gvk_sc_prog = new []int; render3d_st.gvk_sc_koff = new []int; render3d_st.gvk_sc_set = new []long; render3d_st.gvk_sc_next = new []int
|
||||
render3d_st.gvk_sc_keys = new []long
|
||||
}
|
||||
List.clear(render3d_st.gvk_sc_prog); List.clear(render3d_st.gvk_sc_koff); List.clear(render3d_st.gvk_sc_set); List.clear(render3d_st.gvk_sc_next)
|
||||
List.clear(render3d_st.gvk_sc_keys)
|
||||
if render3d_st.gvk_sc_head == null { render3d_st.gvk_sc_head = words(4096) }
|
||||
for i in 0 .. 4096 { render3d_st.gvk_sc_head[i] = -1 }
|
||||
}
|
||||
|
|
@ -851,9 +861,9 @@ function gvk_draw_set(render3d_st: mut Render3dState, p: int, tx: words, tx_w: i
|
|||
function gvk_sc_make(render3d_st: mut Render3dState, p: int, nt: int) -> long {
|
||||
let zero: long = 0
|
||||
let v = render3d_st.gvk_prog_var[p]
|
||||
let dsai = bytes(VkDescriptorSetAllocateInfo_sizeof)
|
||||
let layouts = bytes(8)
|
||||
let sets = bytes(8)
|
||||
let dsai = gvk_tmp(render3d_st, VkDescriptorSetAllocateInfo_sizeof)
|
||||
let layouts = gvk_tmp(render3d_st, 8)
|
||||
let sets = gvk_tmp(render3d_st, 8)
|
||||
var r = 0
|
||||
var tries = 0
|
||||
while tries < 2 {
|
||||
|
|
@ -863,16 +873,17 @@ function gvk_sc_make(render3d_st: mut Render3dState, p: int, nt: int) -> long {
|
|||
Vk.put_i32(dsai, VkDescriptorSetAllocateInfo_descriptorSetCount, 1)
|
||||
Vk.put_i64(layouts, 0, render3d_st.gvk_prog_dsl[p])
|
||||
Vk.put_ptr(dsai, VkDescriptorSetAllocateInfo_pSetLayouts, layouts)
|
||||
render3d_st.gvk_mk_set += 1
|
||||
r = Vk.allocate_descriptor_sets(render3d_st.gvk_dev, dsai, sets)
|
||||
if r == VK_SUCCESS { tries = 2 } else { gvk_kpool_reset(render3d_st); tries += 1 }
|
||||
}
|
||||
if r != VK_SUCCESS { gvk_fail(render3d_st, "vkAllocateDescriptorSets (kept sets)", r); return zero }
|
||||
let set = Vk.get_i64(sets, 0)
|
||||
let ww = VkWriteDescriptorSet_sizeof
|
||||
let writes = bytes(ww * (nt + 3))
|
||||
let writes = gvk_tmp(render3d_st, ww * (nt + 3))
|
||||
Vk.zero(writes, ww * (nt + 3))
|
||||
let bis = bytes(VkDescriptorBufferInfo_sizeof * 2)
|
||||
let iis = bytes(VkDescriptorImageInfo_sizeof * (nt + 1))
|
||||
let bis = gvk_tmp(render3d_st, VkDescriptorBufferInfo_sizeof * 2)
|
||||
let iis = gvk_tmp(render3d_st, VkDescriptorImageInfo_sizeof * (nt + 1))
|
||||
var nw = 0
|
||||
var bi = 0
|
||||
for stage in 0 .. 2 {
|
||||
|
|
@ -1011,9 +1022,10 @@ function gvk_frame_cb(render3d_st: mut Render3dState) -> pointer {
|
|||
# on its own. Keyed by the image's generation, so a replaced image never reuses a stale view.
|
||||
function gvk_view_of(render3d_st: mut Render3dState, tex: int, layer1: int) -> long {
|
||||
if layer1 == 0 and render3d_st.gvk_tex_levels[tex] <= 1 and render3d_st.gvk_tex_layers[tex] <= 1 { return render3d_st.gvk_tex_view[tex] }
|
||||
let key = `{tex}:{render3d_st.gvk_tex_gen[tex]}:{layer1}`
|
||||
if render3d_st.gvk_layer_views == null { render3d_st.gvk_layer_views = new []string; render3d_st.gvk_layer_view = new []long }
|
||||
for i in 0 .. len(render3d_st.gvk_layer_views) { if render3d_st.gvk_layer_views[i] == key { return render3d_st.gvk_layer_view[i] } }
|
||||
# keyed by numbers, not a string built on every call: the texture, its generation and the layer
|
||||
let key = render3d_st.gvk_tex_gen[tex] * 4096 + layer1
|
||||
if render3d_st.gvk_layer_views == null { render3d_st.gvk_layer_views = new []int; render3d_st.gvk_layer_view = new []long; render3d_st.gvk_layer_view_tex = new []int }
|
||||
for i in 0 .. len(render3d_st.gvk_layer_views) { if render3d_st.gvk_layer_view_tex[i] == tex and render3d_st.gvk_layer_views[i] == key { return render3d_st.gvk_layer_view[i] } }
|
||||
let depth = render3d_st.gvk_tex_vkfmt[tex] == VK_FORMAT_D32_SFLOAT
|
||||
let vci = bytes(VkImageViewCreateInfo_sizeof)
|
||||
Vk.zero(vci, VkImageViewCreateInfo_sizeof)
|
||||
|
|
@ -1028,16 +1040,18 @@ function gvk_view_of(render3d_st: mut Render3dState, tex: int, layer1: int) -> l
|
|||
Vk.put_i32(vci, sr + VkImageSubresourceRange_layerCount, 1)
|
||||
let out = bytes(8)
|
||||
let zero: long = 0
|
||||
render3d_st.gvk_mk_view += 1
|
||||
if Vk.create_image_view(render3d_st.gvk_dev, vci, null, out) != VK_SUCCESS { return zero }
|
||||
push(render3d_st.gvk_layer_views, key)
|
||||
push(render3d_st.gvk_layer_view, gvk_handle(out))
|
||||
push(render3d_st.gvk_layer_view_tex, tex)
|
||||
return gvk_handle(out)
|
||||
}
|
||||
# the layer range a barrier for an attachment covers: the whole image unless one layer is drawn
|
||||
function gvk_att_barrier(render3d_st: Render3dState, cb: pointer, tex: int, layer1: int, depth: bool, old_layout: int, new_layout: int) -> void {
|
||||
function gvk_att_barrier(render3d_st: mut Render3dState, cb: pointer, tex: int, layer1: int, depth: bool, old_layout: int, new_layout: int) -> void {
|
||||
# an attachment whose image was never made (no memory for it) has nothing to transition
|
||||
if tex <= 0 or tex >= len(render3d_st.gvk_tex_image) or render3d_st.gvk_tex_image[tex] == 0 { return }
|
||||
let b = bytes(VkImageMemoryBarrier_sizeof)
|
||||
let b = gvk_tmp(render3d_st, VkImageMemoryBarrier_sizeof)
|
||||
Vk.zero(b, VkImageMemoryBarrier_sizeof)
|
||||
Vk.put_i32(b, VkImageMemoryBarrier_sType, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER)
|
||||
Vk.put_i32(b, VkImageMemoryBarrier_srcAccessMask, gvk_layout_access(old_layout))
|
||||
|
|
@ -1085,7 +1099,7 @@ function gvk_pass_begin(render3d_st: mut Render3dState, rec: words, rec_o: int)
|
|||
let cb = gvk_frame_cb(render3d_st)
|
||||
gvk_pass_collect(render3d_st, render3d_st.gvk_fb_cur, rec, rec_o)
|
||||
let aw = VkRenderingAttachmentInfo_sizeof
|
||||
let catt = bytes(aw * 3)
|
||||
let catt = gvk_tmp(render3d_st, aw * 3)
|
||||
Vk.zero(catt, aw * 3)
|
||||
render3d_st.gvk_pass_w = 0
|
||||
render3d_st.gvk_pass_h = 0
|
||||
|
|
@ -1108,7 +1122,7 @@ function gvk_pass_begin(render3d_st: mut Render3dState, rec: words, rec_o: int)
|
|||
render3d_st.gvk_pass_samples = gvk_tex_samples_of(render3d_st, tex)
|
||||
if render3d_st.gvk_pass_w == 0 { render3d_st.gvk_pass_w = gvk_tex_w(render3d_st, tex); render3d_st.gvk_pass_h = gvk_tex_h(render3d_st, tex) }
|
||||
}
|
||||
let datt = bytes(aw)
|
||||
let datt = gvk_tmp(render3d_st, aw)
|
||||
Vk.zero(datt, aw)
|
||||
let dtex = render3d_st.gvk_pass_dep[0]
|
||||
if dtex > 0 {
|
||||
|
|
@ -1126,7 +1140,7 @@ function gvk_pass_begin(render3d_st: mut Render3dState, rec: words, rec_o: int)
|
|||
if render3d_st.gvk_pass_w == 0 { render3d_st.gvk_pass_w = gvk_tex_w(render3d_st, dtex); render3d_st.gvk_pass_h = gvk_tex_h(render3d_st, dtex) }
|
||||
}
|
||||
render3d_st.gvk_clear_bits = 0
|
||||
let ri = bytes(VkRenderingInfo_sizeof)
|
||||
let ri = gvk_tmp(render3d_st, VkRenderingInfo_sizeof)
|
||||
Vk.zero(ri, VkRenderingInfo_sizeof)
|
||||
Vk.put_i32(ri, VkRenderingInfo_sType, VK_STRUCTURE_TYPE_RENDERING_INFO)
|
||||
Vk.put_i32(ri, VkRenderingInfo_renderArea + VkRect2D_extent + VkExtent2D_width, render3d_st.gvk_pass_w)
|
||||
|
|
@ -1157,7 +1171,7 @@ function gvk_clear(render3d_st: mut Render3dState, mask: int, rec: words, rec_o:
|
|||
if not render3d_st.gvk_in_pass { render3d_st.gvk_clear_bits = render3d_st.gvk_clear_bits | mask; return }
|
||||
let cb = render3d_st.gvk_cb
|
||||
let caw = VkClearAttachment_sizeof
|
||||
let atts = bytes(caw * 4)
|
||||
let atts = gvk_tmp(render3d_st, caw * 4)
|
||||
Vk.zero(atts, caw * 4)
|
||||
var n = 0
|
||||
if (mask & GL_COLOR_BUFFER_BIT) != 0 {
|
||||
|
|
@ -1174,7 +1188,7 @@ function gvk_clear(render3d_st: mut Render3dState, mask: int, rec: words, rec_o:
|
|||
n += 1
|
||||
}
|
||||
if n == 0 { return }
|
||||
let rect = bytes(VkClearRect_sizeof)
|
||||
let rect = gvk_tmp(render3d_st, VkClearRect_sizeof)
|
||||
Vk.zero(rect, VkClearRect_sizeof)
|
||||
Vk.put_i32(rect, VkClearRect_rect + VkRect2D_extent + VkExtent2D_width, render3d_st.gvk_pass_w)
|
||||
Vk.put_i32(rect, VkClearRect_rect + VkRect2D_extent + VkExtent2D_height, render3d_st.gvk_pass_h)
|
||||
|
|
@ -1187,8 +1201,8 @@ function gvk_tex_h(render3d_st: Render3dState, tex: int) -> int { return render3
|
|||
|
||||
# viewport and scissor for the draw; OpenGL's rows count from the bottom and so do a Vulkan
|
||||
# target's here (no y flip), so both pass straight through
|
||||
function gvk_set_view(render3d_st: Render3dState, cb: pointer) -> void {
|
||||
let vp = bytes(VkViewport_sizeof)
|
||||
function gvk_set_view(render3d_st: mut Render3dState, cb: pointer) -> void {
|
||||
let vp = gvk_tmp(render3d_st, VkViewport_sizeof)
|
||||
Vk.zero(vp, VkViewport_sizeof)
|
||||
Vk.put_i32(vp, VkViewport_x, float_bits(float(render3d_st.gvk_vp[0])))
|
||||
Vk.put_i32(vp, VkViewport_y, float_bits(float(render3d_st.gvk_vp[1])))
|
||||
|
|
@ -1196,7 +1210,7 @@ function gvk_set_view(render3d_st: Render3dState, cb: pointer) -> void {
|
|||
Vk.put_i32(vp, VkViewport_height, float_bits(float(render3d_st.gvk_vp[3])))
|
||||
Vk.put_i32(vp, VkViewport_maxDepth, 0x3F800000)
|
||||
Vk.cmd_set_viewport(cb, 0, 1, vp)
|
||||
let sc = bytes(VkRect2D_sizeof)
|
||||
let sc = gvk_tmp(render3d_st, VkRect2D_sizeof)
|
||||
Vk.zero(sc, VkRect2D_sizeof)
|
||||
if render3d_st.gvk_sc[0] == 1 {
|
||||
Vk.put_i32(sc, VkRect2D_offset + VkOffset2D_x, render3d_st.gvk_sc[1])
|
||||
|
|
@ -1238,16 +1252,17 @@ function gvk_draw(render3d_st: mut Render3dState, p: int, m: Mesh, st: GvkState,
|
|||
let set = gvk_draw_set(render3d_st, p, tx, tx_w, tx_cap)
|
||||
if set == 0 { return }
|
||||
if prof { render3d_st.gvk_us_set = render3d_st.gvk_us_set + (gl_now_us() - t1) }
|
||||
let sets = bytes(8)
|
||||
let sets = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_i64(sets, 0, set)
|
||||
Vk.cmd_bind_descriptor_sets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, render3d_st.gvk_prog_layout[p], 0, 1, sets, render3d_st.gvk_set_ndyn, render3d_st.gvk_set_offs)
|
||||
let v = render3d_st.gvk_prog_var[p]
|
||||
if m != null and m.attrs != null {
|
||||
# the same bindings, in the same order, as gvk_pipeline gave the layout
|
||||
let bufs = bytes(8 * (GPU_MAX_VBUFS + 1))
|
||||
let offs = bytes(8 * (GPU_MAX_VBUFS + 1))
|
||||
let bufs = gvk_tmp(render3d_st, 8 * (GPU_MAX_VBUFS + 1))
|
||||
let offs = gvk_tmp(render3d_st, 8 * (GPU_MAX_VBUFS + 1))
|
||||
Vk.zero(offs, 8 * (GPU_MAX_VBUFS + 1))
|
||||
let seen = words(GPU_MAX_VBUFS)
|
||||
if render3d_st.gvk_seen == null { render3d_st.gvk_seen = words(GPU_MAX_VBUFS) }
|
||||
let seen = render3d_st.gvk_seen
|
||||
var nbd = 0
|
||||
for i in 0 .. m.n_attrs {
|
||||
let o = i * GPU_ATTR_W
|
||||
|
|
@ -1510,7 +1525,7 @@ function gvk_resolve(render3d_st: mut Render3dState, cb: pointer, src: int, dst:
|
|||
gvk_att_barrier(render3d_st, cb, src, 0, depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, layout)
|
||||
gvk_att_barrier(render3d_st, cb, dst, 0, depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, layout)
|
||||
let aw = VkRenderingAttachmentInfo_sizeof
|
||||
let att = bytes(aw)
|
||||
let att = gvk_tmp(render3d_st, aw)
|
||||
Vk.zero(att, aw)
|
||||
Vk.put_i32(att, VkRenderingAttachmentInfo_sType, VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO)
|
||||
Vk.put_i64(att, VkRenderingAttachmentInfo_imageView, gvk_view_of(render3d_st, src, 0))
|
||||
|
|
@ -1520,7 +1535,7 @@ function gvk_resolve(render3d_st: mut Render3dState, cb: pointer, src: int, dst:
|
|||
Vk.put_i32(att, VkRenderingAttachmentInfo_resolveImageLayout, layout)
|
||||
Vk.put_i32(att, VkRenderingAttachmentInfo_loadOp, VK_ATTACHMENT_LOAD_OP_LOAD)
|
||||
Vk.put_i32(att, VkRenderingAttachmentInfo_storeOp, VK_ATTACHMENT_STORE_OP_STORE)
|
||||
let ri = bytes(VkRenderingInfo_sizeof)
|
||||
let ri = gvk_tmp(render3d_st, VkRenderingInfo_sizeof)
|
||||
Vk.zero(ri, VkRenderingInfo_sizeof)
|
||||
Vk.put_i32(ri, VkRenderingInfo_sType, VK_STRUCTURE_TYPE_RENDERING_INFO)
|
||||
Vk.put_i32(ri, VkRenderingInfo_renderArea + VkRect2D_extent + VkExtent2D_width, w)
|
||||
|
|
@ -1536,9 +1551,9 @@ function gvk_resolve(render3d_st: mut Render3dState, cb: pointer, src: int, dst:
|
|||
function gvk_copy(render3d_st: mut Render3dState, cb: pointer, src: int, dst: int, depth: bool, w: int, h: int) -> void {
|
||||
if src <= 0 or dst <= 0 or render3d_st.gvk_tex_image[src] == 0 or render3d_st.gvk_tex_image[dst] == 0 { return }
|
||||
if gvk_tex_samples_of(render3d_st, src) > 1 and gvk_tex_samples_of(render3d_st, dst) == 1 { gvk_resolve(render3d_st, cb, src, dst, depth, w, h); return }
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[src], depth, 0, 1, render3d_st.gvk_tex_layers[src], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[dst], depth, 0, 1, render3d_st.gvk_tex_layers[dst], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let ic = bytes(VkImageCopy_sizeof)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[src], depth, 0, 1, render3d_st.gvk_tex_layers[src], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[dst], depth, 0, 1, render3d_st.gvk_tex_layers[dst], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let ic = gvk_tmp(render3d_st, VkImageCopy_sizeof)
|
||||
Vk.zero(ic, VkImageCopy_sizeof)
|
||||
var aspect = VK_IMAGE_ASPECT_COLOR_BIT
|
||||
if depth { aspect = VK_IMAGE_ASPECT_DEPTH_BIT }
|
||||
|
|
@ -1550,8 +1565,8 @@ function gvk_copy(render3d_st: mut Render3dState, cb: pointer, src: int, dst: in
|
|||
Vk.put_i32(ic, VkImageCopy_extent + VkExtent3D_height, h)
|
||||
Vk.put_i32(ic, VkImageCopy_extent + VkExtent3D_depth, 1)
|
||||
Vk.cmd_copy_image(cb, render3d_st.gvk_tex_image[src], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, render3d_st.gvk_tex_image[dst], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ic)
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[src], depth, 0, 1, render3d_st.gvk_tex_layers[src], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[dst], depth, 0, 1, render3d_st.gvk_tex_layers[dst], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[src], depth, 0, 1, render3d_st.gvk_tex_layers[src], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[dst], depth, 0, 1, render3d_st.gvk_tex_layers[dst], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
}
|
||||
function gvk_blit(render3d_st: mut Render3dState, w: int, h: int, mask: int) -> void {
|
||||
gvk_pass_end(render3d_st)
|
||||
|
|
@ -1725,20 +1740,20 @@ function gvk_present_window(render3d_st: mut Render3dState) -> void {
|
|||
Time.sleep_us(16000)
|
||||
return
|
||||
}
|
||||
if render3d_st.gvk_swap_stale { gvk_once_end(render3d_st, cb); render3d_st.gvk_cb = null; gvk_swap_make(render3d_st, render3d_st.gvk_swap_w, render3d_st.gvk_swap_h); return }
|
||||
let idx = bytes(4)
|
||||
if render3d_st.gvk_swap_stale { gvk_once_end(render3d_st, cb); render3d_st.gvk_cb = null; print("r3d: vulkan: swapchain remade at acquire"); gvk_swap_make(render3d_st, render3d_st.gvk_swap_w, render3d_st.gvk_swap_h); return }
|
||||
let idx = gvk_tmp(render3d_st, 4)
|
||||
Vk.reset_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_acq_fence)
|
||||
let forever: long = -1
|
||||
let zero: long = 0
|
||||
var r = Vk.acquire_next_image_khr(render3d_st.gvk_dev, render3d_st.gvk_swap, forever, zero, Vk.get_i64(render3d_st.gvk_acq_fence, 0), idx)
|
||||
if r == VK_ERROR_OUT_OF_DATE_KHR { gvk_once_end(render3d_st, cb); render3d_st.gvk_cb = null; gvk_swap_make(render3d_st, render3d_st.gvk_swap_w, render3d_st.gvk_swap_h); return }
|
||||
if r == VK_ERROR_OUT_OF_DATE_KHR { gvk_once_end(render3d_st, cb); render3d_st.gvk_cb = null; print("r3d: vulkan: swapchain remade at acquire"); gvk_swap_make(render3d_st, render3d_st.gvk_swap_w, render3d_st.gvk_swap_h); return }
|
||||
Vk.wait_for_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_acq_fence, 1, forever)
|
||||
let i = Vk.get_i32(idx, 0)
|
||||
let dst = Vk.get_i64(render3d_st.gvk_swap_images, i * 8)
|
||||
let src = render3d_st.gvk_tex_image[render3d_st.gvk_screen_color]
|
||||
gvk_barrier(cb, src, false, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(cb, dst, false, 0, 1, 1, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let blit = bytes(VkImageBlit_sizeof)
|
||||
gvk_barrier(render3d_st, cb, src, false, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, dst, false, 0, 1, 1, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let blit = gvk_tmp(render3d_st, VkImageBlit_sizeof)
|
||||
Vk.zero(blit, VkImageBlit_sizeof)
|
||||
Vk.put_i32(blit, VkImageBlit_srcSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT)
|
||||
Vk.put_i32(blit, VkImageBlit_srcSubresource + VkImageSubresourceLayers_layerCount, 1)
|
||||
|
|
@ -1752,14 +1767,14 @@ function gvk_present_window(render3d_st: mut Render3dState) -> void {
|
|||
Vk.put_i32(blit, VkImageBlit_dstOffsets + VkOffset3D_sizeof + VkOffset3D_y, render3d_st.gvk_swap_h)
|
||||
Vk.put_i32(blit, VkImageBlit_dstOffsets + VkOffset3D_sizeof + VkOffset3D_z, 1)
|
||||
Vk.cmd_blit_image(cb, src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blit, VK_FILTER_LINEAR)
|
||||
gvk_barrier(cb, src, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(cb, dst, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
|
||||
gvk_barrier(render3d_st, cb, src, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, dst, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
|
||||
if gvk_inflight_on(render3d_st) { gvk_frame_submit(render3d_st, cb) } else { gvk_once_end(render3d_st, cb) }
|
||||
render3d_st.gvk_cb = null
|
||||
let pi = bytes(VkPresentInfoKHR_sizeof)
|
||||
let pi = gvk_tmp(render3d_st, VkPresentInfoKHR_sizeof)
|
||||
Vk.zero(pi, VkPresentInfoKHR_sizeof)
|
||||
Vk.put_i32(pi, VkPresentInfoKHR_sType, VK_STRUCTURE_TYPE_PRESENT_INFO_KHR)
|
||||
let chains = bytes(8)
|
||||
let chains = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_i64(chains, 0, render3d_st.gvk_swap)
|
||||
Vk.put_i32(pi, VkPresentInfoKHR_swapchainCount, 1)
|
||||
Vk.put_ptr(pi, VkPresentInfoKHR_pSwapchains, chains)
|
||||
|
|
@ -1779,6 +1794,8 @@ function gvk_resize_check(render3d_st: mut Render3dState) -> bool {
|
|||
let h = render3d_st.gvk_size_buf[1]
|
||||
if w <= 0 or h <= 0 { return false }
|
||||
if w == gl_width() and h == gl_height() and not render3d_st.gvk_swap_stale { return false }
|
||||
# a remake is rare (a resize); said, so one on every frame shows in the log
|
||||
print(`r3d: vulkan: swapchain remade {w}x{h} (was {gl_width()}x{gl_height()}, stale {render3d_st.gvk_swap_stale})`)
|
||||
gvk_flush(render3d_st)
|
||||
gl_set_drawable(w, h)
|
||||
gvk_screen_make(render3d_st, w, h)
|
||||
|
|
@ -1816,11 +1833,26 @@ function gvk_prof_frame(render3d_st: mut Render3dState) -> void {
|
|||
if render3d_st.gvk_n_asked != render3d_st.gvk_n_draws {
|
||||
print(`r3d: vulkan: {(render3d_st.gvk_n_asked - render3d_st.gvk_n_draws) / f} draws a frame were asked for and not made ({render3d_st.gvk_n_asked / f} asked, {render3d_st.gvk_n_draws / f} made)`)
|
||||
}
|
||||
gvk_prof_made(render3d_st)
|
||||
let zero: long = 0
|
||||
render3d_st.gvk_us_pipe = zero; render3d_st.gvk_us_set = zero; render3d_st.gvk_us_draw = zero
|
||||
render3d_st.gvk_n_draws = 0; render3d_st.gvk_n_asked = 0; render3d_st.gvk_n_flush = 0; render3d_st.gvk_prof_frames = 0; render3d_st.gvk_n_pipe_new = 0
|
||||
}
|
||||
|
||||
# what was made and destroyed over those frames, and how long the caches are: a kind made every
|
||||
# frame and never destroyed, or a cache that only grows, is a leak
|
||||
function gvk_prof_made(render3d_st: mut Render3dState) -> void {
|
||||
let r = render3d_st
|
||||
print(`r3d: vulkan made/destroyed: images {r.gvk_mk_img}/{r.gvk_mk_x_img}, views {r.gvk_mk_view}/{r.gvk_mk_x_view}, buffers {r.gvk_mk_buf}/{r.gvk_mk_x_buf}, memory {r.gvk_mk_mem}/{r.gvk_mk_x_mem}, samplers {r.gvk_mk_smp}, sets {r.gvk_mk_set}, pools {r.gvk_mk_dpool}, cmds {r.gvk_mk_cmd}`)
|
||||
print(`r3d: vulkan caches: layer views {gvk_len_i(r.gvk_layer_views)}, pipelines {gvk_len_s(r.gvk_pipe_keys)}, layouts {gvk_len_s(r.gvk_layout_keys)}, pipe cache {gvk_len_i(r.gvk_pc_prog)}, retired {gvk_len_l(r.gvk_retired_buf)}, buffers {gvk_len_l(r.gvk_buf)}, allocs {r.gvk_n_allocs}`)
|
||||
render3d_st.gvk_mk_img = 0; render3d_st.gvk_mk_x_img = 0; render3d_st.gvk_mk_view = 0; render3d_st.gvk_mk_x_view = 0
|
||||
render3d_st.gvk_mk_buf = 0; render3d_st.gvk_mk_x_buf = 0; render3d_st.gvk_mk_mem = 0; render3d_st.gvk_mk_x_mem = 0
|
||||
render3d_st.gvk_mk_smp = 0; render3d_st.gvk_mk_set = 0; render3d_st.gvk_mk_dpool = 0; render3d_st.gvk_mk_cmd = 0
|
||||
}
|
||||
function gvk_len_s(xs: []string) -> int { if xs == null { return 0 }; return len(xs) }
|
||||
function gvk_len_i(xs: []int) -> int { if xs == null { return 0 }; return len(xs) }
|
||||
function gvk_len_l(xs: []long) -> int { if xs == null { return 0 }; return len(xs) }
|
||||
|
||||
# ---- the pipeline cache, by integers -----------------------------------------------------------
|
||||
# gvk_pipeline builds and keys pipelines by a string; a draw only needs to find one it already has.
|
||||
# A mesh carries an interned layout id (its recorded layout, buffers named by order), the render
|
||||
|
|
|
|||
|
|
@ -88,10 +88,10 @@ function gvk_layout_access(layout: int) -> int {
|
|||
return 0
|
||||
}
|
||||
# levels [base, base + n) of every layer of an image, from one layout to another
|
||||
function gvk_barrier(cb: pointer, image: long, depth: bool, base: int, n: int, layers: int, old_layout: int, new_layout: int) -> void {
|
||||
function gvk_barrier(render3d_st: mut Render3dState, cb: pointer, image: long, depth: bool, base: int, n: int, layers: int, old_layout: int, new_layout: int) -> void {
|
||||
# an image that was never made (its memory could not be had) has nothing to transition
|
||||
if image == 0 { return }
|
||||
let b = bytes(VkImageMemoryBarrier_sizeof)
|
||||
let b = gvk_tmp(render3d_st, VkImageMemoryBarrier_sizeof)
|
||||
Vk.zero(b, VkImageMemoryBarrier_sizeof)
|
||||
Vk.put_i32(b, VkImageMemoryBarrier_sType, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER)
|
||||
Vk.put_i32(b, VkImageMemoryBarrier_srcAccessMask, gvk_layout_access(old_layout))
|
||||
|
|
@ -115,28 +115,31 @@ function gvk_barrier(cb: pointer, image: long, depth: bool, base: int, n: int, l
|
|||
# a host-visible buffer of n bytes, mapped; gvk_staging_free unmaps and frees it
|
||||
function gvk_staging(render3d_st: mut Render3dState, n: int, usage: int) -> pointer {
|
||||
let size: long = n
|
||||
let bci = bytes(VkBufferCreateInfo_sizeof)
|
||||
let bci = gvk_tmp(render3d_st, VkBufferCreateInfo_sizeof)
|
||||
Vk.zero(bci, VkBufferCreateInfo_sizeof)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_sType, VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO)
|
||||
Vk.put_i64(bci, VkBufferCreateInfo_size, size)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_usage, usage)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_sharingMode, VK_SHARING_MODE_EXCLUSIVE)
|
||||
let out = bytes(8)
|
||||
let out = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_buf += 1
|
||||
if Vk.create_buffer(render3d_st.gvk_dev, bci, null, out) != VK_SUCCESS { return null }
|
||||
render3d_st.gvk_st_buf = gvk_handle(out)
|
||||
let req = bytes(VkMemoryRequirements_sizeof)
|
||||
let req = gvk_tmp(render3d_st, VkMemoryRequirements_sizeof)
|
||||
Vk.get_buffer_memory_requirements(render3d_st.gvk_dev, render3d_st.gvk_st_buf, req)
|
||||
render3d_st.gvk_st_mem = gvk_alloc(render3d_st, req, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
|
||||
let zero: long = 0
|
||||
Vk.bind_buffer_memory(render3d_st.gvk_dev, render3d_st.gvk_st_buf, render3d_st.gvk_st_mem, zero)
|
||||
let pp = bytes(8)
|
||||
let pp = gvk_tmp(render3d_st, 8)
|
||||
if Vk.map_memory(render3d_st.gvk_dev, render3d_st.gvk_st_mem, zero, size, 0, pp) != VK_SUCCESS { return null }
|
||||
return Vk.get_ptr(pp, 0)
|
||||
}
|
||||
function gvk_staging_free(render3d_st: mut Render3dState) -> void {
|
||||
gvk_frame_wait(render3d_st) # the frame in flight may still read it
|
||||
Vk.unmap_memory(render3d_st.gvk_dev, render3d_st.gvk_st_mem)
|
||||
render3d_st.gvk_mk_x_buf += 1
|
||||
Vk.destroy_buffer(render3d_st.gvk_dev, render3d_st.gvk_st_buf, null)
|
||||
render3d_st.gvk_mk_x_mem += 1
|
||||
Vk.free_memory(render3d_st.gvk_dev, render3d_st.gvk_st_mem, null)
|
||||
render3d_st.gvk_n_allocs -= 1
|
||||
}
|
||||
|
|
@ -175,6 +178,7 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool,
|
|||
Vk.put_i32(ici, VkImageCreateInfo_sharingMode, VK_SHARING_MODE_EXCLUSIVE)
|
||||
Vk.put_i32(ici, VkImageCreateInfo_initialLayout, VK_IMAGE_LAYOUT_UNDEFINED)
|
||||
let out = bytes(8)
|
||||
render3d_st.gvk_mk_img += 1
|
||||
var r = Vk.create_image(render3d_st.gvk_dev, ici, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, `vkCreateImage {w}x{h}x{layers} format {vkfmt}`, r) }
|
||||
let image = gvk_handle(out)
|
||||
|
|
@ -186,6 +190,7 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool,
|
|||
# no memory for it (one allocation per resource meets the driver's allocation limit long before
|
||||
# the card is full): say so instead of binding a null allocation, which the driver may accept
|
||||
if mem == 0 {
|
||||
render3d_st.gvk_mk_x_img += 1
|
||||
Vk.destroy_image(render3d_st.gvk_dev, image, null)
|
||||
return gvk_fail(render3d_st, `no device memory for a {w}x{h}x{layers} image ({render3d_st.gvk_n_allocs} allocations live)`, VK_ERROR_OUT_OF_DEVICE_MEMORY)
|
||||
}
|
||||
|
|
@ -201,6 +206,7 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool,
|
|||
if depth { Vk.put_i32(vci, sr + VkImageSubresourceRange_aspectMask, VK_IMAGE_ASPECT_DEPTH_BIT) } else { Vk.put_i32(vci, sr + VkImageSubresourceRange_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT) }
|
||||
Vk.put_i32(vci, sr + VkImageSubresourceRange_levelCount, levels)
|
||||
Vk.put_i32(vci, sr + VkImageSubresourceRange_layerCount, layers)
|
||||
render3d_st.gvk_mk_view += 1
|
||||
r = Vk.create_image_view(render3d_st.gvk_dev, vci, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateImageView", r) }
|
||||
render3d_st.gvk_tex_image[tex] = image; render3d_st.gvk_tex_view[tex] = gvk_handle(out); render3d_st.gvk_tex_mem[tex] = mem
|
||||
|
|
@ -211,7 +217,7 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool,
|
|||
if array { render3d_st.gvk_tex_array[tex] = 1 } else { render3d_st.gvk_tex_array[tex] = 0 }
|
||||
# a target starts cleared-to-nothing but readable: every level in the layout samplers expect
|
||||
let cb = gvk_once_begin(render3d_st)
|
||||
gvk_barrier(cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
return gvk_once_end(render3d_st, cb)
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +254,8 @@ function gvk_tex_upload(render3d_st: mut Render3dState, tex: int, ifmt: int, w:
|
|||
let n = texels * cout * bout
|
||||
let dst = gvk_staging(render3d_st, n, VK_BUFFER_USAGE_TRANSFER_SRC_BIT)
|
||||
if dst == null { print("r3d: vulkan: no staging buffer for an upload"); return false }
|
||||
let buf = bytes(n)
|
||||
# written straight into the mapped staging memory: a copy of its own was never freed
|
||||
let buf: pointer = dst
|
||||
if cin == cout and bin == bout and not (bin == 2 and render3d_st.gvk_unpack_swap) {
|
||||
mem_copy(buf, data, n)
|
||||
} else {
|
||||
|
|
@ -281,13 +288,12 @@ function gvk_tex_upload(render3d_st: mut Render3dState, tex: int, ifmt: int, w:
|
|||
}
|
||||
}
|
||||
}
|
||||
mem_copy(dst, buf, n)
|
||||
let image = render3d_st.gvk_tex_image[tex]
|
||||
let depth = gvk_is_depth(ifmt)
|
||||
let levels = render3d_st.gvk_tex_levels[tex]
|
||||
let cb = gvk_once_begin(render3d_st)
|
||||
gvk_barrier(cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let bic = bytes(VkBufferImageCopy_sizeof)
|
||||
gvk_barrier(render3d_st, cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let bic = gvk_tmp(render3d_st, VkBufferImageCopy_sizeof)
|
||||
Vk.zero(bic, VkBufferImageCopy_sizeof)
|
||||
if depth { Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_DEPTH_BIT) }
|
||||
else { Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT) }
|
||||
|
|
@ -296,7 +302,7 @@ function gvk_tex_upload(render3d_st: mut Render3dState, tex: int, ifmt: int, w:
|
|||
Vk.put_i32(bic, VkBufferImageCopy_imageExtent + VkExtent3D_height, h)
|
||||
Vk.put_i32(bic, VkBufferImageCopy_imageExtent + VkExtent3D_depth, 1)
|
||||
Vk.cmd_copy_buffer_to_image(cb, render3d_st.gvk_st_buf, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, bic)
|
||||
gvk_barrier(cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
let ok = gvk_once_end(render3d_st, cb)
|
||||
gvk_staging_free(render3d_st)
|
||||
return ok
|
||||
|
|
@ -315,8 +321,8 @@ function gvk_tex_grow_mips(render3d_st: mut Render3dState, tex: int, w: int, h:
|
|||
render3d_st.gvk_tex_image[tex] = zero
|
||||
if not gvk_tex_storage(render3d_st, tex, render3d_st.gvk_tex_array[tex] == 1, render3d_st.gvk_tex_glfmt[tex], w, h, layers, true) { return false }
|
||||
let cb = gvk_once_begin(render3d_st)
|
||||
gvk_barrier(cb, old_image, false, 0, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[tex], false, 0, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, old_image, false, 0, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[tex], false, 0, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let ic = bytes(VkImageCopy_sizeof)
|
||||
Vk.zero(ic, VkImageCopy_sizeof)
|
||||
Vk.put_i32(ic, VkImageCopy_srcSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT)
|
||||
|
|
@ -327,9 +333,11 @@ function gvk_tex_grow_mips(render3d_st: mut Render3dState, tex: int, w: int, h:
|
|||
Vk.put_i32(ic, VkImageCopy_extent + VkExtent3D_height, h)
|
||||
Vk.put_i32(ic, VkImageCopy_extent + VkExtent3D_depth, 1)
|
||||
Vk.cmd_copy_image(cb, old_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, render3d_st.gvk_tex_image[tex], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ic)
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[tex], false, 0, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[tex], false, 0, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
let ok = gvk_once_end(render3d_st, cb)
|
||||
render3d_st.gvk_mk_x_view += 1
|
||||
Vk.destroy_image_view(render3d_st.gvk_dev, old_view, null)
|
||||
render3d_st.gvk_mk_x_img += 1
|
||||
Vk.destroy_image(render3d_st.gvk_dev, old_image, null)
|
||||
gvk_mem_free(render3d_st, gvk_mem_id(old_mem))
|
||||
return ok
|
||||
|
|
@ -346,7 +354,7 @@ function gvk_tex_mips(render3d_st: mut Render3dState, tex: int, w: int, h: int)
|
|||
return gvk_once_end(render3d_st, cb)
|
||||
}
|
||||
# the chain's blits and barriers recorded into cb (the frame's own, when one is open)
|
||||
function gvk_tex_mips_into(render3d_st: Render3dState, cb: pointer, tex: int, w: int, h: int) -> void {
|
||||
function gvk_tex_mips_into(render3d_st: mut Render3dState, cb: pointer, tex: int, w: int, h: int) -> void {
|
||||
let levels = render3d_st.gvk_tex_levels[tex]
|
||||
if levels <= 1 { return }
|
||||
let image = render3d_st.gvk_tex_image[tex]
|
||||
|
|
@ -358,8 +366,8 @@ function gvk_tex_mips_into(render3d_st: Render3dState, cb: pointer, tex: int, w:
|
|||
if dw < 1 { dw = 1 }
|
||||
var dh = sh / 2
|
||||
if dh < 1 { dh = 1 }
|
||||
gvk_barrier(cb, image, false, lv - 1, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(cb, image, false, lv, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, false, lv - 1, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, false, lv, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let blit = bytes(VkImageBlit_sizeof)
|
||||
Vk.zero(blit, VkImageBlit_sizeof)
|
||||
Vk.put_i32(blit, VkImageBlit_srcSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT)
|
||||
|
|
@ -375,8 +383,8 @@ function gvk_tex_mips_into(render3d_st: Render3dState, cb: pointer, tex: int, w:
|
|||
Vk.put_i32(blit, VkImageBlit_dstOffsets + VkOffset3D_sizeof + VkOffset3D_y, dh)
|
||||
Vk.put_i32(blit, VkImageBlit_dstOffsets + VkOffset3D_sizeof + VkOffset3D_z, 1)
|
||||
Vk.cmd_blit_image(cb, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blit, VK_FILTER_LINEAR)
|
||||
gvk_barrier(cb, image, false, lv - 1, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(cb, image, false, lv, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, false, lv - 1, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, false, lv, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
sw = dw
|
||||
sh = dh
|
||||
}
|
||||
|
|
@ -400,7 +408,7 @@ function gvk_tex_read(render3d_st: mut Render3dState, tex: int, ifmt: int, w: in
|
|||
let image = render3d_st.gvk_tex_image[tex]
|
||||
let depth = gvk_is_depth(ifmt)
|
||||
let cb = gvk_once_begin(render3d_st)
|
||||
gvk_barrier(cb, image, depth, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, depth, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
let bic = bytes(VkBufferImageCopy_sizeof)
|
||||
Vk.zero(bic, VkBufferImageCopy_sizeof)
|
||||
if depth { Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_DEPTH_BIT) }
|
||||
|
|
@ -410,7 +418,7 @@ function gvk_tex_read(render3d_st: mut Render3dState, tex: int, ifmt: int, w: in
|
|||
Vk.put_i32(bic, VkBufferImageCopy_imageExtent + VkExtent3D_height, h)
|
||||
Vk.put_i32(bic, VkBufferImageCopy_imageExtent + VkExtent3D_depth, 1)
|
||||
Vk.cmd_copy_image_to_buffer(cb, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, render3d_st.gvk_st_buf, 1, bic)
|
||||
gvk_barrier(cb, image, depth, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, depth, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
let ok = gvk_once_end(render3d_st, cb)
|
||||
if ok and cwant == cout { mem_copy(out, src, n) }
|
||||
if ok and cwant < cout {
|
||||
|
|
@ -426,14 +434,39 @@ function gvk_tex_release(render3d_st: mut Render3dState, tex: int) -> void {
|
|||
gvk_frame_wait(render3d_st) # the frame in flight may still read it
|
||||
if render3d_st.gvk_tex_image[tex] == 0 { return }
|
||||
let zero: long = 0
|
||||
render3d_st.gvk_mk_x_view += 1
|
||||
Vk.destroy_image_view(render3d_st.gvk_dev, render3d_st.gvk_tex_view[tex], null)
|
||||
render3d_st.gvk_mk_x_img += 1
|
||||
Vk.destroy_image(render3d_st.gvk_dev, render3d_st.gvk_tex_image[tex], null)
|
||||
gvk_mem_free(render3d_st, gvk_mem_id(render3d_st.gvk_tex_mem[tex]))
|
||||
gvk_layer_views_drop(render3d_st, tex)
|
||||
render3d_st.gvk_tex_image[tex] = zero; render3d_st.gvk_tex_view[tex] = zero; render3d_st.gvk_tex_mem[tex] = zero
|
||||
render3d_st.gvk_tex_levels[tex] = 0; render3d_st.gvk_tex_layers[tex] = 0; render3d_st.gvk_tex_vkfmt[tex] = 0
|
||||
render3d_st.gvk_tex_gen[tex] = render3d_st.gvk_tex_gen[tex] + 1
|
||||
}
|
||||
|
||||
# the per-level and per-layer views gvk_view_of made of a texture go with it: kept, each held its
|
||||
# old Metal texture alive, so every rebuild of the screen targets left the last set's memory behind
|
||||
function gvk_layer_views_drop(render3d_st: mut Render3dState, tex: int) -> void {
|
||||
if render3d_st.gvk_layer_views == null { return }
|
||||
let keys = render3d_st.gvk_layer_views
|
||||
let views = render3d_st.gvk_layer_view
|
||||
let texs = render3d_st.gvk_layer_view_tex
|
||||
var w = 0
|
||||
for i in 0 .. len(keys) {
|
||||
if texs[i] == tex {
|
||||
render3d_st.gvk_mk_x_view += 1
|
||||
Vk.destroy_image_view(render3d_st.gvk_dev, views[i], null)
|
||||
} else {
|
||||
keys[w] = keys[i]; views[w] = views[i]; texs[w] = texs[i]
|
||||
w += 1
|
||||
}
|
||||
}
|
||||
while len(keys) > w { List.pop(keys) }
|
||||
while len(views) > w { List.pop(views) }
|
||||
while len(texs) > w { List.pop(texs) }
|
||||
}
|
||||
|
||||
# ---- samplers -----------------------------------------------------------------------------
|
||||
# One VkSampler per distinct way of reading a texture, made the first time it is asked for.
|
||||
# The arguments are what the renderer set through gpu_tex_param (0 where it set nothing, which
|
||||
|
|
@ -526,6 +559,7 @@ function gvk_sampler(render3d_st: mut Render3dState, min_f: int, mag_f: int, wra
|
|||
Vk.put_i32(sci, VkSamplerCreateInfo_borderColor, VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE)
|
||||
let out = bytes(8)
|
||||
let zero: long = 0
|
||||
render3d_st.gvk_mk_smp += 1
|
||||
let r = Vk.create_sampler(render3d_st.gvk_dev, sci, null, out)
|
||||
if r != VK_SUCCESS { gvk_fail(render3d_st, "vkCreateSampler", r); return zero }
|
||||
let s = gvk_handle(out)
|
||||
|
|
@ -564,6 +598,7 @@ function gvk_buf_release(render3d_st: mut Render3dState, b: int) -> void {
|
|||
while len(render3d_st.gvk_retired_frame) < len(render3d_st.gvk_retired_buf) - 1 { push(render3d_st.gvk_retired_frame, 0) }
|
||||
push(render3d_st.gvk_retired_frame, render3d_st.gvk_buf_used[b])
|
||||
} else {
|
||||
render3d_st.gvk_mk_x_buf += 1
|
||||
Vk.destroy_buffer(render3d_st.gvk_dev, render3d_st.gvk_buf[b], null)
|
||||
gvk_mem_free(render3d_st, gvk_mem_id(render3d_st.gvk_buf_mem[b]))
|
||||
}
|
||||
|
|
@ -582,22 +617,27 @@ function gvk_retire_flush(render3d_st: mut Render3dState) -> void { gvk_retire_u
|
|||
# the frame being recorded read stays
|
||||
function gvk_retire_upto(render3d_st: mut Render3dState, done: int) -> void {
|
||||
if render3d_st.gvk_retired_buf == null { return }
|
||||
let keep_buf = new []long
|
||||
let keep_mem = new []long
|
||||
let keep_frame = new []int
|
||||
if render3d_st.gvk_retired_frame == null { render3d_st.gvk_retired_frame = new []int }
|
||||
while len(render3d_st.gvk_retired_frame) < len(render3d_st.gvk_retired_buf) { push(render3d_st.gvk_retired_frame, 0) }
|
||||
# compacted in place: three fresh lists a frame were never given back
|
||||
var w = 0
|
||||
for i in 0 .. len(render3d_st.gvk_retired_buf) {
|
||||
var f = 0
|
||||
if render3d_st.gvk_retired_frame != null and i < len(render3d_st.gvk_retired_frame) { f = render3d_st.gvk_retired_frame[i] }
|
||||
let f = render3d_st.gvk_retired_frame[i]
|
||||
if f > done {
|
||||
push(keep_buf, render3d_st.gvk_retired_buf[i]); push(keep_mem, render3d_st.gvk_retired_mem[i]); push(keep_frame, f)
|
||||
render3d_st.gvk_retired_buf[w] = render3d_st.gvk_retired_buf[i]; render3d_st.gvk_retired_mem[w] = render3d_st.gvk_retired_mem[i]; render3d_st.gvk_retired_frame[w] = f
|
||||
w += 1
|
||||
} else {
|
||||
render3d_st.gvk_mk_x_buf += 1
|
||||
Vk.destroy_buffer(render3d_st.gvk_dev, render3d_st.gvk_retired_buf[i], null)
|
||||
gvk_mem_free(render3d_st, gvk_mem_id(render3d_st.gvk_retired_mem[i]))
|
||||
}
|
||||
}
|
||||
render3d_st.gvk_retired_buf = keep_buf
|
||||
render3d_st.gvk_retired_mem = keep_mem
|
||||
render3d_st.gvk_retired_frame = keep_frame
|
||||
let rb = render3d_st.gvk_retired_buf
|
||||
let rm = render3d_st.gvk_retired_mem
|
||||
let rf = render3d_st.gvk_retired_frame
|
||||
while len(rb) > w { List.pop(rb) }
|
||||
while len(rm) > w { List.pop(rm) }
|
||||
while len(rf) > w { List.pop(rf) }
|
||||
}
|
||||
|
||||
# A buffer a compute pass writes: a draw later in the same frame reads what the GPU put there,
|
||||
|
|
@ -619,17 +659,18 @@ function gvk_buf_reserve(render3d_st: mut Render3dState, b: int, n: int) -> bool
|
|||
var size = n
|
||||
if size < 64 { size = 64 }
|
||||
let size_l: long = size
|
||||
let bci = bytes(VkBufferCreateInfo_sizeof)
|
||||
let bci = gvk_tmp(render3d_st, VkBufferCreateInfo_sizeof)
|
||||
Vk.zero(bci, VkBufferCreateInfo_sizeof)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_sType, VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO)
|
||||
Vk.put_i64(bci, VkBufferCreateInfo_size, size_l)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_usage, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_sharingMode, VK_SHARING_MODE_EXCLUSIVE)
|
||||
let out = bytes(8)
|
||||
let out = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_buf += 1
|
||||
var r = Vk.create_buffer(render3d_st.gvk_dev, bci, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, `vkCreateBuffer ({size} bytes)`, r) }
|
||||
let buf = gvk_handle(out)
|
||||
let req = bytes(VkMemoryRequirements_sizeof)
|
||||
let req = gvk_tmp(render3d_st, VkMemoryRequirements_sizeof)
|
||||
Vk.get_buffer_memory_requirements(render3d_st.gvk_dev, buf, req)
|
||||
let ma = gvk_mem_new(render3d_st, req, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, false)
|
||||
let zero: long = 0
|
||||
|
|
|
|||
|
|
@ -102,9 +102,14 @@ function gg_cull_frame(render3d_st: mut Render3dState) -> void {
|
|||
gg_tiles(render3d_st, 4, 0.0, 16.0)
|
||||
gg_tiles(render3d_st, 8, 16.0, 40.0)
|
||||
gg_tiles(render3d_st, 16, 40.0, render3d_st.grass_radius)
|
||||
let rb = words(4)
|
||||
# made once: a words() a frame is a calloc nothing frees
|
||||
if render3d_st.gg_rb == null {
|
||||
render3d_st.gg_rb = words(4); render3d_st.gg_cb = words(1); render3d_st.gg_bufs = words(3)
|
||||
render3d_st.gg_texs = words(2); render3d_st.gg_pr = words(48)
|
||||
}
|
||||
let rb = render3d_st.gg_rb
|
||||
rb[0] = GG_BANDS; rb[1] = 0; rb[2] = 0; rb[3] = 0
|
||||
let cb = words(1)
|
||||
let cb = render3d_st.gg_cb
|
||||
cb[0] = render3d_st.gg_cmds
|
||||
gpu_dispatch(render3d_st, render3d_st.gg_reset, data_of(rb), 16, cb, 1)
|
||||
if render3d_st.gg_n == 0 { return }
|
||||
|
|
@ -112,16 +117,16 @@ function gg_cull_frame(render3d_st: mut Render3dState) -> void {
|
|||
let tb = render3d_st.gg_tiles_buf[render3d_st.r3d_test_frame % 2]
|
||||
gpu_buffer_upload(render3d_st, tb, render3d_st.gg_n * 16, data_of(render3d_st.gg_tv), GPU_DYNAMIC)
|
||||
let pr = gg_params(render3d_st)
|
||||
let bufs = words(3)
|
||||
let bufs = render3d_st.gg_bufs
|
||||
bufs[0] = tb; bufs[1] = render3d_st.gg_out; bufs[2] = render3d_st.gg_cmds
|
||||
let texs = words(2)
|
||||
let texs = render3d_st.gg_texs
|
||||
texs[0] = render3d_st.ter_height_tex; texs[1] = render3d_st.ter_ortho_tex
|
||||
gpu_dispatch_tex(render3d_st, render3d_st.gg_cull, data_of(pr), 192, bufs, texs, (render3d_st.gg_max + 63) / 64, render3d_st.gg_n)
|
||||
}
|
||||
|
||||
# grass_cull.comp's Params, std140: 12 vec4s
|
||||
function gg_params(render3d_st: Render3dState) -> words {
|
||||
let pr = words(48)
|
||||
# grass_cull.comp's Params, std140: 12 vec4s, into the block made once in gg_cull_frame
|
||||
function gg_params(render3d_st: mut Render3dState) -> words {
|
||||
let pr = render3d_st.gg_pr
|
||||
for i in 0 .. 48 { pr[i] = 0 }
|
||||
if render3d_st.cam_planes != null { for i in 0 .. 16 { pr[i] = float_bits(render3d_st.cam_planes[i]) } }
|
||||
pr[16] = float_bits(render3d_st.cam_pos[0]); pr[17] = float_bits(render3d_st.cam_pos[1]); pr[18] = float_bits(render3d_st.cam_pos[2]); pr[19] = float_bits(render3d_st.grass_radius)
|
||||
|
|
|
|||
34
packages/ludic.things/chunks.ludic
Normal file
34
packages/ludic.things/chunks.ludic
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# chunks.ludic - the Things of one chunk of the world, counted and let go together: what a streamed
|
||||
# world asks when a chunk is loaded or dropped. A chunk is chunk_of(x, z, TH_CHUNK_M).
|
||||
export function things_chunk(x: float, z: float) -> int { return chunk_of(x, z, TH_CHUNK_M) }
|
||||
|
||||
export function things_chunk_count(things_st: ThingsState, chunk: int) -> int { return ix_count(things_st.th.chunks, chunk) }
|
||||
|
||||
# every Thing standing in the chunk removed for good, the port and the facts told of each as
|
||||
# thing_remove tells them; how many went
|
||||
export function things_drop_chunk(things_st: mut ThingsState, chunk: int) -> int {
|
||||
var n = 0
|
||||
let rows = ix_rows(things_st.th.chunks, chunk)
|
||||
while len(rows) > 0 {
|
||||
thing_remove(things_st, things_st.th.tab.rec[rows[len(rows) - 1]])
|
||||
n += 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
# change detection: the tick now, the table moving on to the next - a consumer keeps what this
|
||||
# returned and asks things_changed(since: it) for what was written after
|
||||
export function things_now(things_st: mut ThingsState) -> int {
|
||||
let t = tb_tick(things_st.th.tab)
|
||||
tb_advance(things_st.th.tab)
|
||||
return t
|
||||
}
|
||||
|
||||
# every Thing a verb wrote after `since`, into the caller's list (untouched 64-row blocks skipped)
|
||||
export function things_changed(things_st: ThingsState, since: int, rows: words, out: []Thing) -> int {
|
||||
List.clear(out)
|
||||
let tb = things_st.th.tab
|
||||
let n = tb_changed_since(tb, since, rows)
|
||||
for k in 0 .. n { push(out, tb.rec[rows[k]]) }
|
||||
return n
|
||||
}
|
||||
|
|
@ -45,14 +45,22 @@ export function thing_is_personal(t: Thing) -> bool { return thing_kind_ok(t.kin
|
|||
# set down by someone rather than grown by the world: the kind says, and the port has the last word
|
||||
export function thing_is_put_down(t: Thing) -> bool { return ThingsWorld.put_down(t) }
|
||||
|
||||
# every active Thing whose kind ticks, `hours` of the world's clock on
|
||||
export function things_tick(things_st: ThingsState, hours: float) -> void {
|
||||
let all = things_all(things_st)
|
||||
for i in 0 .. len(all) {
|
||||
let t = all[i]
|
||||
if not t.active or not thing_kind_ok(t.kind) { continue }
|
||||
let k = ThingKinds[t.kind]
|
||||
if k.tick != null { k.tick(t, hours) }
|
||||
# every active Thing whose kind ticks, `hours` of the world's clock on: only the kinds that tick
|
||||
# are visited, through the kind index, from a list of handles taken first - a tick may hide one
|
||||
export function things_tick(things_st: mut ThingsState, hours: float) -> void {
|
||||
let hs = things_st.th_hs
|
||||
List.clear(hs)
|
||||
let tb = things_st.th.tab
|
||||
for k in 0 .. TH_COUNT {
|
||||
if ThingKinds[k].tick == null { continue }
|
||||
let rows = ix_rows(things_st.th.kinds, k)
|
||||
for i in 0 .. len(rows) { push(hs, tb.ent[rows[i]]) }
|
||||
}
|
||||
for i in 0 .. len(hs) {
|
||||
let r = tb_row(tb, hs[i])
|
||||
if r < 0 { continue }
|
||||
let t = tb.rec[r]
|
||||
if t.active { ThingKinds[t.kind].tick(t, hours) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ import "ludic.base"
|
|||
import "kinds.ludic"
|
||||
import "port.ludic"
|
||||
import "store.ludic"
|
||||
import "verbs.ludic"
|
||||
import "moves.ludic"
|
||||
import "queries.ludic"
|
||||
import "near.ludic"
|
||||
import "chunks.ludic"
|
||||
import "dispatch.ludic"
|
||||
import "system.ludic"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ export property Thing {
|
|||
sz: float = 0.0
|
||||
syaw: float = 0.0
|
||||
shared: bool = false # the game copies it to others (a party)
|
||||
ent: int = -1 # its handle in the table, -1 once removed
|
||||
tab: Table<Thing> = null # the table holding it, so a verb on the Thing alone keeps the indexes
|
||||
}
|
||||
|
||||
export property ThingKind {
|
||||
|
|
|
|||
55
packages/ludic.things/moves.ludic
Normal file
55
packages/ludic.things/moves.ludic
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# moves.ludic - a Thing moved, shown or hidden: the record and its row together, so the grid, the
|
||||
# kind indexes and the chunk index never answer for where it was or what it was
|
||||
export function thing_move(t: Thing, x: float, z: float, yaw: float) -> void {
|
||||
thing_move_to(t, x, ThingsWorld.ground(x, z), z, yaw)
|
||||
}
|
||||
|
||||
export function thing_move_to(t: Thing, x: float, y: float, z: float, yaw: float) -> void {
|
||||
thing_place_at(t, x, y, z, yaw)
|
||||
ThingsWorld.moved(t)
|
||||
}
|
||||
|
||||
# moved without telling the port: a stand-in the game draws itself, or nothing draws
|
||||
export function thing_place_at(t: Thing, x: float, y: float, z: float, yaw: float) -> void {
|
||||
t.x = x
|
||||
t.y = y
|
||||
t.z = z
|
||||
t.yaw = yaw
|
||||
let r = th_row(t)
|
||||
if r >= 0 { th_moved(t.tab, r, x, z) }
|
||||
}
|
||||
|
||||
export function thing_set_active(t: Thing, on: bool) -> void {
|
||||
if t == null { return }
|
||||
thing_set_on(t, on)
|
||||
ThingsWorld.shown(t)
|
||||
}
|
||||
export function thing_hide(t: Thing) -> void { thing_set_active(t, false) }
|
||||
export function thing_show(t: Thing) -> void { thing_set_active(t, true) }
|
||||
|
||||
# active or not without telling the port
|
||||
export function thing_set_on(t: Thing, on: bool) -> void {
|
||||
if t == null { return }
|
||||
t.active = on
|
||||
let r = th_row(t)
|
||||
if r < 0 { return }
|
||||
var v = 0
|
||||
if on { v = 1 }
|
||||
tb_set_i(t.tab, TH_ON, r, v)
|
||||
}
|
||||
|
||||
# where it stands on the ground plane, without telling the port (its height is the caller's)
|
||||
export function thing_set_xz(t: Thing, x: float, z: float) -> void {
|
||||
if t == null { return }
|
||||
t.x = x
|
||||
t.z = z
|
||||
let r = th_row(t)
|
||||
if r >= 0 { th_moved(t.tab, r, x, z) }
|
||||
}
|
||||
|
||||
# where a row now stands: its columns, its cell in the grid and its chunk
|
||||
function th_moved(tb: Table<Thing>, r: int, x: float, z: float) -> void {
|
||||
tb_set_xz(tb, TH_X, TH_Z, r, x, z)
|
||||
let c = chunk_of(x, z, TH_CHUNK_M)
|
||||
if tb.i[TH_CHUNK][r] != c { tb_set_i(tb, TH_CHUNK, r, c) }
|
||||
}
|
||||
35
packages/ludic.things/near.ludic
Normal file
35
packages/ludic.things/near.ludic
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# near.ludic - the neighbourhood questions a frame asks many times: what is around a point, of any
|
||||
# kind or of one, into a list the CALLER keeps (in its own state, reused every frame), from the grid
|
||||
# or the kind's own list - never a walk of every Thing, and nothing allocated or written here
|
||||
export function things_near(things_st: ThingsState, x: float, z: float, r: float, out: []Thing) -> int {
|
||||
return tb_within_recs(things_st.th.tab, things_st.th.grid, x, z, r, -1, 0, out)
|
||||
}
|
||||
|
||||
export function things_near_of(things_st: ThingsState, kind: int, x: float, z: float, r: float, out: []Thing) -> int {
|
||||
return tb_within_of_recs(things_st.th.tab, things_st.th.grid, things_st.th.kinds, x, z, r, kind, out)
|
||||
}
|
||||
|
||||
# the nearest active Thing within maxr (0: any distance) that `keep` says yes to, or null
|
||||
export function thing_nearest_where(things_st: ThingsState, x: float, z: float, maxr: float, keep: fn(Thing) -> bool) -> Thing {
|
||||
let r = tb_nearest_where(things_st.th.tab, things_st.th.grid, x, z, maxr, keep)
|
||||
if r < 0 { return null }
|
||||
return things_st.th.tab.rec[r]
|
||||
}
|
||||
|
||||
# every one of a kind, active or not, counted and walked without a list
|
||||
export function things_count_all_of(things_st: ThingsState, kind: int) -> int { return ix_count(things_st.th.every, kind) }
|
||||
|
||||
export function thing_of_kind_all(things_st: ThingsState, kind: int, i: int) -> Thing {
|
||||
let rows = ix_rows(things_st.th.every, kind)
|
||||
if i < 0 or i >= len(rows) { return null }
|
||||
return things_st.th.tab.rec[rows[i]]
|
||||
}
|
||||
|
||||
# the nearest active one of a kind (below 0: any) strictly within maxr of (x, z), or null
|
||||
export function thing_nearest_within(things_st: ThingsState, kind: int, x: float, z: float, maxr: float) -> Thing {
|
||||
let tb = things_st.th.tab
|
||||
var r = -1
|
||||
if kind < 0 { r = tb_nearest(tb, things_st.th.grid, x, z, maxr, -1, 0) } else { r = tb_nearest_of(tb, things_st.th.grid, things_st.th.kinds, x, z, maxr, kind) }
|
||||
if r < 0 { return null }
|
||||
return tb.rec[r]
|
||||
}
|
||||
|
|
@ -1,38 +1,43 @@
|
|||
# queries.ludic - where the Things are. Only active Things answer, except thing_by_uid and
|
||||
# things_each_all, which ask about everything that exists. A kind below 0 is any kind.
|
||||
# queries.ludic - where the Things are, answered by the table's indexes rather than a scan. Only
|
||||
# active Things answer, except thing_by_uid and things_each_all, which ask about everything that
|
||||
# exists. A kind below 0 is any kind.
|
||||
function th_of(t: Thing, kind: int) -> bool { return kind < 0 or t.kind == kind }
|
||||
|
||||
# the first active one of a kind (and of that id, unless id < 0), in the order placed
|
||||
# the first-placed active one of a kind (and of that id, unless id < 0)
|
||||
export function thing_find(things_st: ThingsState, kind: int, id: int) -> Thing {
|
||||
let all = things_all(things_st)
|
||||
for i in 0 .. len(all) {
|
||||
let t = all[i]
|
||||
if t.active and th_of(t, kind) and (id < 0 or t.id == id) { return t }
|
||||
let tb = things_st.th.tab
|
||||
var best: Thing = null
|
||||
if kind < 0 {
|
||||
for r in 0 .. tb_len(tb) { best = th_earlier(best, tb.rec[r], id) }
|
||||
return best
|
||||
}
|
||||
return null
|
||||
let rows = ix_rows(things_st.th.kinds, kind)
|
||||
for k in 0 .. len(rows) { best = th_earlier(best, tb.rec[rows[k]], id) }
|
||||
return best
|
||||
}
|
||||
|
||||
# the one of the two placed first that is active and has the id asked for
|
||||
function th_earlier(best: Thing, t: Thing, id: int) -> Thing {
|
||||
if not t.active or (id >= 0 and t.id != id) { return best }
|
||||
if best == null or t.uid < best.uid { return t }
|
||||
return best
|
||||
}
|
||||
|
||||
export function thing_by_uid(things_st: ThingsState, uid: int) -> Thing {
|
||||
let all = things_all(things_st)
|
||||
for i in 0 .. len(all) { if all[i].uid == uid { return all[i] } }
|
||||
return null
|
||||
let r = tb_row(things_st.th.tab, imap_get(things_st.th_uids, uid, -1))
|
||||
if r < 0 { return null }
|
||||
return things_st.th.tab.rec[r]
|
||||
}
|
||||
|
||||
# the nearest active one of a kind to (x, z), or null
|
||||
export function thing_nearest(things_st: ThingsState, kind: int, x: float, z: float) -> Thing {
|
||||
var best: Thing = null
|
||||
var bd = 0.0
|
||||
let all = things_all(things_st)
|
||||
for i in 0 .. len(all) {
|
||||
let t = all[i]
|
||||
if not t.active or not th_of(t, kind) { continue }
|
||||
let d = thing_dist2(t, x, z)
|
||||
if best == null or d < bd {
|
||||
best = t
|
||||
bd = d
|
||||
}
|
||||
let tb = things_st.th.tab
|
||||
var r = -1
|
||||
if kind < 0 { r = tb_nearest(tb, things_st.th.grid, x, z, 0.0, -1, 0) } else {
|
||||
r = tb_nearest_of(tb, things_st.th.grid, things_st.th.kinds, x, z, 0.0, kind)
|
||||
}
|
||||
return best
|
||||
if r < 0 { return null }
|
||||
return tb.rec[r]
|
||||
}
|
||||
|
||||
# the squared ground distance from a Thing to (x, z)
|
||||
|
|
@ -42,14 +47,13 @@ export function thing_dist2(t: Thing, x: float, z: float) -> float {
|
|||
return dx * dx + dz * dz
|
||||
}
|
||||
|
||||
# every active one within r of (x, z), nearest first
|
||||
# every active one within r of (x, z), nearest first, in a new list: for a question asked now and
|
||||
# then - a frame's question is things_near, into a list the caller keeps
|
||||
export function things_within(things_st: ThingsState, x: float, z: float, r: float) -> []Thing {
|
||||
let found = new []Thing
|
||||
things_near(things_st, x, z, r, found)
|
||||
let out = new []Thing
|
||||
let all = things_all(things_st)
|
||||
for i in 0 .. len(all) {
|
||||
let t = all[i]
|
||||
if t.active and thing_dist2(t, x, z) <= r * r { th_insert(out, t, x, z) }
|
||||
}
|
||||
for k in 0 .. len(found) { th_insert(out, found[k], x, z) }
|
||||
return out
|
||||
}
|
||||
|
||||
|
|
@ -63,18 +67,44 @@ function th_insert(out: []Thing, t: Thing, x: float, z: float) -> void {
|
|||
out[j] = t
|
||||
}
|
||||
|
||||
# every active one of a kind, in the order placed
|
||||
# every active one of a kind, first placed first
|
||||
export function things_each(things_st: ThingsState, kind: int) -> []Thing {
|
||||
let out = new []Thing
|
||||
let all = things_all(things_st)
|
||||
for i in 0 .. len(all) { if all[i].active and th_of(all[i], kind) { push(out, all[i]) } }
|
||||
return out
|
||||
return th_listed(things_st, ix_rows(things_st.th.kinds, kind))
|
||||
}
|
||||
|
||||
# every one of a kind, active or not
|
||||
export function things_each_all(things_st: ThingsState, kind: int) -> []Thing {
|
||||
let out = new []Thing
|
||||
let all = things_all(things_st)
|
||||
for i in 0 .. len(all) { if th_of(all[i], kind) { push(out, all[i]) } }
|
||||
return out
|
||||
return th_listed(things_st, ix_rows(things_st.th.every, kind))
|
||||
}
|
||||
|
||||
# how many active ones of a kind there are, without a list
|
||||
export function things_count_of(things_st: ThingsState, kind: int) -> int {
|
||||
return ix_count(things_st.th.kinds, kind)
|
||||
}
|
||||
|
||||
function th_listed(things_st: ThingsState, rows: words) -> []Thing {
|
||||
let out = new []Thing
|
||||
for k in 0 .. len(rows) { push(out, things_st.th.tab.rec[rows[k]]) }
|
||||
return th_by_uid_order(out)
|
||||
}
|
||||
|
||||
function th_by_uid_order(xs: []Thing) -> []Thing {
|
||||
for i in 1 .. len(xs) {
|
||||
let t = xs[i]
|
||||
var j = i
|
||||
while j > 0 and xs[j - 1].uid > t.uid {
|
||||
xs[j] = xs[j - 1]
|
||||
j -= 1
|
||||
}
|
||||
xs[j] = t
|
||||
}
|
||||
return xs
|
||||
}
|
||||
|
||||
# the i-th active one of a kind, 0 .. things_count_of(kind) - 1, in the index's order (which a
|
||||
# removal or a hide changes: walk it within a frame, never keep an i)
|
||||
export function thing_of_kind(things_st: ThingsState, kind: int, i: int) -> Thing {
|
||||
let rows = ix_rows(things_st.th.kinds, kind)
|
||||
if i < 0 or i >= len(rows) { return null }
|
||||
return things_st.th.tab.rec[rows[i]]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,42 @@
|
|||
# store.ludic - the Things, and the only verbs that make, remove, move, show and hide one; each
|
||||
# placing and removing is a fact, and the port is told at once so the game can draw it
|
||||
# store.ludic - the Things, rows of a ludic.base Table: where each stands and whether it is active
|
||||
# are columns a grid and two kind indexes are kept over, and only the verbs (verbs.ludic) make,
|
||||
# move, show, hide and remove one; each placing and removing is a fact the port hears at once
|
||||
export const TH_X: int = 0 # float columns
|
||||
export const TH_Z: int = 1
|
||||
export const TH_KIND: int = 0 # int columns
|
||||
export const TH_ON: int = 1 # 1 while active
|
||||
export const TH_CHUNK: int = 2 # the chunk of the world it stands in (chunk_of, TH_CHUNK_M)
|
||||
export const TH_CHUNK_M: float = 256.0
|
||||
const TH_CELL: float = 16.0
|
||||
|
||||
# the table and the indexes kept over it, made together
|
||||
export property ThingsTable {
|
||||
tab: Table<Thing> = null
|
||||
grid: Grid = null
|
||||
kinds: IntIndex = null # active Things by kind
|
||||
every: IntIndex = null # every Thing by kind, active or not
|
||||
chunks: IntIndex = null # every Thing by the chunk it stands in: loaded and let go together
|
||||
}
|
||||
|
||||
export state ThingsState {
|
||||
th_list: []Thing = new []Thing
|
||||
th: ThingsTable = th__new()
|
||||
th_uids: IntMap = imap_new()
|
||||
th_uid: int = 0
|
||||
th_facts: Queue<ThingFact> = th_facts__new()
|
||||
th_marked: int = -1
|
||||
th_hs: words = words(0) # things_tick's handles, taken before any tick runs
|
||||
}
|
||||
|
||||
function th_facts__new() -> Queue<ThingFact> { return queue_new("things.facts") }
|
||||
|
||||
function th__new() -> ThingsTable {
|
||||
let th = new ThingsTable
|
||||
let tb: Table<Thing> = table_new(2, 3)
|
||||
th.tab = tb
|
||||
th.grid = tb_grid(tb, TH_X, TH_Z, TH_ON, TH_CELL)
|
||||
th.kinds = tb_index(tb, TH_KIND, TH_ON)
|
||||
th.every = tb_index(tb, TH_KIND, -1)
|
||||
th.chunks = tb_index(tb, TH_CHUNK, -1)
|
||||
return th
|
||||
}
|
||||
|
||||
export function things_facts(things_st: ThingsState) -> Queue<ThingFact> {
|
||||
|
|
@ -24,88 +56,37 @@ function th_fact(things_st: ThingsState, what: int, t: Thing) -> void {
|
|||
}
|
||||
|
||||
# whether a world has been set out (things_clear) since the program began
|
||||
export function things_ready(things_st: ThingsState) -> bool { return things_st.th_list != null }
|
||||
export function things_ready(things_st: ThingsState) -> bool { return things_st.th.tab != null }
|
||||
|
||||
# every Thing, in the order placed; the list is the package's, never to be pushed to
|
||||
export function things_all(things_st: ThingsState) -> []Thing {
|
||||
return things_st.th_list
|
||||
}
|
||||
# every Thing, one per row - the table's own list: read it, never push to it. A removal moves the
|
||||
# last Thing into the removed one's place, so the order is the table's, not the order placed.
|
||||
export function things_all(things_st: ThingsState) -> []Thing { return things_st.th.tab.rec }
|
||||
|
||||
export function things_count(things_st: ThingsState) -> int { return len(things_all(things_st)) }
|
||||
export function things_count(things_st: ThingsState) -> int { return tb_len(things_st.th.tab) }
|
||||
|
||||
# a new world: every Thing forgotten, quietly (the game has already dropped its drawings)
|
||||
export function things_clear(things_st: mut ThingsState) -> void { things_st.th_list = new []Thing }
|
||||
|
||||
export function thing_spawn(things_st: mut ThingsState, kind: int, look: string, x: float, y: float, z: float, yaw: float, id: int) -> Thing {
|
||||
return th_place(things_st, new Thing, kind, look, x, y, z, yaw, id)
|
||||
export function things_clear(things_st: mut ThingsState) -> void {
|
||||
let tb = things_st.th.tab
|
||||
for r in 0 .. tb_len(tb) { tb.rec[r].ent = -1 }
|
||||
tb_clear(tb)
|
||||
imap_clear(things_st.th_uids)
|
||||
}
|
||||
|
||||
function th_place(things_st: mut ThingsState, t: Thing, kind: int, look: string, x: float, y: float, z: float, yaw: float, id: int) -> Thing {
|
||||
t.kind = kind
|
||||
t.look = look
|
||||
t.x = x
|
||||
t.y = y
|
||||
t.z = z
|
||||
t.yaw = yaw
|
||||
t.id = id
|
||||
things_st.th_uid += 1
|
||||
t.uid = things_st.th_uid
|
||||
push(things_all(things_st), t)
|
||||
ThingsWorld.placed(t)
|
||||
th_fact(things_st, THING_PLACED, t)
|
||||
return t
|
||||
# the row a Thing stands in, or -1 when it is not in the table any more
|
||||
function th_row(t: Thing) -> int {
|
||||
if t == null or t.tab == null { return -1 }
|
||||
return tb_row(t.tab, t.ent)
|
||||
}
|
||||
|
||||
# a Thing on the ground (the port's), set into it by `sink`, used from `reach`
|
||||
export function thing_put(things_st: mut ThingsState, kind: int, look: string, x: float, z: float, yaw: float, sink: float, reach: float, id: int) -> Thing {
|
||||
let t = new Thing
|
||||
t.reach = reach
|
||||
return th_place(things_st, t, kind, look, x, ThingsWorld.ground(x, z) - sink, z, yaw, id)
|
||||
}
|
||||
|
||||
# gone for good
|
||||
export function thing_remove(things_st: mut ThingsState, t: Thing) -> void {
|
||||
if t == null { return }
|
||||
things_st.th_marked = t.uid
|
||||
things_drop(things_st, fn th_is_marked)
|
||||
things_st.th_marked = -1
|
||||
}
|
||||
|
||||
function th_is_marked(things_st: ThingsState, t: Thing) -> bool { return t.uid == things_st.th_marked }
|
||||
|
||||
# every Thing `gone` says so is removed for good
|
||||
export function things_drop(things_st: mut ThingsState, gone: fn(Thing) -> bool) -> void {
|
||||
let keep = new []Thing
|
||||
let all = things_all(things_st)
|
||||
for i in 0 .. len(all) {
|
||||
let t = all[i]
|
||||
if gone(t) {
|
||||
ThingsWorld.removed(t)
|
||||
th_fact(things_st, THING_REMOVED, t)
|
||||
} else {
|
||||
push(keep, t)
|
||||
}
|
||||
# the columns say what the record says: a Thing's fields written around the verbs are caught here
|
||||
export function things_verify(things_st: ThingsState) -> int {
|
||||
let tb = things_st.th.tab
|
||||
var bad = 0
|
||||
for r in 0 .. tb_len(tb) {
|
||||
let t = tb.rec[r]
|
||||
var on = 0
|
||||
if t.active { on = 1 }
|
||||
if tb.f[TH_X][r] != t.x or tb.f[TH_Z][r] != t.z or tb.i[TH_KIND][r] != t.kind or tb.i[TH_ON][r] != on or tb.i[TH_CHUNK][r] != chunk_of(t.x, t.z, TH_CHUNK_M) or t.ent != tb.ent[r] { bad += 1 }
|
||||
}
|
||||
things_st.th_list = keep
|
||||
return bad
|
||||
}
|
||||
|
||||
export function thing_move(t: Thing, x: float, z: float, yaw: float) -> void {
|
||||
thing_move_to(t, x, ThingsWorld.ground(x, z), z, yaw)
|
||||
}
|
||||
|
||||
export function thing_move_to(t: Thing, x: float, y: float, z: float, yaw: float) -> void {
|
||||
t.x = x
|
||||
t.y = y
|
||||
t.z = z
|
||||
t.yaw = yaw
|
||||
ThingsWorld.moved(t)
|
||||
}
|
||||
|
||||
export function thing_set_active(t: Thing, on: bool) -> void {
|
||||
if t == null { return }
|
||||
t.active = on
|
||||
ThingsWorld.shown(t)
|
||||
}
|
||||
export function thing_hide(t: Thing) -> void { thing_set_active(t, false) }
|
||||
export function thing_show(t: Thing) -> void { thing_set_active(t, true) }
|
||||
function th_facts__new() -> Queue<ThingFact> { return queue_new("things.facts") }
|
||||
|
|
|
|||
|
|
@ -155,4 +155,64 @@ program ThingsTest {
|
|||
expect_eq(t.owner, 0)
|
||||
expect_eq(things_test_st.back, 1)
|
||||
}
|
||||
|
||||
test "removals keep the indexes true: nearest, find, uid and the columns agree" (things_st: mut ThingsState) {
|
||||
things_clear(things_st)
|
||||
var keep: Thing = null
|
||||
for i in 0 .. 400 {
|
||||
let t = put(things_st, TH_BRANCH + (i % 2), float(i) * 3.0 - 600.0, float(i % 17) * 5.0)
|
||||
if i == 250 { keep = t }
|
||||
}
|
||||
let gone = thing_find(things_st, TH_BRANCH, -1)
|
||||
let uid = gone.uid
|
||||
thing_remove(things_st, gone)
|
||||
expect(thing_by_uid(things_st, uid) == null)
|
||||
expect(thing_by_uid(things_st, keep.uid) == keep)
|
||||
expect_eq(things_count(things_st), 399)
|
||||
expect_eq(things_count_of(things_st, TH_BRANCH), 199)
|
||||
thing_move(keep, 5000.0, 5000.0, 0.0)
|
||||
expect(thing_nearest(things_st, -1, 4990.0, 4990.0) == keep)
|
||||
thing_hide(keep)
|
||||
expect(thing_nearest(things_st, -1, 4990.0, 4990.0) != keep)
|
||||
expect_eq(things_verify(things_st), 0)
|
||||
keep.x = 1.0
|
||||
expect_eq(things_verify(things_st), 1)
|
||||
}
|
||||
|
||||
test "a chunk's Things are counted and let go together; a move carries a Thing to its new chunk" (things_st: mut ThingsState, things_test_st: ThingsTestState) {
|
||||
things_clear(things_st)
|
||||
put(things_st, TH_BRANCH, 10.0, 10.0)
|
||||
put(things_st, TH_BRANCH, 20.0, 30.0)
|
||||
let far = put(things_st, TH_BRANCH, 600.0, 10.0)
|
||||
let here = things_chunk(10.0, 10.0)
|
||||
expect_eq(things_chunk_count(things_st, here), 2)
|
||||
expect_eq(things_chunk_count(things_st, things_chunk(600.0, 10.0)), 1)
|
||||
thing_move(far, 15.0, 15.0, 0.0)
|
||||
expect_eq(things_chunk_count(things_st, here), 3)
|
||||
facts(things_st)
|
||||
let gone = things_test_st.gone
|
||||
expect_eq(things_drop_chunk(things_st, here), 3)
|
||||
expect_eq(things_count(things_st), 0)
|
||||
expect_eq(things_test_st.gone, gone + 3)
|
||||
expect_eq(len(facts(things_st)), 3)
|
||||
expect_eq(things_verify(things_st), 0)
|
||||
}
|
||||
|
||||
test "change detection names what a verb wrote since the tick a consumer kept" (things_st: mut ThingsState) {
|
||||
things_clear(things_st)
|
||||
let a = put(things_st, TH_BRANCH, 1.0, 0.0)
|
||||
let b = put(things_st, TH_BRANCH, 2.0, 0.0)
|
||||
let seen = things_now(things_st)
|
||||
let rows = words(0)
|
||||
let out = new []Thing
|
||||
expect_eq(things_changed(things_st, seen, rows, out), 0)
|
||||
thing_hide(b)
|
||||
expect_eq(things_changed(things_st, seen, rows, out), 1)
|
||||
expect(out[0] == b)
|
||||
let later = things_now(things_st)
|
||||
thing_move(a, 9.0, 9.0, 0.0)
|
||||
expect_eq(things_changed(things_st, later, rows, out), 1)
|
||||
expect(out[0] == a)
|
||||
expect_eq(things_changed(things_st, seen, rows, out), 2)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
69
packages/ludic.things/verbs.ludic
Normal file
69
packages/ludic.things/verbs.ludic
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# verbs.ludic - the only ways a Thing is made, removed, moved, shown or hidden: each writes the
|
||||
# record and the table's columns together, so the grid and the kind indexes never fall behind
|
||||
export function thing_spawn(things_st: mut ThingsState, kind: int, look: string, x: float, y: float, z: float, yaw: float, id: int) -> Thing {
|
||||
return th_place(things_st, new Thing, kind, look, x, y, z, yaw, id)
|
||||
}
|
||||
|
||||
function th_place(things_st: mut ThingsState, t: Thing, kind: int, look: string, x: float, y: float, z: float, yaw: float, id: int) -> Thing {
|
||||
t.kind = kind
|
||||
t.look = look
|
||||
t.x = x
|
||||
t.y = y
|
||||
t.z = z
|
||||
t.yaw = yaw
|
||||
t.id = id
|
||||
things_st.th_uid += 1
|
||||
t.uid = things_st.th_uid
|
||||
let tb = things_st.th.tab
|
||||
t.tab = tb
|
||||
t.ent = tb_add(tb, t)
|
||||
th_columns(t)
|
||||
imap_put(things_st.th_uids, t.uid, t.ent)
|
||||
ThingsWorld.placed(t)
|
||||
th_fact(things_st, THING_PLACED, t)
|
||||
return t
|
||||
}
|
||||
|
||||
# the record's x, z, kind and active into its row, refiled once
|
||||
function th_columns(t: Thing) -> void {
|
||||
let r = th_row(t)
|
||||
if r < 0 { return }
|
||||
let tb = t.tab
|
||||
tb.f[TH_X][r] = t.x
|
||||
tb.f[TH_Z][r] = t.z
|
||||
tb.i[TH_KIND][r] = t.kind
|
||||
var on = 0
|
||||
if t.active { on = 1 }
|
||||
tb.i[TH_ON][r] = on
|
||||
tb.i[TH_CHUNK][r] = chunk_of(t.x, t.z, TH_CHUNK_M)
|
||||
tb_refile(tb, r)
|
||||
}
|
||||
|
||||
# a Thing on the ground (the port's), set into it by `sink`, used from `reach`
|
||||
export function thing_put(things_st: mut ThingsState, kind: int, look: string, x: float, z: float, yaw: float, sink: float, reach: float, id: int) -> Thing {
|
||||
let t = new Thing
|
||||
t.reach = reach
|
||||
return th_place(things_st, t, kind, look, x, ThingsWorld.ground(x, z) - sink, z, yaw, id)
|
||||
}
|
||||
|
||||
# gone for good
|
||||
export function thing_remove(things_st: mut ThingsState, t: Thing) -> void {
|
||||
if th_row(t) < 0 { return }
|
||||
ThingsWorld.removed(t)
|
||||
th_fact(things_st, THING_REMOVED, t)
|
||||
imap_del(things_st.th_uids, t.uid)
|
||||
tb_remove(t.tab, t.ent)
|
||||
t.ent = -1
|
||||
}
|
||||
|
||||
# every Thing `gone` says so is removed for good; from the last row down, so the row a removal
|
||||
# moves into place has already been asked
|
||||
export function things_drop(things_st: mut ThingsState, gone: fn(Thing) -> bool) -> void {
|
||||
let tb = things_st.th.tab
|
||||
var r = tb_len(tb) - 1
|
||||
while r >= 0 {
|
||||
let t = tb.rec[r]
|
||||
if gone(t) { thing_remove(things_st, t) }
|
||||
r -= 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
# call.ludic - a player's boat or horse brought to a place: made there the first time, fetched there
|
||||
# after; gone when its player leaves
|
||||
function ve_add(vehicles_st: VehiclesState, kind: int, x: float, z: float, yaw: float) -> Vehicle {
|
||||
function ve_add(vehicles_st: VehiclesState, kind: int, owner: int, x: float, z: float, yaw: float) -> Vehicle {
|
||||
if kind != VEHICLE_HORSE and kind != VEHICLE_BOAT { return null }
|
||||
let v = new Vehicle
|
||||
v.nid = VehicleWorld.next_id()
|
||||
v.kind = kind
|
||||
v.owner = owner
|
||||
v.yaw = yaw
|
||||
if kind == VEHICLE_HORSE {
|
||||
v.seat_h = 1.35
|
||||
|
|
@ -14,7 +15,7 @@ function ve_add(vehicles_st: VehiclesState, kind: int, x: float, z: float, yaw:
|
|||
v.seat_h = 0.30
|
||||
}
|
||||
ve_put(v, x, z)
|
||||
push(ve_all(vehicles_st), v)
|
||||
ve_file(vehicles_st.ve, v)
|
||||
ve_settle(v)
|
||||
VehicleWorld.made(v)
|
||||
return v
|
||||
|
|
@ -51,9 +52,8 @@ export function vehicle_call_for(vehicles_st: VehiclesState, pid: int, kind: int
|
|||
var yaw = vehicles_st.ve_dock_yaw
|
||||
if kind == VEHICLE_HORSE { yaw = vehicles_st.ve_rail_yaw }
|
||||
if v == null {
|
||||
let made = ve_add(vehicles_st, kind, vehicle_place_x(vehicles_st, kind, k), vehicle_place_z(vehicles_st, kind, k), yaw)
|
||||
let made = ve_add(vehicles_st, kind, pid, vehicle_place_x(vehicles_st, kind, k), vehicle_place_z(vehicles_st, kind, k), yaw)
|
||||
if made == null { return false }
|
||||
made.owner = pid
|
||||
if pid == 0 { ve_apply_kept(vehicles_st, made) }
|
||||
return true
|
||||
}
|
||||
|
|
@ -68,10 +68,10 @@ export function vehicle_call_for(vehicles_st: VehiclesState, pid: int, kind: int
|
|||
# a player left: their boat and horse go with them, and their places are free
|
||||
export function vehicles_release(vehicles_st: mut VehiclesState, pid: int) -> void {
|
||||
if pid == 0 { return }
|
||||
let keep = new []Vehicle
|
||||
let l = ve_all(vehicles_st)
|
||||
for i in 0 .. len(l) {
|
||||
if l[i].owner != pid { push(keep, l[i]) } else { ve_gone(l[i]) }
|
||||
let rows = ix_rows(vehicles_st.ve.owners, pid)
|
||||
while len(rows) > 0 {
|
||||
let v = ve_all(vehicles_st)[rows[len(rows) - 1]]
|
||||
ve_gone(v)
|
||||
ve_unfile(vehicles_st.ve, v)
|
||||
}
|
||||
vehicles_st.ve_list = keep
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ module ludic_vehicles uses ludic_base
|
|||
numbers float
|
||||
import "ludic.base"
|
||||
import "kinds.ludic"
|
||||
import "table.ludic"
|
||||
import "ports.ludic"
|
||||
import "state.ludic"
|
||||
import "places.ludic"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ export property Vehicle {
|
|||
rider: int = -1 # the player on it, -1 nobody
|
||||
home_x: float = 0.0 # a horse is tied where its home is
|
||||
home_z: float = 0.0
|
||||
ent: int = -1 # its handle in the table
|
||||
tab: Table<Vehicle> = null
|
||||
}
|
||||
|
||||
export const VEHICLE_MOUNTED: int = 0
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export state VehiclesState {
|
|||
ve_ids: int = 0
|
||||
ve_sx: float = 0.0
|
||||
ve_sz: float = 0.0
|
||||
ve_list: []Vehicle = new []Vehicle
|
||||
ve: VehicleTable = ve__new()
|
||||
ve_cur: Vehicle = null
|
||||
ve_dock_x: float = 0.0
|
||||
ve_dock_z: float = 0.0
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function ve_say(vehicles_st: VehiclesState, what: int, v: Vehicle, n: float) ->
|
|||
q_push(vehicle_facts(vehicles_st), f)
|
||||
}
|
||||
|
||||
function ve_all(vehicles_st: VehiclesState) -> []Vehicle { return vehicles_st.ve_list }
|
||||
function ve_all(vehicles_st: VehiclesState) -> []Vehicle { return vehicles_st.ve.tab.rec }
|
||||
|
||||
# every vehicle in the valley, to draw and to aim at (change one only through the verbs)
|
||||
export function vehicles_all(vehicles_st: VehiclesState) -> []Vehicle { return ve_all(vehicles_st) }
|
||||
|
|
@ -32,8 +32,9 @@ export function vehicle_ride_kind(vehicles_st: VehiclesState) -> int {
|
|||
export function vehicle_current(vehicles_st: VehiclesState) -> Vehicle { return vehicles_st.ve_cur }
|
||||
|
||||
export function vehicle_of(vehicles_st: VehiclesState, pid: int, kind: int) -> Vehicle {
|
||||
let rows = ix_rows(vehicles_st.ve.owners, pid)
|
||||
let l = ve_all(vehicles_st)
|
||||
for i in 0 .. len(l) { if l[i].kind == kind and l[i].owner == pid { return l[i] } }
|
||||
for k in 0 .. len(rows) { if l[rows[k]].kind == kind { return l[rows[k]] } }
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
# and the horse's hay); where they lie is not saved - they are called again. The boats' hulls are
|
||||
# read back and pushed by the wind in vehicles_tick, which the game calls where the world is run
|
||||
export function vehicles_reset(vehicles_st: mut VehiclesState) -> void {
|
||||
if vehicles_st.ve_list != null {
|
||||
for i in 0 .. len(vehicles_st.ve_list) {
|
||||
let v = vehicles_st.ve_list[i]
|
||||
if v.kind == VEHICLE_BOAT { VehicleHull.gone(v.nid) } else { VehicleLegs.gone(v.nid) }
|
||||
}
|
||||
let l = ve_all(vehicles_st)
|
||||
for i in 0 .. len(l) {
|
||||
let v = l[i]
|
||||
if v.kind == VEHICLE_BOAT { VehicleHull.gone(v.nid) } else { VehicleLegs.gone(v.nid) }
|
||||
v.ent = -1
|
||||
}
|
||||
vehicles_st.ve_list = new []Vehicle
|
||||
tb_clear(vehicles_st.ve.tab)
|
||||
imap_clear(vehicles_st.ve.nids)
|
||||
vehicles_st.ve_cur = null
|
||||
vehicles_st.ve_have_dock = false
|
||||
vehicles_st.ve_swell_said = 0.0
|
||||
|
|
|
|||
45
packages/ludic.vehicles/table.ludic
Normal file
45
packages/ludic.vehicles/table.ludic
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# table.ludic - the vehicles, rows of a ludic.base Table: kind and owner as columns, each player's
|
||||
# own by index, and a nid to its handle. Where one is stays on its record: a boat moves every frame
|
||||
# and there are never more than two a player to search.
|
||||
export const VE_KIND: int = 0 # int columns
|
||||
export const VE_OWNER: int = 1
|
||||
|
||||
export property VehicleTable {
|
||||
tab: Table<Vehicle> = null
|
||||
owners: IntIndex = null # every vehicle by the player it belongs to
|
||||
nids: IntMap = null
|
||||
}
|
||||
|
||||
function ve__new() -> VehicleTable {
|
||||
let w = new VehicleTable
|
||||
let tb: Table<Vehicle> = table_new(0, 2)
|
||||
w.tab = tb
|
||||
w.owners = tb_index(tb, VE_OWNER, -1)
|
||||
w.nids = imap_new()
|
||||
return w
|
||||
}
|
||||
|
||||
# a new row for v, its kind and owner from its record
|
||||
function ve_file(w: VehicleTable, v: Vehicle) -> void {
|
||||
v.tab = w.tab
|
||||
v.ent = tb_add(w.tab, v)
|
||||
let r = tb_row(w.tab, v.ent)
|
||||
w.tab.i[VE_KIND][r] = v.kind
|
||||
w.tab.i[VE_OWNER][r] = v.owner
|
||||
tb_refile(w.tab, r)
|
||||
imap_put(w.nids, v.nid, v.ent)
|
||||
}
|
||||
|
||||
function ve_unfile(w: VehicleTable, v: Vehicle) -> void {
|
||||
imap_del(w.nids, v.nid)
|
||||
tb_remove(w.tab, v.ent)
|
||||
v.ent = -1
|
||||
}
|
||||
|
||||
# the vehicle a message names, or null
|
||||
export function vehicle_by_nid(vehicles_st: VehiclesState, nid: int) -> Vehicle {
|
||||
let tb = vehicles_st.ve.tab
|
||||
let r = tb_row(tb, imap_get(vehicles_st.ve.nids, nid, -1))
|
||||
if r < 0 or tb.rec[r].nid != nid { return null }
|
||||
return tb.rec[r]
|
||||
}
|
||||
|
|
@ -354,4 +354,23 @@ program VehiclesTest {
|
|||
expect_near(vehicle_find(vehicles_st, VEHICLE_HORSE).food, 77.0, 0.01)
|
||||
expect(not vehicle_find(vehicles_st, VEHICLE_BOAT).owned)
|
||||
}
|
||||
|
||||
test "the table: a nid finds its vehicle, each player's own by the owner index, and a release takes both" (vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) {
|
||||
fresh(vehicles_st, vehicles_test_st)
|
||||
vehicle_call_for(vehicles_st, 5, VEHICLE_HORSE, 0)
|
||||
vehicle_call_for(vehicles_st, 5, VEHICLE_BOAT, 0)
|
||||
vehicle_call_for(vehicles_st, 6, VEHICLE_BOAT, 1)
|
||||
let b = vehicle_of(vehicles_st, 6, VEHICLE_BOAT)
|
||||
expect(b != null)
|
||||
expect(vehicle_by_nid(vehicles_st, b.nid) == b)
|
||||
expect(vehicle_of(vehicles_st, 6, VEHICLE_HORSE) == null)
|
||||
let h = vehicle_of(vehicles_st, 5, VEHICLE_HORSE)
|
||||
vehicles_release(vehicles_st, 5)
|
||||
expect_eq(len(vehicles_all(vehicles_st)), 1)
|
||||
expect(vehicle_by_nid(vehicles_st, h.nid) == null)
|
||||
expect(vehicle_of(vehicles_st, 6, VEHICLE_BOAT) == b)
|
||||
vehicles_reset(vehicles_st)
|
||||
expect_eq(len(vehicles_all(vehicles_st)), 0)
|
||||
expect(vehicle_by_nid(vehicles_st, b.nid) == null)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,4 +64,6 @@ export property WildAnimal {
|
|||
sus_z: float = 0.0
|
||||
spook: float = 0.0 # game minutes of extra wariness after being flushed
|
||||
seen: float = 0.0 # the alert it has already reacted to
|
||||
ent: int = -1 # its handle in the table
|
||||
tab: Table<WildAnimal> = null # the table holding it, so a verb on the animal alone keeps the indexes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
export function wildlife_put_away(wildlife_st: mut WildlifeState) -> void {
|
||||
if wildlife_st.wl_own != null { return }
|
||||
wildlife_st.wl_own = wildlife_all(wildlife_st)
|
||||
wildlife_st.wl_list = new []WildAnimal
|
||||
wildlife_st.wl_own = wildlife_st.wl
|
||||
wildlife_st.wl = wl__new()
|
||||
}
|
||||
|
||||
export function wildlife_bring_back(wildlife_st: mut WildlifeState) -> void {
|
||||
if wildlife_st.wl_own == null { return }
|
||||
wildlife_st.wl_list = wildlife_st.wl_own
|
||||
wildlife_st.wl = wildlife_st.wl_own
|
||||
wildlife_st.wl_own = null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import "animal.ludic"
|
|||
import "places.ludic"
|
||||
import "port.ludic"
|
||||
import "facts.ludic"
|
||||
import "table.ludic"
|
||||
import "store.ludic"
|
||||
import "near.ludic"
|
||||
import "guest.ludic"
|
||||
import "record.ludic"
|
||||
import "habitat.ludic"
|
||||
|
|
|
|||
15
packages/ludic.wildlife/near.ludic
Normal file
15
packages/ludic.wildlife/near.ludic
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# near.ludic - the animals around a point, living ones only, into a list the CALLER keeps (in its own
|
||||
# state, reused every frame): from the grid, or a species' own list when it is rare. Nothing is
|
||||
# allocated or written here.
|
||||
export function wildlife_near(wildlife_st: WildlifeState, x: float, z: float, r: float, out: []WildAnimal) -> int {
|
||||
let w = wildlife_st.wl
|
||||
return tb_within_recs(w.tab, w.grid, x, z, r, -1, 0, out)
|
||||
}
|
||||
|
||||
export function wildlife_near_of(wildlife_st: WildlifeState, sp: int, x: float, z: float, r: float, out: []WildAnimal) -> int {
|
||||
let w = wildlife_st.wl
|
||||
return tb_within_of_recs(w.tab, w.grid, w.species, x, z, r, sp, out)
|
||||
}
|
||||
|
||||
# how many of a species are alive, legends included, without a walk
|
||||
export function wildlife_alive_of(wildlife_st: WildlifeState, sp: int) -> int { return ix_count(wildlife_st.wl.species, sp) }
|
||||
|
|
@ -4,6 +4,7 @@ export function wildlife_tick(wildlife_st: mut WildlifeState, dt: float, gh: flo
|
|||
wl_seed_scan(wildlife_st, dt)
|
||||
let all = wildlife_all(wildlife_st)
|
||||
for i in 0 .. len(all) { wl_tick_one(wildlife_st, all[i], dt, gh) }
|
||||
wildlife_refile(wildlife_st)
|
||||
}
|
||||
|
||||
function wl_tick_one(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float, gh: float) -> void {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export property WildSpecies {
|
|||
|
||||
export state WildlifeState {
|
||||
wl_facts: Queue<WildFact> = wl_facts__new()
|
||||
wl_own: []WildAnimal = null
|
||||
wl_own: WildTable = null # a guest's own animals, put away whole while it draws the host's
|
||||
wl_px: float = 0.0
|
||||
wl_pz: float = 0.0
|
||||
wl_ask: WildLure = new WildLure
|
||||
|
|
@ -53,7 +53,7 @@ export state WildlifeState {
|
|||
wl_fed_any: bool = false
|
||||
wl_near_i: int = 0
|
||||
wl_species: []WildSpecies = new []WildSpecies
|
||||
wl_list: []WildAnimal = new []WildAnimal
|
||||
wl: WildTable = wl__new()
|
||||
wl_keys: int = 0
|
||||
wl_dice: Rng = rng_new(97)
|
||||
wl_hold: bool = false # staged: nobody thinks, everybody keeps their speed
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
# store.ludic - the animals, their own dice, and the verbs that make and take one away
|
||||
const WL_TAU: float = 6.2831853
|
||||
|
||||
# every animal, one per row - the table's own list: read it, never push to it
|
||||
export function wildlife_all(wildlife_st: WildlifeState) -> []WildAnimal {
|
||||
return wildlife_st.wl_list
|
||||
return wildlife_st.wl.tab.rec
|
||||
}
|
||||
|
||||
export function wildlife_count_all(wildlife_st: WildlifeState) -> int { return len(wildlife_all(wildlife_st)) }
|
||||
export function wildlife_count_all(wildlife_st: WildlifeState) -> int { return tb_len(wildlife_st.wl.tab) }
|
||||
|
||||
# a new valley: every animal forgotten, quietly (the game has already let its drawings go)
|
||||
export function wildlife_clear(wildlife_st: mut WildlifeState) -> void { wildlife_st.wl_list = new []WildAnimal }
|
||||
export function wildlife_clear(wildlife_st: mut WildlifeState) -> void {
|
||||
let tb = wildlife_st.wl.tab
|
||||
for r in 0 .. tb_len(tb) { tb.rec[r].ent = -1 }
|
||||
tb_clear(tb)
|
||||
imap_clear(wildlife_st.wl.nids)
|
||||
}
|
||||
|
||||
function wl_rng(wildlife_st: WildlifeState) -> Rng {
|
||||
return wildlife_st.wl_dice
|
||||
|
|
@ -40,7 +46,7 @@ export function wildlife_make(wildlife_st: mut WildlifeState, sp: int, x: float,
|
|||
a.scale = scale
|
||||
a.legend = legend
|
||||
a.state = WILD_IDLE
|
||||
push(wildlife_all(wildlife_st), a)
|
||||
wl_file(wildlife_st.wl, a)
|
||||
WildlifeWorld.born(a)
|
||||
return a
|
||||
}
|
||||
|
|
@ -67,7 +73,7 @@ export function wildlife_new(wildlife_st: mut WildlifeState, sp: int, x: float,
|
|||
# gone for good: not drawn, not ticked
|
||||
export function wildlife_remove(a: WildAnimal) -> void {
|
||||
if a == null { return }
|
||||
a.alive = false
|
||||
wildlife_set_alive(a, false)
|
||||
a.shown = false
|
||||
}
|
||||
|
||||
|
|
@ -75,15 +81,16 @@ export function wildlife_remove(a: WildAnimal) -> void {
|
|||
export function wildlife_hold(wildlife_st: mut WildlifeState, on: bool) -> void { wildlife_st.wl_hold = on }
|
||||
|
||||
export function wildlife_by_nid(wildlife_st: WildlifeState, nid: int) -> WildAnimal {
|
||||
let all = wildlife_all(wildlife_st)
|
||||
for i in 0 .. len(all) { if all[i].nid == nid { return all[i] } }
|
||||
return null
|
||||
let tb = wildlife_st.wl.tab
|
||||
let r = tb_row(tb, imap_get(wildlife_st.wl.nids, nid, -1))
|
||||
if r < 0 or tb.rec[r].nid != nid { return null }
|
||||
return tb.rec[r]
|
||||
}
|
||||
|
||||
# how many of a species are alive, legends apart
|
||||
export function wildlife_count(wildlife_st: WildlifeState, sp: int) -> int {
|
||||
var n = 0
|
||||
let all = wildlife_all(wildlife_st)
|
||||
for i in 0 .. len(all) { if all[i].alive and all[i].sp == sp and not all[i].legend { n += 1 } }
|
||||
let rows = ix_rows(wildlife_st.wl.species, sp)
|
||||
for k in 0 .. len(rows) { if not wildlife_st.wl.tab.rec[rows[k]].legend { n += 1 } }
|
||||
return n
|
||||
}
|
||||
|
|
|
|||
91
packages/ludic.wildlife/table.ludic
Normal file
91
packages/ludic.wildlife/table.ludic
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# table.ludic - the animals, rows of a ludic.base Table: where each stands, its species and whether
|
||||
# it lives are columns, with a grid over the living, the living by species, and a nid to its handle.
|
||||
# The AI moves an animal by its record; wildlife_refile files every move once, after the tick.
|
||||
export const WA_X: int = 0 # float columns
|
||||
export const WA_Z: int = 1
|
||||
export const WA_SP: int = 0 # int columns
|
||||
export const WA_ALIVE: int = 1 # 1 while alive
|
||||
const WA_CELL: float = 32.0
|
||||
|
||||
export property WildTable {
|
||||
tab: Table<WildAnimal> = null
|
||||
grid: Grid = null
|
||||
species: IntIndex = null # the living, by species
|
||||
nids: IntMap = null
|
||||
}
|
||||
|
||||
function wl__new() -> WildTable {
|
||||
let w = new WildTable
|
||||
let tb: Table<WildAnimal> = table_new(2, 2)
|
||||
w.tab = tb
|
||||
w.grid = tb_grid(tb, WA_X, WA_Z, WA_ALIVE, WA_CELL)
|
||||
w.species = tb_index(tb, WA_SP, WA_ALIVE)
|
||||
w.nids = imap_new()
|
||||
return w
|
||||
}
|
||||
|
||||
function wl_row(a: WildAnimal) -> int {
|
||||
if a == null or a.tab == null { return -1 }
|
||||
return tb_row(a.tab, a.ent)
|
||||
}
|
||||
|
||||
# a new row for `a`, its columns from its record
|
||||
function wl_file(w: WildTable, a: WildAnimal) -> void {
|
||||
a.tab = w.tab
|
||||
a.ent = tb_add(w.tab, a)
|
||||
let r = wl_row(a)
|
||||
let tb = w.tab
|
||||
tb.f[WA_X][r] = a.x
|
||||
tb.f[WA_Z][r] = a.z
|
||||
tb.i[WA_SP][r] = a.sp
|
||||
var on = 0
|
||||
if a.alive { on = 1 }
|
||||
tb.i[WA_ALIVE][r] = on
|
||||
tb_refile(tb, r)
|
||||
if a.nid != 0 { imap_put(w.nids, a.nid, a.ent) }
|
||||
}
|
||||
|
||||
# alive or not, the record and the row together (a copy the game is handed says so too)
|
||||
export function wildlife_set_alive(a: WildAnimal, on: bool) -> void {
|
||||
if a == null { return }
|
||||
a.alive = on
|
||||
let r = wl_row(a)
|
||||
if r < 0 { return }
|
||||
var v = 0
|
||||
if on { v = 1 }
|
||||
tb_set_i(a.tab, WA_ALIVE, r, v)
|
||||
}
|
||||
|
||||
# every row whose record moved since it was filed, refiled: once after a tick that moves them all,
|
||||
# and by the game after it moves the copies a guest is handed
|
||||
export function wildlife_refile(wildlife_st: mut WildlifeState) -> void {
|
||||
let tb = wildlife_st.wl.tab
|
||||
let xs = tb.f[WA_X]
|
||||
let zs = tb.f[WA_Z]
|
||||
for r in 0 .. tb_len(tb) {
|
||||
let a = tb.rec[r]
|
||||
if xs[r] != a.x or zs[r] != a.z { tb_set_xz(tb, WA_X, WA_Z, r, a.x, a.z) }
|
||||
}
|
||||
}
|
||||
|
||||
# after a refile, the columns say what the records say; the count of rows that do not
|
||||
export function wildlife_verify(wildlife_st: WildlifeState) -> int {
|
||||
let tb = wildlife_st.wl.tab
|
||||
var bad = 0
|
||||
for r in 0 .. tb_len(tb) {
|
||||
let a = tb.rec[r]
|
||||
var on = 0
|
||||
if a.alive { on = 1 }
|
||||
if tb.f[WA_X][r] != a.x or tb.f[WA_Z][r] != a.z or tb.i[WA_SP][r] != a.sp or tb.i[WA_ALIVE][r] != on or a.ent != tb.ent[r] { bad += 1 }
|
||||
}
|
||||
return bad
|
||||
}
|
||||
|
||||
# the nearest living animal of a species (below 0: any) within maxr (0: any distance), or null
|
||||
export function wildlife_nearest(wildlife_st: WildlifeState, sp: int, x: float, z: float, maxr: float) -> WildAnimal {
|
||||
let w = wildlife_st.wl
|
||||
var r = -1
|
||||
if sp < 0 { r = tb_nearest(w.tab, w.grid, x, z, maxr, -1, 0) } else { r = tb_nearest_of(w.tab, w.grid, w.species, x, z, maxr, sp) }
|
||||
if r < 0 { return null }
|
||||
return w.tab.rec[r]
|
||||
}
|
||||
|
|
@ -230,4 +230,33 @@ program WildlifeTest {
|
|||
expect_near(r.x, r.wx, 0.001)
|
||||
expect(r.x != x0 or r.wx == r.sx)
|
||||
}
|
||||
|
||||
test "the table: a nid finds its animal, the living count by species, the nearest, and a refile" (wildlife_st: mut WildlifeState, wildlife_tests_fake_st: mut WildlifeTestsFakeState) {
|
||||
fk_setup(wildlife_st, wildlife_tests_fake_st)
|
||||
wildlife_clear(wildlife_st)
|
||||
let a = wildlife_make(wildlife_st, 0, 10.0, 0.0, false, 0.0, 0, 1.0, 41)
|
||||
let b = wildlife_make(wildlife_st, 0, 90.0, 0.0, false, 0.0, 0, 1.0, 42)
|
||||
let c = wildlife_make(wildlife_st, 1, 30.0, 0.0, true, 0.0, 0, 1.0, 43)
|
||||
expect(wildlife_by_nid(wildlife_st, 42) == b)
|
||||
expect(wildlife_by_nid(wildlife_st, 7) == null)
|
||||
expect_eq(wildlife_count(wildlife_st, 0), 2)
|
||||
expect_eq(wildlife_count(wildlife_st, 1), 0)
|
||||
expect(wildlife_nearest(wildlife_st, -1, 0.0, 0.0, 0.0) == a)
|
||||
expect(wildlife_nearest(wildlife_st, 1, 0.0, 0.0, 0.0) == c)
|
||||
let near = new []WildAnimal
|
||||
expect_eq(wildlife_near(wildlife_st, 0.0, 0.0, 40.0, near), 2)
|
||||
expect_eq(wildlife_near_of(wildlife_st, 0, 0.0, 0.0, 200.0, near), 2)
|
||||
expect_eq(wildlife_alive_of(wildlife_st, 1), 1)
|
||||
wildlife_remove(a)
|
||||
expect_eq(wildlife_count(wildlife_st, 0), 1)
|
||||
expect(wildlife_nearest(wildlife_st, 0, 0.0, 0.0, 0.0) == b)
|
||||
b.x = -5.0
|
||||
wildlife_refile(wildlife_st)
|
||||
expect(wildlife_nearest(wildlife_st, -1, 0.0, 0.0, 20.0) == b)
|
||||
expect_eq(wildlife_verify(wildlife_st), 0)
|
||||
expect_eq(wildlife_count_all(wildlife_st), 3)
|
||||
wildlife_clear(wildlife_st)
|
||||
expect_eq(wildlife_count_all(wildlife_st), 0)
|
||||
expect(wildlife_by_nid(wildlife_st, 42) == null)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ import "vk_api.ludic"
|
|||
# Vk.sl_active() says whether it was. Each Vk.sl_* is the sl* export of the same name and returns
|
||||
# its sl::Result (0 is eOk); -1 means there is no Streamline (macOS, or the DLL missing). The
|
||||
# structs are plain memory, laid out as the SDK headers (include/sl_*.h, SDK 2.14.1) declare them.
|
||||
# Once a frame: on macOS drains what MoltenVK and the layer autoreleased during the last one (a
|
||||
# program pumping its own events has no other pool); nothing elsewhere.
|
||||
extern function vk_frame_pool() = "lvk_frame_pool"
|
||||
extern function vk_sl_prefer(on: int) = "lvk_sl_prefer"
|
||||
extern function vk_sl_active() -> int = "lvk_sl_active"
|
||||
extern function vk_sl_init(pref: pointer, sdk_version: long) -> int = "lsl_slInit"
|
||||
|
|
|
|||
|
|
@ -225,3 +225,26 @@ entry:
|
|||
%r = call i64 %fn(ptr %a0, ptr %a1)
|
||||
ret i64 %r
|
||||
}
|
||||
|
||||
; ---- the frame's autorelease pool --------------------------------------------------------------
|
||||
; MoltenVK and CAMetalLayer autorelease objects on every frame (the drawable, each render pass's
|
||||
; descriptor), and a program that pumps its own events never drains a pool: without this they are
|
||||
; kept for the life of the process - 160 MB/s of small allocations in a windowed valley.
|
||||
; lvk_frame_pool() pops the pool the last frame pushed and pushes the next one.
|
||||
declare ptr @objc_autoreleasePoolPush()
|
||||
declare void @objc_autoreleasePoolPop(ptr)
|
||||
@lvk_pool = internal global ptr null
|
||||
|
||||
define void @lvk_frame_pool() {
|
||||
entry:
|
||||
%old = load ptr, ptr @lvk_pool
|
||||
%have = icmp ne ptr %old, null
|
||||
br i1 %have, label %pop, label %push
|
||||
pop:
|
||||
call void @objc_autoreleasePoolPop(ptr %old)
|
||||
br label %push
|
||||
push:
|
||||
%p = call ptr @objc_autoreleasePoolPush()
|
||||
store ptr %p, ptr @lvk_pool
|
||||
ret void
|
||||
}
|
||||
|
|
|
|||
|
|
@ -363,3 +363,9 @@ entry:
|
|||
%r = call i64 %fn(ptr %a0, ptr %a1)
|
||||
ret i64 %r
|
||||
}
|
||||
|
||||
; the frame's autorelease pool is a macOS matter (vk_mac.ll); Windows has nothing to drain
|
||||
define void @lvk_frame_pool() {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,9 +136,11 @@ function emit_component_access(prop: pointer, meth: pointer, e: Node) -> Val {
|
|||
let lt = emit_bind(`icmp slt i32 {i0}, {ec}`)
|
||||
emit(" br i1 "); emit(lt); emit(", label %"); emit(body); emit(", label %"); emit(endl); emit("\n")
|
||||
emit(body); emit(":\n")
|
||||
let ap = emit_bind(`getelementptr inbounds [{me} x i32], ptr @L_alive, i32 0, i32 {i0}`)
|
||||
let eb1 = ecs_base("L_alive")
|
||||
let ap = emit_bind(`getelementptr inbounds i32, ptr {eb1}, i32 {i0}`)
|
||||
let al = emit_bind(`load i32, ptr {ap}`)
|
||||
let hp = emit_bind(`getelementptr inbounds [{me} x i8], ptr @H_{prop}, i32 0, i32 {i0}`)
|
||||
let eb2 = ecs_base(`H_{prop}`)
|
||||
let hp = emit_bind(`getelementptr inbounds i8, ptr {eb2}, i32 {i0}`)
|
||||
let hv = emit_bind(`load i8, ptr {hp}`)
|
||||
let hz = emit_bind(`zext i8 {hv} to i32`)
|
||||
let both = emit_bind(`and i32 {al}, {hz}`)
|
||||
|
|
@ -154,15 +156,19 @@ function emit_component_access(prop: pointer, meth: pointer, e: Node) -> Val {
|
|||
if len(e.kids) != 1 { perr(`{prop}.{meth} takes one argument: the entity`) }
|
||||
let ev = emit_expr(e.kids[0])
|
||||
if (meth == "of") {
|
||||
let slot = emit_bind(`getelementptr inbounds [{me} x %Cmp_{prop}], ptr @S_{prop}, i32 0, i32 {ev.code}`)
|
||||
let eb3 = ecs_base(`S_{prop}`)
|
||||
let slot = emit_bind(`getelementptr inbounds %Cmp_{prop}, ptr {eb3}, i32 {ev.code}`)
|
||||
return val(slot, prop)
|
||||
}
|
||||
let in_range = emit_bind(`icmp ult i32 {ev.code}, {me}`)
|
||||
let cap = emit_bind("load i32, ptr @L_cap")
|
||||
let in_range = emit_bind(`icmp ult i32 {ev.code}, {cap}`)
|
||||
let idx = emit_bind(`select i1 {in_range}, i32 {ev.code}, i32 0`)
|
||||
let ap = emit_bind(`getelementptr inbounds [{me} x i32], ptr @L_alive, i32 0, i32 {idx}`)
|
||||
let eb4 = ecs_base("L_alive")
|
||||
let ap = emit_bind(`getelementptr inbounds i32, ptr {eb4}, i32 {idx}`)
|
||||
let alive = emit_bind(`load i32, ptr {ap}`)
|
||||
let is_alive = emit_bind(`icmp ne i32 {alive}, 0`)
|
||||
let hp = emit_bind(`getelementptr inbounds [{me} x i8], ptr @H_{prop}, i32 0, i32 {idx}`)
|
||||
let eb5 = ecs_base(`H_{prop}`)
|
||||
let hp = emit_bind(`getelementptr inbounds i8, ptr {eb5}, i32 {idx}`)
|
||||
let hv = emit_bind(`load i8, ptr {hp}`)
|
||||
let carries = emit_bind(`icmp ne i8 {hv}, 0`)
|
||||
let ok1 = emit_bind(`and i1 {in_range}, {is_alive}`)
|
||||
|
|
@ -372,7 +378,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
return emit_alias_or_fail("App", meth, e)
|
||||
}
|
||||
if (ns == "Pool") {
|
||||
if (meth == "capacity") { return val(itoa(MAX_ENT), "int") } # max entities
|
||||
if (meth == "capacity") { return val(emit_bind("load i32, ptr @L_cap"), "int") } # the stores' size now: they grow
|
||||
if (meth == "reserved") { return val(emit_bind("load i32, ptr @L_entc"), "int") } # slots ever allocated (high-water)
|
||||
if (meth == "free") { return val(emit_bind("load i32, ptr @L_freen"), "int") } # recycled slots ready for reuse
|
||||
if (meth == "live") { # currently alive = reserved - free
|
||||
|
|
@ -407,7 +413,8 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
let at = emit_expr(e.kids[1])
|
||||
let ent = emit_bind(`call i32 @L_spawn_prefab(ptr {nm.code})`)
|
||||
let me = itoa(MAX_ENT)
|
||||
let slot = emit_bind(`getelementptr inbounds [{me} x %Cmp_Position], ptr @S_Position, i32 0, i32 {ent}`)
|
||||
let eb6 = ecs_base(`S_Position`)
|
||||
let slot = emit_bind(`getelementptr inbounds %Cmp_Position, ptr {eb6}, i32 {ent}`)
|
||||
let xa = emit_bind(`getelementptr inbounds %Cmp_Position, ptr {slot}, i32 0, i32 {itoa(field_index(pos, "x"))}`)
|
||||
let ya = emit_bind(`getelementptr inbounds %Cmp_Position, ptr {slot}, i32 0, i32 {itoa(field_index(pos, "y"))}`)
|
||||
let ax = vec_x(at.code)
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ function emit_main(d: Node) -> void {
|
|||
# its framebuffer allocated. Headless it only allocates — no window, no output —
|
||||
# so a non-rendering entry game is unchanged.
|
||||
emit(" call void @L_init_runtime()\n")
|
||||
if has_ecs() { emit(" call void @L_grow(i32 1)\n") }
|
||||
if (find_fn("rt_init") != null) { emit(" call void " + fn_sym("rt_init") + "()\n") }
|
||||
emit(" call void @L_init_globals()\n")
|
||||
emit_block(d.a)
|
||||
|
|
@ -149,6 +150,7 @@ function emit_test_runner() -> void {
|
|||
emit(" ret i32 0\n")
|
||||
emit(`{lrun0}:\n`)
|
||||
emit(" call void @L_init_runtime()\n")
|
||||
if has_ecs() { emit(" call void @L_grow(i32 1)\n") }
|
||||
if has_ecs() { emit(" call void @rt_init()\n") }
|
||||
emit(" call void @L_init_globals()\n")
|
||||
i = 0
|
||||
|
|
|
|||
|
|
@ -185,4 +185,11 @@ function check_worker_ref(e: Node) -> void {
|
|||
var ok = len(d.kids) == s + 2 and llty(d.ty) == "void"
|
||||
if ok { ok = llty(d.kids[s].ty) == "i32" and llty(d.kids[s + 1].ty) == "ptr" }
|
||||
if not ok { perr(`fn {e.s}: a worker function takes (i: int, ctx: pointer) and returns nothing`) }
|
||||
# every thread runs it at once: a state it is handed is read by all of them, so none may change one
|
||||
var k = 0
|
||||
while k < s {
|
||||
let p = d.kids[k]
|
||||
if p.uns == 1 { perr(`fn {e.s}: a worker runs on every core at once, so it may read {p.ty} but not change it ({p.s}: mut {p.ty}); write results into ctx, shared counts through Sync.add`) }
|
||||
k += 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,9 +56,12 @@ function emit_ecs_storage() -> void {
|
|||
if len(g_scenes) > 0 { emith("@L_scene = internal global i32 0\n") } # active base scene id
|
||||
emith("@L_entc = internal global i32 0\n")
|
||||
let me = itoa(MAX_ENT)
|
||||
emith(`@L_alive = internal global [{me} x i32] zeroinitializer\n`)
|
||||
emith(`@L_kind = internal global [{me} x i32] zeroinitializer\n`)
|
||||
emith(`@L_freelist = internal global [{me} x i32] zeroinitializer\n`)
|
||||
# the per-entity stores are heap blocks L_grow makes and doubles: @L_cap slots each, from MAX_ENT
|
||||
emith("@L_cap = internal global i32 0\n")
|
||||
emith("@L_snapcap = internal global i32 0\n") # a snapshot's slot count, read before its stores
|
||||
emith("@L_alive = internal global ptr null\n")
|
||||
emith("@L_kind = internal global ptr null\n")
|
||||
emith("@L_freelist = internal global ptr null\n")
|
||||
emith("@L_freen = internal global i32 0\n")
|
||||
# NETWORKING role registers (N3/N5): a runtime sets these; offline they hold the
|
||||
# single-player default — @L_role=1 (this peer is the authority), local id 0.
|
||||
|
|
@ -68,7 +71,7 @@ function emit_ecs_storage() -> void {
|
|||
emith("@L_role = internal global i32 1\n") # 1 = server/authority (offline default)
|
||||
emith("@L_localid = internal global i32 0\n") # this peer's id
|
||||
}
|
||||
if net_has_owned() { emith(`@L_owner_arr = internal global [{me} x i32] zeroinitializer\n`) }
|
||||
if net_has_owned() { emith("@L_owner_arr = internal global ptr null\n") }
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let c = prog[i]
|
||||
|
|
@ -76,8 +79,8 @@ function emit_ecs_storage() -> void {
|
|||
# header). Every property in an ECS program is a component today; a property
|
||||
# used only via `new` would not need these, but no such program mixes the two.
|
||||
if c.kind == N_COMP {
|
||||
emith(`@S_{c.s} = internal global [{me} x %Cmp_{c.s}] zeroinitializer\n`)
|
||||
emith(`@H_{c.s} = internal global [{me} x i8] zeroinitializer\n`)
|
||||
emith(`@S_{c.s} = internal global ptr null\n`)
|
||||
emith(`@H_{c.s} = internal global ptr null\n`)
|
||||
}
|
||||
# one enabled-flag global per model and per handler (default enabled)
|
||||
if c.kind == N_ARCH { emith(`@ME_{c.s} = internal global i32 1\n`) }
|
||||
|
|
@ -92,21 +95,25 @@ function emit_ecs_storage() -> void {
|
|||
# L_reset(e): clear every has-flag and the archetype kind for entity e
|
||||
function emit_ecs_allocator() -> void {
|
||||
let me = itoa(MAX_ENT)
|
||||
emit_ecs_grow()
|
||||
emit("define void @L_reset(i32 %e) {\nentry:\n")
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP {
|
||||
let hn = `%h{itoa(i)}`
|
||||
emit(" "); emit(hn); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(prog[i].s); emit(", i32 0, i32 %e\n")
|
||||
let eb1 = ecs_base(`H_{prog[i].s}`)
|
||||
emit(" "); emit(hn); emit(" = getelementptr inbounds i8, ptr "); emit(eb1); emit(", i32 %e\n")
|
||||
emit(" store i8 0, ptr "); emit(hn); emit("\n")
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
emit(" %k = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
|
||||
let eb2 = ecs_base("L_kind")
|
||||
emit(" %k = getelementptr inbounds i32, ptr "); emit(eb2); emit(", i32 %e\n")
|
||||
emit(" store i32 0, ptr %k\n")
|
||||
# N3: reset an @Owned entity's network owner to -1 (unowned) on alloc/free
|
||||
if net_has_owned() {
|
||||
emit(" %ow = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n")
|
||||
let eb3 = ecs_base("L_owner_arr")
|
||||
emit(" %ow = getelementptr inbounds i32, ptr "); emit(eb3); emit(", i32 %e\n")
|
||||
emit(" store i32 -1, ptr %ow\n")
|
||||
}
|
||||
emit(" ret void\n}\n\n")
|
||||
|
|
@ -118,29 +125,100 @@ function emit_ecs_allocator() -> void {
|
|||
emit("reuse:\n")
|
||||
emit(" %fn1 = sub i32 %fn, 1\n")
|
||||
emit(" store i32 %fn1, ptr @L_freen\n")
|
||||
emit(" %fp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_freelist, i32 0, i32 %fn1\n")
|
||||
let eb4 = ecs_base("L_freelist")
|
||||
emit(" %fp = getelementptr inbounds i32, ptr "); emit(eb4); emit(", i32 %fn1\n")
|
||||
emit(" %re = load i32, ptr %fp\n")
|
||||
emit(" br label %done\n")
|
||||
emit("fresh:\n")
|
||||
emit(" %ec = load i32, ptr @L_entc\n")
|
||||
emit(" %ec1 = add i32 %ec, 1\n")
|
||||
emit(" call void @L_grow(i32 %ec1)\n") # a slot past the stores doubles them first
|
||||
emit(" store i32 %ec1, ptr @L_entc\n")
|
||||
emit(" br label %done\n")
|
||||
emit("done:\n")
|
||||
emit(" %e = phi i32 [ %re, %reuse ], [ %ec, %fresh ]\n")
|
||||
emit(" call void @L_reset(i32 %e)\n")
|
||||
emit(" %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
|
||||
let eb5 = ecs_base("L_alive")
|
||||
emit(" %ap = getelementptr inbounds i32, ptr "); emit(eb5); emit(", i32 %e\n")
|
||||
emit(" store i32 1, ptr %ap\n")
|
||||
emit(" ret i32 %e\n}\n\n")
|
||||
|
||||
emit("define void @L_free_entity(i32 %e) {\nentry:\n")
|
||||
emit(" %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
|
||||
let eb6 = ecs_base("L_alive")
|
||||
emit(" %ap = getelementptr inbounds i32, ptr "); emit(eb6); emit(", i32 %e\n")
|
||||
emit(" store i32 0, ptr %ap\n")
|
||||
emit(" call void @L_reset(i32 %e)\n")
|
||||
emit(" %fn = load i32, ptr @L_freen\n")
|
||||
emit(" %fp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_freelist, i32 0, i32 %fn\n")
|
||||
let eb7 = ecs_base("L_freelist")
|
||||
emit(" %fp = getelementptr inbounds i32, ptr "); emit(eb7); emit(", i32 %fn\n")
|
||||
emit(" store i32 %e, ptr %fp\n")
|
||||
emit(" %fn1 = add i32 %fn, 1\n")
|
||||
emit(" store i32 %fn1, ptr @L_freen\n")
|
||||
emit(" ret void\n}\n\n")
|
||||
}
|
||||
|
||||
# the base of a per-entity store, loaded where it is indexed (a store moves when L_grow doubles it)
|
||||
function ecs_base(store: pointer) -> pointer {
|
||||
let r = `%ecsb{itoa(ll_t)}` # its own prefix: raw functions name blocks t0, t1, ...
|
||||
ll_t += 1
|
||||
emit(` {r} = load ptr, ptr @{store}\n`)
|
||||
return r
|
||||
}
|
||||
|
||||
# L_grow(need): every per-entity store holds at least `need` slots - doubled from MAX_ENT as the
|
||||
# entities outgrow them, the new slots zero, the mods' registered stores with them. Nothing is
|
||||
# ever moved back down: an entity's slot is its handle for its whole life.
|
||||
function emit_ecs_grow() -> void {
|
||||
emit("define void @L_grow(i32 %need) {\nentry:\n %cap = load i32, ptr @L_cap\n")
|
||||
emit(" %ok = icmp sle i32 %need, %cap\n br i1 %ok, label %done, label %grow\ngrow:\n")
|
||||
emit(` %c0 = icmp eq i32 %cap, 0\n %start = select i1 %c0, i32 {itoa(MAX_ENT)}, i32 %cap\n br label %dbl\n`)
|
||||
emit("dbl:\n %nc = phi i32 [ %start, %grow ], [ %nc2, %dbl2 ]\n %enough = icmp sge i32 %nc, %need\n")
|
||||
emit(" br i1 %enough, label %apply, label %dbl2\ndbl2:\n %nc2 = mul i32 %nc, 2\n br label %dbl\n")
|
||||
emit("apply:\n %cap64 = sext i32 %cap to i64\n %nc64 = sext i32 %nc to i64\n")
|
||||
emit_grow_store("L_alive", "4", 0)
|
||||
emit_grow_store("L_kind", "4", 1)
|
||||
emit_grow_store("L_freelist", "4", 2)
|
||||
if net_has_owned() { emit_grow_store("L_owner_arr", "4", 3) }
|
||||
var k = 4
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP {
|
||||
let sz = `%gs{itoa(k)}`
|
||||
emit(` {sz} = ptrtoint ptr getelementptr (%Cmp_{prog[i].s}, ptr null, i32 1) to i64\n`)
|
||||
emit_grow_store(`S_{prog[i].s}`, sz, k)
|
||||
emit_grow_store(`H_{prog[i].s}`, "1", k + 1)
|
||||
k += 2
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
emit_grow_dyn()
|
||||
emit("fin:\n store i32 %nc, ptr @L_cap\n br label %done\ndone:\n ret void\n}\n\n")
|
||||
}
|
||||
|
||||
# one store to nc slots of `esz` bytes: reallocated (8 bytes spare, so a field-less tag is never
|
||||
# a zero-byte block), its new tail zeroed
|
||||
function emit_grow_store(store: pointer, esz: pointer, k: int) -> void {
|
||||
let n = itoa(k)
|
||||
emit(` %gp{n} = load ptr, ptr @{store}\n %gb{n} = mul i64 %nc64, {esz}\n %gr{n} = add i64 %gb{n}, 8\n`)
|
||||
emit(` %gn{n} = call ptr @realloc(ptr %gp{n}, i64 %gr{n})\n %go{n} = mul i64 %cap64, {esz}\n`)
|
||||
emit(` %gt{n} = getelementptr i8, ptr %gn{n}, i64 %go{n}\n %gz{n} = sub i64 %gr{n}, %go{n}\n`)
|
||||
emit(` %gm{n} = call ptr @memset(ptr %gt{n}, i32 0, i64 %gz{n})\n store ptr %gn{n}, ptr @{store}\n`)
|
||||
}
|
||||
|
||||
# the stores a mod registered at run time (ludic_register_prop), grown the same way
|
||||
function emit_grow_dyn() -> void {
|
||||
emit(" br label %dynh\ndynh:\n %di = phi i32 [ 0, %apply ], [ %di1, %dynb ]\n %dcn = load i32, ptr @dyn_count\n")
|
||||
emit(" %dmore = icmp slt i32 %di, %dcn\n br i1 %dmore, label %dynb, label %fin\ndynb:\n")
|
||||
emit(" %dfp = getelementptr inbounds [32 x i32], ptr @dynF, i32 0, i32 %di\n %dnf = load i32, ptr %dfp\n")
|
||||
emit(" %dnf4 = mul i32 %dnf, 4\n %desz = sext i32 %dnf4 to i64\n")
|
||||
emit_grow_slot("dynS", "%desz", "s")
|
||||
emit_grow_slot("dynH", "1", "h")
|
||||
emit(" %di1 = add i32 %di, 1\n br label %dynh\n")
|
||||
}
|
||||
|
||||
function emit_grow_slot(table: pointer, esz: pointer, t: pointer) -> void {
|
||||
emit(` %d{t}p = getelementptr inbounds [32 x ptr], ptr @{table}, i32 0, i32 %di\n %d{t}o = load ptr, ptr %d{t}p\n`)
|
||||
emit(` %d{t}b = mul i64 %nc64, {esz}\n %d{t}r = add i64 %d{t}b, 8\n %d{t}n = call ptr @realloc(ptr %d{t}o, i64 %d{t}r)\n`)
|
||||
emit(` %d{t}f = mul i64 %cap64, {esz}\n %d{t}t = getelementptr i8, ptr %d{t}n, i64 %d{t}f\n %d{t}z = sub i64 %d{t}r, %d{t}f\n`)
|
||||
emit(` %d{t}m = call ptr @memset(ptr %d{t}t, i32 0, i64 %d{t}z)\n store ptr %d{t}n, ptr %d{t}p\n`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,8 @@ function emit_countdown_system() -> void {
|
|||
emit(" %n = load i32, ptr @L_entc\n br label %loop\nloop:\n")
|
||||
emit(" %i = phi i32 [ 0, %entry ], [ %i1, %next ]\n")
|
||||
emit(" %go = icmp slt i32 %i, %n\n br i1 %go, label %body, label %done\nbody:\n")
|
||||
emit(" %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %i\n")
|
||||
let eb1 = ecs_base("L_alive")
|
||||
emit(" %ap = getelementptr inbounds i32, ptr "); emit(eb1); emit(", i32 %i\n")
|
||||
emit(" %al = load i32, ptr %ap\n %alive = icmp ne i32 %al, 0\n br i1 %alive, label %c0, label %next\n")
|
||||
var k = 0
|
||||
var i = 0
|
||||
|
|
@ -106,12 +107,14 @@ function emit_countdown_system() -> void {
|
|||
if any {
|
||||
let sk = itoa(k); let nk = itoa(k + 1)
|
||||
emit("c"); emit(sk); emit(":\n")
|
||||
emit(" %h"); emit(sk); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(d.s); emit(", i32 0, i32 %i\n")
|
||||
let eb2 = ecs_base(`H_{d.s}`)
|
||||
emit(" %h"); emit(sk); emit(" = getelementptr inbounds i8, ptr "); emit(eb2); emit(", i32 %i\n")
|
||||
emit(" %hv"); emit(sk); emit(" = load i8, ptr %h"); emit(sk); emit("\n")
|
||||
emit(" %has"); emit(sk); emit(" = icmp ne i8 %hv"); emit(sk); emit(", 0\n")
|
||||
emit(" br i1 %has"); emit(sk); emit(", label %t"); emit(sk); emit(", label %c"); emit(nk); emit("\n")
|
||||
emit("t"); emit(sk); emit(":\n")
|
||||
emit(" %s"); emit(sk); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(d.s); emit("], ptr @S_"); emit(d.s); emit(", i32 0, i32 %i\n")
|
||||
let eb3 = ecs_base(`S_{d.s}`)
|
||||
emit(" %s"); emit(sk); emit(" = getelementptr inbounds %Cmp_"); emit(d.s); emit(", ptr "); emit(eb3); emit(", i32 %i\n")
|
||||
f = 0
|
||||
while f < len(d.kids) {
|
||||
if (d.kids[f].ty == "countdown") {
|
||||
|
|
@ -474,7 +477,8 @@ function emit_world_despawn_fn() -> void {
|
|||
emit("define void @fn_world_despawn(i32 %e) {\nentry:\n")
|
||||
if len(g_ondespawn) > 0 {
|
||||
let me = itoa(MAX_ENT)
|
||||
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
|
||||
let eb4 = ecs_base("L_kind")
|
||||
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb4); emit(", i32 %e\n")
|
||||
emit(" %k = load i32, ptr %kp\n")
|
||||
var i = 0
|
||||
while i < len(g_ondespawn) {
|
||||
|
|
@ -508,10 +512,14 @@ function emit_despawn_all_fn() -> void {
|
|||
emit("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %cont ]\n")
|
||||
emit(" %n = load i32, ptr @L_entc\n %go = icmp slt i32 %i, %n\n")
|
||||
emit(" br i1 %go, label %body, label %fin\n")
|
||||
emit("body:\n %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %i\n")
|
||||
emit("body:\n")
|
||||
let eb5 = ecs_base("L_alive")
|
||||
emit(" %ap = getelementptr inbounds i32, ptr "); emit(eb5); emit(", i32 %i\n")
|
||||
emit(" %al = load i32, ptr %ap\n %isa = icmp ne i32 %al, 0\n")
|
||||
emit(" br i1 %isa, label %do, label %cont\n")
|
||||
emit("do:\n %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %i\n")
|
||||
emit("do:\n")
|
||||
let eb6 = ecs_base("L_kind")
|
||||
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb6); emit(", i32 %i\n")
|
||||
emit(" %k = load i32, ptr %kp\n")
|
||||
var i = 0
|
||||
while i < len(g_ondespawn) {
|
||||
|
|
@ -608,7 +616,8 @@ function emit_event_fns() -> void {
|
|||
emit(" %n = load i32, ptr @evN_"); emit(en); emit("\n")
|
||||
emit(" %lo = icmp sge i32 %tok, 0\n %hi = icmp slt i32 %tok, %n\n %ok = and i1 %lo, %hi\n")
|
||||
emit(" br i1 %ok, label %do, label %skip\n")
|
||||
emit("do:\n %slot = getelementptr inbounds ["); emit(cap); emit(" x ptr], ptr @evL_"); emit(en); emit(", i32 0, i32 %tok\n")
|
||||
emit("do:\n")
|
||||
emit(" %slot = getelementptr inbounds ["); emit(cap); emit(" x ptr], ptr @evL_"); emit(en); emit(", i32 0, i32 %tok\n")
|
||||
emit(" store ptr null, ptr %slot\n br label %skip\n")
|
||||
emit("skip:\n ret void\n}\n\n")
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ function emit_query(st: Node) -> void {
|
|||
emit(body); emit(":\n"); g_term = false
|
||||
let i1 = emit_bind(`load i32, ptr {ip}`)
|
||||
# alive?
|
||||
let ap = nreg(); emit(" "); emit(ap); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 "); emit(i1); emit("\n")
|
||||
let eb1 = ecs_base("L_alive")
|
||||
let ap = nreg(); emit(" "); emit(ap); emit(" = getelementptr inbounds i32, ptr "); emit(eb1); emit(", i32 "); emit(i1); emit("\n")
|
||||
let al = emit_bind(`load i32, ptr {ap}`)
|
||||
let alc = emit_bind(`icmp ne i32 {al}, 0`)
|
||||
let ka = lbl("qa")
|
||||
|
|
@ -40,14 +41,16 @@ function emit_query(st: Node) -> void {
|
|||
let ak = find_arch_id(tm.s)
|
||||
var ok: pointer = "0"
|
||||
if ak > 0 {
|
||||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(i1); emit("\n")
|
||||
let eb2 = ecs_base("L_kind")
|
||||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds i32, ptr "); emit(eb2); emit(", i32 "); emit(i1); emit("\n")
|
||||
let kv = emit_bind(`load i32, ptr {kp}`)
|
||||
let kok = emit_bind(`icmp eq i32 {kv}, {itoa(ak)}`)
|
||||
let mev = emit_bind(`load i32, ptr @ME_{tm.s}`) # model-enabled flag
|
||||
let meok = emit_bind(`icmp ne i32 {mev}, 0`)
|
||||
ok = emit_bind(`and i1 {kok}, {meok}`)
|
||||
} else {
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(tm.s); emit(", i32 0, i32 "); emit(i1); emit("\n")
|
||||
let eb3 = ecs_base(`H_{tm.s}`)
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds i8, ptr "); emit(eb3); emit(", i32 "); emit(i1); emit("\n")
|
||||
let hv = emit_bind(`load i8, ptr {hp}`)
|
||||
ok = emit_bind(`icmp ne i8 {hv}, 0`)
|
||||
}
|
||||
|
|
@ -66,7 +69,8 @@ function emit_query(st: Node) -> void {
|
|||
if tm.ival == 0 and find_arch_id(tm.s) == 0 {
|
||||
if vi < len(st.kids) {
|
||||
let slot = nreg()
|
||||
emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(tm.s); emit("], ptr @S_"); emit(tm.s); emit(", i32 0, i32 "); emit(i1); emit("\n")
|
||||
let eb4 = ecs_base(`S_{tm.s}`)
|
||||
emit(" "); emit(slot); emit(" = getelementptr inbounds %Cmp_"); emit(tm.s); emit(", ptr "); emit(eb4); emit(", i32 "); emit(i1); emit("\n")
|
||||
let vslot = emit_alloca("ptr")
|
||||
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
|
||||
loc_push(st.kids[vi].s, vslot, tm.s)
|
||||
|
|
|
|||
|
|
@ -46,16 +46,23 @@ function emit_io(fn2: pointer, p: pointer, bytes: pointer) -> void {
|
|||
function emit_snapshot_blocks(fn2: pointer) -> void {
|
||||
g_iok = 0
|
||||
g_off = "0"
|
||||
let me = itoa(MAX_ENT)
|
||||
# the stores grow, so a snapshot says how many slots it holds first; a load grows to it before
|
||||
# reading them back
|
||||
let reading = (fn2 == "fread") or (g_snap_mode == "load")
|
||||
if not reading { emit(" %sncap0 = load i32, ptr @L_cap\n store i32 %sncap0, ptr @L_snapcap\n") }
|
||||
emit_io(fn2, "@L_snapcap", "4")
|
||||
emit(" %sncap = load i32, ptr @L_snapcap\n")
|
||||
if reading { emit(" call void @L_grow(i32 %sncap)\n") }
|
||||
emit(" %sncap64 = sext i32 %sncap to i64\n")
|
||||
emit_io(fn2, "@L_entc", "4")
|
||||
emit_io(fn2, "@L_freen", "4")
|
||||
emit(" %nalive = mul i64 "); emit(me); emit(", 4\n")
|
||||
emit_io(fn2, "@L_alive", "%nalive")
|
||||
emit_io(fn2, "@L_freelist", "%nalive")
|
||||
emit_io(fn2, "@L_kind", "%nalive")
|
||||
emit(" %nalive = mul i64 %sncap64, 4\n")
|
||||
emit_io(fn2, snap_base("L_alive", 0), "%nalive")
|
||||
emit_io(fn2, snap_base("L_freelist", 1), "%nalive")
|
||||
emit_io(fn2, snap_base("L_kind", 2), "%nalive")
|
||||
# N3: an @Owned world snapshots its per-entity owners too, so rollback/replication
|
||||
# round-trips ownership (like @L_kind). Gated, so non-@Owned snapshots are unchanged.
|
||||
if net_has_owned() { emit_io(fn2, "@L_owner_arr", "%nalive") }
|
||||
if net_has_owned() { emit_io(fn2, snap_base("L_owner_arr", 3), "%nalive") }
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_VAR { emit_io(fn2, `@g_{prog[i].s}`, "4") }; i += 1 }
|
||||
var ci = 0
|
||||
|
|
@ -63,16 +70,25 @@ function emit_snapshot_blocks(fn2: pointer) -> void {
|
|||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP {
|
||||
let c = prog[i].s
|
||||
let csz1 = `%csz1_{itoa(ci)}`
|
||||
let csz = `%csz{itoa(ci)}`
|
||||
emit(" "); emit(csz); emit(" = ptrtoint ptr getelementptr (%Cmp_"); emit(c); emit(", ptr null, i32 "); emit(me); emit(") to i64\n")
|
||||
emit_io(fn2, `@S_{c}`, csz)
|
||||
emit_io(fn2, `@H_{c}`, me)
|
||||
emit(" "); emit(csz1); emit(" = ptrtoint ptr getelementptr (%Cmp_"); emit(c); emit(", ptr null, i32 1) to i64\n")
|
||||
emit(" "); emit(csz); emit(" = mul i64 %sncap64, "); emit(csz1); emit("\n")
|
||||
emit_io(fn2, snap_base(`S_{c}`, 4 + ci * 2), csz)
|
||||
emit_io(fn2, snap_base(`H_{c}`, 5 + ci * 2), "%sncap64")
|
||||
ci += 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
# a store's base, loaded after any grow the snapshot made
|
||||
function snap_base(store: pointer, k: int) -> pointer {
|
||||
let r = `%snb{itoa(k)}`
|
||||
emit(` {r} = load ptr, ptr @{store}\n`)
|
||||
return r
|
||||
}
|
||||
|
||||
function emit_snapshot() -> void {
|
||||
g_snap_mode = "file" # seed (module ptr inits are null)
|
||||
emith("@.sav_path = private unnamed_addr constant [10 x i8] c\"ludic.sav\\00\"\n")
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ function emit_init_component(e: pointer, comp: pointer, rec: Node) -> void {
|
|||
let me = itoa(MAX_ENT)
|
||||
let c = find_comp(comp)
|
||||
if (c == null) { perr(`spawn: unknown property {comp}`) }
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(comp); emit(", i32 0, i32 "); emit(e); emit("\n")
|
||||
let eb1 = ecs_base(`H_{comp}`)
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds i8, ptr "); emit(eb1); emit(", i32 "); emit(e); emit("\n")
|
||||
emit(" store i8 1, ptr "); emit(hp); emit("\n")
|
||||
let slot = nreg()
|
||||
emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(comp); emit("], ptr @S_"); emit(comp); emit(", i32 0, i32 "); emit(e); emit("\n")
|
||||
let eb2 = ecs_base(`S_{comp}`)
|
||||
emit(" "); emit(slot); emit(" = getelementptr inbounds %Cmp_"); emit(comp); emit(", ptr "); emit(eb2); emit(", i32 "); emit(e); emit("\n")
|
||||
# defaults
|
||||
var f = 0
|
||||
while f < len(c.kids) {
|
||||
|
|
@ -71,7 +73,8 @@ function emit_bind_props(model: Node, e: pointer) -> void {
|
|||
while c < len(model.kids) {
|
||||
let pname = model.kids[c].s
|
||||
let slot = nreg()
|
||||
emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(pname); emit("], ptr @S_"); emit(pname); emit(", i32 0, i32 "); emit(e); emit("\n")
|
||||
let eb3 = ecs_base(`S_{pname}`)
|
||||
emit(" "); emit(slot); emit(" = getelementptr inbounds %Cmp_"); emit(pname); emit(", ptr "); emit(eb3); emit(", i32 "); emit(e); emit("\n")
|
||||
let vslot = emit_alloca("ptr")
|
||||
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
|
||||
loc_push(pname, vslot, pname)
|
||||
|
|
@ -146,7 +149,8 @@ function emit_spawn(st: Node) -> pointer {
|
|||
if (find_prefab(st.s) != null) and (ak == 0) { perr(`prefab {st.s}: unknown model {model}`) }
|
||||
if ak > 0 {
|
||||
let me = itoa(MAX_ENT)
|
||||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(e); emit("\n")
|
||||
let eb4 = ecs_base("L_kind")
|
||||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds i32, ptr "); emit(eb4); emit(", i32 "); emit(e); emit("\n")
|
||||
emit(" store i32 "); emit(itoa(ak)); emit(", ptr "); emit(kp); emit("\n")
|
||||
let arch = find_arch(model)
|
||||
var c = 0
|
||||
|
|
@ -179,7 +183,8 @@ function emit_despawn(st: Node) -> void {
|
|||
# @OnDespawn: dispatch on the entity's kind and run the matching model's hook
|
||||
if len(g_ondespawn) > 0 {
|
||||
let me = itoa(MAX_ENT)
|
||||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(v.code); emit("\n")
|
||||
let eb5 = ecs_base("L_kind")
|
||||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds i32, ptr "); emit(eb5); emit(", i32 "); emit(v.code); emit("\n")
|
||||
let kind = emit_bind(`load i32, ptr {kp}`)
|
||||
var i = 0
|
||||
while i < len(g_ondespawn) {
|
||||
|
|
@ -205,7 +210,8 @@ function emit_despawn(st: Node) -> void {
|
|||
function emit_attach(st: Node) -> void {
|
||||
let ev = emit_expr(st.a)
|
||||
let me = itoa(MAX_ENT)
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
|
||||
let eb6 = ecs_base(`H_{st.s}`)
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds i8, ptr "); emit(eb6); emit(", i32 "); emit(ev.code); emit("\n")
|
||||
let cur = emit_bind(`load i8, ptr {hp}`)
|
||||
let isnew = emit_bind(`icmp eq i8 {cur}, 0`)
|
||||
let doit = lbl("attach"); let done = lbl("attdone")
|
||||
|
|
@ -223,7 +229,8 @@ function emit_attach(st: Node) -> void {
|
|||
function emit_detach(st: Node) -> void {
|
||||
let ev = emit_expr(st.a)
|
||||
let me = itoa(MAX_ENT)
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
|
||||
let eb7 = ecs_base(`H_{st.s}`)
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds i8, ptr "); emit(eb7); emit(", i32 "); emit(ev.code); emit("\n")
|
||||
let cur = emit_bind(`load i8, ptr {hp}`)
|
||||
let here = emit_bind(`icmp ne i8 {cur}, 0`)
|
||||
let doit = lbl("detach"); let done = lbl("detdone")
|
||||
|
|
@ -233,7 +240,8 @@ function emit_detach(st: Node) -> void {
|
|||
let hb = ondetach_body(st.s) # @OnDetach reads the outgoing value
|
||||
if (hb != null) {
|
||||
let save = nloc
|
||||
let slot = nreg(); emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(st.s); emit("], ptr @S_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
|
||||
let eb8 = ecs_base(`S_{st.s}`)
|
||||
let slot = nreg(); emit(" "); emit(slot); emit(" = getelementptr inbounds %Cmp_"); emit(st.s); emit(", ptr "); emit(eb8); emit(", i32 "); emit(ev.code); emit("\n")
|
||||
let vslot = emit_alloca("ptr")
|
||||
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
|
||||
loc_push(st.s, vslot, st.s)
|
||||
|
|
@ -266,13 +274,15 @@ function emit_toggle(st: Node) -> void {
|
|||
if (st.a != null) {
|
||||
let ev = emit_expr(st.a)
|
||||
let me = itoa(MAX_ENT)
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
|
||||
let eb9 = ecs_base(`H_{st.s}`)
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds i8, ptr "); emit(eb9); emit(", i32 "); emit(ev.code); emit("\n")
|
||||
emit(" store i8 "); emit(val); emit(", ptr "); emit(hp); emit("\n")
|
||||
var hb: Node = null
|
||||
if st.ival == 1 { hb = onenable_body(st.s) } else { hb = ondisable_body(st.s) }
|
||||
if (hb != null) {
|
||||
let save = nloc
|
||||
let slot = nreg(); emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(st.s); emit("], ptr @S_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
|
||||
let eb10 = ecs_base(`S_{st.s}`)
|
||||
let slot = nreg(); emit(" "); emit(slot); emit(" = getelementptr inbounds %Cmp_"); emit(st.s); emit(", ptr "); emit(eb10); emit(", i32 "); emit(ev.code); emit("\n")
|
||||
let vslot = emit_alloca("ptr")
|
||||
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
|
||||
loc_push(st.s, vslot, st.s)
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ function emit_world_table() -> void {
|
|||
emit(" %m"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
|
||||
emit(" br i1 %m"); emit(sk); emit(", label %g"); emit(sk); emit(", label %gn"); emit(sk); emit("\n")
|
||||
emit("g"); emit(sk); emit(":\n")
|
||||
emit(" %s"); emit(sk); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(cn); emit("], ptr @S_"); emit(cn); emit(", i32 0, i32 %e\n")
|
||||
let eb1 = ecs_base(`S_{cn}`)
|
||||
emit(" %s"); emit(sk); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(eb1); emit(", i32 %e\n")
|
||||
var fj = 0
|
||||
while fj < len(c.kids) {
|
||||
let ft = llty(c.kids[fj].ty); let fk = `{sk}_{itoa(fj)}`
|
||||
|
|
@ -139,7 +140,8 @@ function emit_world_table() -> void {
|
|||
emit(" %m"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
|
||||
emit(" br i1 %m"); emit(sk); emit(", label %g"); emit(sk); emit(", label %gn"); emit(sk); emit("\n")
|
||||
emit("g"); emit(sk); emit(":\n")
|
||||
emit(" %s"); emit(sk); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(cn); emit("], ptr @S_"); emit(cn); emit(", i32 0, i32 %e\n")
|
||||
let eb2 = ecs_base(`S_{cn}`)
|
||||
emit(" %s"); emit(sk); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(eb2); emit(", i32 %e\n")
|
||||
var fj = 0
|
||||
while fj < len(c.kids) {
|
||||
let ft = llty(c.kids[fj].ty); let fk = `{sk}_{itoa(fj)}`
|
||||
|
|
@ -182,7 +184,8 @@ function emit_world_table() -> void {
|
|||
emit(" %m"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
|
||||
emit(" br i1 %m"); emit(sk); emit(", label %g"); emit(sk); emit(", label %gn"); emit(sk); emit("\n")
|
||||
emit("g"); emit(sk); emit(":\n")
|
||||
emit(" %hp"); emit(sk); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(cn); emit(", i32 0, i32 %e\n")
|
||||
let eb3 = ecs_base(`H_{cn}`)
|
||||
emit(" %hp"); emit(sk); emit(" = getelementptr inbounds i8, ptr "); emit(eb3); emit(", i32 %e\n")
|
||||
emit(" %hv"); emit(sk); emit(" = load i8, ptr %hp"); emit(sk); emit("\n")
|
||||
emit(" %hr"); emit(sk); emit(" = zext i8 %hv"); emit(sk); emit(" to i32\n ret i32 %hr"); emit(sk); emit("\n")
|
||||
emit("gn"); emit(sk); emit(":\n")
|
||||
|
|
@ -201,10 +204,10 @@ function emit_world_table() -> void {
|
|||
# zeroed. The returned id works with get/set/has/attach exactly like a built-in.
|
||||
emit("define i32 @ludic_register_prop(ptr %name, i32 %nfields) {\nentry:\n")
|
||||
emit(" %dc = load i32, ptr @dyn_count\n %full = icmp slt i32 %dc, 32\n br i1 %full, label %do, label %rej\n")
|
||||
emit("do:\n %nf4 = mul i32 %nfields, 4\n %sz = mul i32 %nf4, "); emit(me); emit("\n %szl = sext i32 %sz to i64\n")
|
||||
emit("do:\n %rcap = load i32, ptr @L_cap\n %nf4 = mul i32 %nfields, 4\n %sz = mul i32 %nf4, %rcap\n %szl = sext i32 %sz to i64\n")
|
||||
emit(" %buf = call ptr @malloc(i64 %szl)\n call ptr @memset(ptr %buf, i32 0, i64 %szl)\n")
|
||||
emit(" %sp = getelementptr inbounds [32 x ptr], ptr @dynS, i32 0, i32 %dc\n store ptr %buf, ptr %sp\n")
|
||||
emit(" %hbuf = call ptr @malloc(i64 "); emit(me); emit(")\n call ptr @memset(ptr %hbuf, i32 0, i64 "); emit(me); emit(")\n")
|
||||
emit(" %hcap = sext i32 %rcap to i64\n %hbuf = call ptr @malloc(i64 %hcap)\n call ptr @memset(ptr %hbuf, i32 0, i64 %hcap)\n")
|
||||
emit(" %hp = getelementptr inbounds [32 x ptr], ptr @dynH, i32 0, i32 %dc\n store ptr %hbuf, ptr %hp\n")
|
||||
emit(" %fp = getelementptr inbounds [32 x i32], ptr @dynF, i32 0, i32 %dc\n store i32 %nfields, ptr %fp\n")
|
||||
emit(" %np = getelementptr inbounds [32 x ptr], ptr @dynName, i32 0, i32 %dc\n store ptr %name, ptr %np\n")
|
||||
|
|
@ -222,7 +225,8 @@ function emit_world_table() -> void {
|
|||
# identifies each entity's model, then reads/writes it with get/set/has above.
|
||||
emit("define i32 @ludic_entity_count() {\nentry:\n %n = load i32, ptr @L_entc\n ret i32 %n\n}\n\n")
|
||||
emit("define i32 @ludic_kind(i32 %e) {\nentry:\n")
|
||||
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
|
||||
let eb4 = ecs_base("L_kind")
|
||||
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb4); emit(", i32 %e\n")
|
||||
emit(" %k = load i32, ptr %kp\n ret i32 %k\n}\n\n")
|
||||
emit("define i32 @ludic_model_id(ptr %name) {\nentry:\n")
|
||||
k = 0; i = 0
|
||||
|
|
@ -287,7 +291,9 @@ function emit_world_table() -> void {
|
|||
emit(" %n = load i32, ptr @L_entc\n br label %loop\n")
|
||||
emit("loop:\n %e = phi i32 [ %from, %entry ], [ %e1, %cont ]\n")
|
||||
emit(" %go = icmp slt i32 %e, %n\n br i1 %go, label %body, label %none\n")
|
||||
emit("body:\n %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
|
||||
emit("body:\n")
|
||||
let eb5 = ecs_base("L_alive")
|
||||
emit(" %ap = getelementptr inbounds i32, ptr "); emit(eb5); emit(", i32 %e\n")
|
||||
emit(" %al = load i32, ptr %ap\n %isa = icmp ne i32 %al, 0\n br i1 %isa, label %chk, label %cont\n")
|
||||
emit("chk:\n %h = call i32 @ludic_has(i32 %e, i32 %p)\n %hit = icmp ne i32 %h, 0\n br i1 %hit, label %hitb, label %cont\n")
|
||||
emit("hitb:\n ret i32 %e\n")
|
||||
|
|
@ -446,6 +452,7 @@ function emit_game_main() -> void {
|
|||
emit(" store i32 %argc, ptr @L_argc\n")
|
||||
emit(" store ptr %argv, ptr @L_argv\n")
|
||||
emit(" call void @L_init_runtime()\n")
|
||||
if has_ecs() { emit(" call void @L_grow(i32 1)\n") }
|
||||
if (find_fn("rt_init") != null) { emit(" call void " + fn_sym("rt_init") + "()\n") }
|
||||
emit(" call void @L_init_globals()\n")
|
||||
emit_calls_for_phase("Start")
|
||||
|
|
|
|||
|
|
@ -121,7 +121,8 @@ function emit_net_serialize(m: Node) -> void {
|
|||
let cn = m.kids[ci].s
|
||||
let c = find_comp(cn)
|
||||
if (c != null) {
|
||||
let s = nreg(); emit(" "); emit(s); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(cn); emit("], ptr @S_"); emit(cn); emit(", i32 0, i32 %e\n")
|
||||
let eb1 = ecs_base(`S_{cn}`)
|
||||
let s = nreg(); emit(" "); emit(s); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(eb1); emit(", i32 %e\n")
|
||||
var fj = 0
|
||||
while fj < len(c.kids) {
|
||||
if c.kids[fj].ival == 1 {
|
||||
|
|
@ -154,7 +155,8 @@ function emit_net_apply(m: Node) -> void {
|
|||
let cn = m.kids[ci].s
|
||||
let c = find_comp(cn)
|
||||
if (c != null) {
|
||||
let s = nreg(); emit(" "); emit(s); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(cn); emit("], ptr @S_"); emit(cn); emit(", i32 0, i32 %e\n")
|
||||
let eb2 = ecs_base(`S_{cn}`)
|
||||
let s = nreg(); emit(" "); emit(s); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(eb2); emit(", i32 %e\n")
|
||||
var fj = 0
|
||||
while fj < len(c.kids) {
|
||||
if c.kids[fj].ival == 1 {
|
||||
|
|
@ -181,7 +183,8 @@ function emit_net_dispatch() -> void {
|
|||
let me = itoa(MAX_ENT)
|
||||
|
||||
emit("define i32 @ludic_serialize(i32 %e, ptr %buf) {\nentry:\n")
|
||||
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n %k = load i32, ptr %kp\n")
|
||||
let eb3 = ecs_base("L_kind")
|
||||
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb3); emit(", i32 %e\n %k = load i32, ptr %kp\n")
|
||||
var k = 0
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
|
|
@ -198,7 +201,8 @@ function emit_net_dispatch() -> void {
|
|||
emit(" ret i32 0\n}\n\n")
|
||||
|
||||
emit("define void @ludic_apply(i32 %e, ptr %buf, i32 %len) {\nentry:\n")
|
||||
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n %k = load i32, ptr %kp\n")
|
||||
let eb4 = ecs_base("L_kind")
|
||||
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb4); emit(", i32 %e\n %k = load i32, ptr %kp\n")
|
||||
k = 0; i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_ARCH and net_model_syncs(prog[i]) {
|
||||
|
|
@ -216,7 +220,8 @@ function emit_net_dispatch() -> void {
|
|||
# ludic_sync_size(e): the replicated byte count for the entity's model — a
|
||||
# constant per kind, so a runtime can size a buffer before serialize.
|
||||
emit("define i32 @ludic_sync_size(i32 %e) {\nentry:\n")
|
||||
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n %k = load i32, ptr %kp\n")
|
||||
let eb5 = ecs_base("L_kind")
|
||||
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb5); emit(", i32 %e\n %k = load i32, ptr %kp\n")
|
||||
k = 0; i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_ARCH and net_model_syncs(prog[i]) {
|
||||
|
|
@ -239,12 +244,15 @@ function emit_net_dispatch() -> void {
|
|||
function emit_net_owner() -> void {
|
||||
let me = itoa(MAX_ENT)
|
||||
emit("define i32 @L_owner(i32 %e) {\nentry:\n")
|
||||
emit(" %p = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n %v = load i32, ptr %p\n ret i32 %v\n}\n\n")
|
||||
let eb6 = ecs_base("L_owner_arr")
|
||||
emit(" %p = getelementptr inbounds i32, ptr "); emit(eb6); emit(", i32 %e\n %v = load i32, ptr %p\n ret i32 %v\n}\n\n")
|
||||
emit("define void @L_set_owner(i32 %e, i32 %id) {\nentry:\n")
|
||||
emit(" %p = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n store i32 %id, ptr %p\n ret void\n}\n\n")
|
||||
let eb7 = ecs_base("L_owner_arr")
|
||||
emit(" %p = getelementptr inbounds i32, ptr "); emit(eb7); emit(", i32 %e\n store i32 %id, ptr %p\n ret void\n}\n\n")
|
||||
# is_owner(e): does the local peer own e? owner(e) == local_id().
|
||||
emit("define i32 @L_is_owner(i32 %e) {\nentry:\n")
|
||||
emit(" %p = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n %o = load i32, ptr %p\n")
|
||||
let eb8 = ecs_base("L_owner_arr")
|
||||
emit(" %p = getelementptr inbounds i32, ptr "); emit(eb8); emit(", i32 %e\n %o = load i32, ptr %p\n")
|
||||
emit(" %lid = load i32, ptr @L_localid\n %eq = icmp eq i32 %o, %lid\n %r = zext i1 %eq to i32\n ret i32 %r\n}\n\n")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ function red_check(i: int) -> void {
|
|||
function act_src() -> pointer {
|
||||
var src = "export state LudicActions {\n kinds: []int = new []int\n vals: []pointer = new []pointer\n head: int = 0\n depth: int = 0\n}\n"
|
||||
src = src + "export function ludic_act_push(q: mut LudicActions, k: int, v: pointer) -> void {\n push(q.kinds, k)\n push(q.vals, v)\n}\n"
|
||||
src = src + "export function drain_actions(q: mut LudicActions) -> void {\n if q.depth > 0 { return }\n q.depth = 1\n var pass = 0\n var round_end = len(q.kinds)\n"
|
||||
src = src + "export function drain_actions(q: mut LudicActions) -> void {\n if q.depth > 0 or len(q.kinds) == 0 { return }\n q.depth = 1\n var pass = 0\n var round_end = len(q.kinds)\n"
|
||||
src = src + " while q.head < len(q.kinds) {\n if q.head == round_end {\n pass += 1\n round_end = len(q.kinds)\n"
|
||||
src = src + ` if pass >= {itoa(ACTION_PASSES)} {{ ludic_act_runaway(q.kinds[q.head]) }}\n }}\n`
|
||||
src = src + " let k = q.kinds[q.head]\n let v = q.vals[q.head]\n q.head += 1\n"
|
||||
|
|
@ -165,7 +165,7 @@ function act_src() -> pointer {
|
|||
}
|
||||
a += 1
|
||||
}
|
||||
src = src + " }\n q.kinds = new []int\n q.vals = new []pointer\n q.head = 0\n q.depth = 0\n}\n"
|
||||
src = src + " }\n List.clear(q.kinds)\n List.clear(q.vals)\n q.head = 0\n q.depth = 0\n}\n" # in place: a drain a phase, seven a frame, allocates nothing
|
||||
src = src + "export function ludic_act_runaway(k: int) -> void {\n var name = \"?\"\n"
|
||||
a = 0
|
||||
while a < len(g_act_names) {
|
||||
|
|
|
|||
26594
selfhost/ludicc.seed.ll
26594
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue