Closed DrSensor closed 1 year ago
For now I only need query all value in multiple instance. For example,
<render-scope>
<link as="script" href="position.js" data-persist>
<input type="range" min="0" max="100" value="10" value:="x">
</render-scope>
...long content
<render-scope>
<link as="script" href="position.js" data-persist>
<input type="range" min="0" max="100" value="20" value:="x">
</render-scope>
import { u8 } from "wiles/types"
import { all, count, spawn } from "wiles/std"
class Position {
@spawn(100, u8) // cap it to 100 instance **across pages**
accessor x: number
}
// let's say I use this function in a widget/island build using Preact
const sumMin10 = () => {
let total = 0
for (let i = 0, x = all(Position).x; i < count(Position); i++)
if (x[i] >= 10) total += x[i]
return total
}
export { default as Position, sumLess10 }
This scenario doesn't require managing entity id via SparseSet or BitSet. The accessor just proxy to an item in UInt8Array. If size not specified, it will default to Array with dynamic capacity. For example,
import { all, spawn } from "wiles/std"
class Position {
@spawn // no cap, grow as needed
accessor x: number
}
// let's say I use this function in a widget/island build using Preact
const sumMin10 = () => {
let total = 0 // simpler query since there is no cap (.length === number of instances)
for (const x of all(Position).x) if (x >= 10) total += x
return total
}
export { default as Position, sumLess10 }
All examples above are realtime! It changes for-loop behavior when there is a new instance or the value of 'x' is changed. To make it deterministic, you need to copy both count and the backed array. For example,
const sumMin10 = () => {
let total = 0
const // copy both count and backed array
countPosition = count(Position),
x = Object.getPrototypeOf(all(Position).x).constructor.from(all(Position).x) // or simply do UInt8Array.from(all(Position).x)
for (let i = 0; i < countPosition; i++)
if (x[i] >= 10) total += x[i]
return total
}
But most of the time it really doesn't matter since Javascript is a single threaded language.
š¶ "Never Gonna Give You Up" by Rick Ashley
I think I solve how to share data across spectrum (worker, wasm, shader). Basically spawn decorator need to know which buffer to use. For example,
// assume that WorldBuffer can be from 3rd-party lib
type AnyBufferConstructor = ArrayBufferConstructor | SharedArrayBufferConstructor
const init = <T extends AnyBufferConstructor>(
Buffer: T,
capacity?: number,
) => class Single {
static #buffer: InstanceType<T>
static get buffer() {
return this.#buffer
}
offset: number
constructor(
length: number | 100,
type: TypedArrayConstructor | UInt8ArrayConstructor,
) {
Single.#buffer ??= new Buffer(capacity ?? length)
this.offset = type.BYTES_PER_ELEMENT * length
}
}
import { u8 } from "wiles/type"
import { spawn } from "wiles/std"
export default class {
@spawn(100, u8, WorldBuffer)
x: number
}
const WorldBuffer = init(ArrayBuffer, 500) // limit to 5 instances
Now if we want share it with worker thread, we need to tell it to use SharedArrayBuffer instead of ArrayBuffer.
const WorldBuffer = init(SharedArrayBuffer, 500)
new Worker(
"sumMin10.js",
{ type: "module" },
).postMessage(WorldBuffer.buffer)
@frameloop
decoratorBasically it just wrap method in requestAnimationFrame and some logic to make the system in loop deterministic and suspend/resume-able.
import { u16 } from "wiles/types"
import { frameloop, spawn, all, count } from "wiles/std"
export default class {
@spawn(200, u16)
accessor x // infer value
@frameloop.once // explicit trigger but only once (i.e <button on:click="activateSinMove">)
activateSinMove() {
const { dt } = frameloop
const { x } = all(this)
for (let i = count(this); i--;) {
x[i] = Math.sin(x[i])
}
// automatically update all x when this method end
}
}
š¤ TODO: explain how to pause/suspend and resume the frameloop (especially async one), especially when there is a lot of frameloop function running
Moved to #55 #56
This ECS feature are more like my toy experiment rather than serious one. Although no one ask for this š, code composition for multiple logics that constantly in a loop while updating the retained GUI (DOM) are damn so hard.
@allocate
decoratorkill(entity)
(reset component value)suspend(entity)
andresume(entity)
(doesn't reset component value).add(components)
from an entity.remove(components)
from an entityquery(components)
system = () => ...
dt
(delta time)system.dt