On this page
tecs.assets
Reads bytes and decodes images and sounds on a worker.
Each load returns a Future immediately:
return tecs.newApplication({
plugin = function(world: tecs.World, app: tecs.Application)
tecs.assets.loadImage(
tecs.io.files.assetPath("sprites/hero.png")
)
:map(function(image: tecs.assets.Image): tecs.gfx.Sprite
return app.renderer.sprites:registerImage(image)
end)
:onSettle(function(sprite: tecs.Future<tecs.gfx.Sprite>)
if sprite.status == "ready" then
world:spawn(
tecs.Transform2D(100, 100),
sprite.value,
tecs.gfx.Renderable2D()
)
end
end)
end,
})The loader decodes pixels but creates no GPU resource. The renderer decides texture residency. Audio follows the same split between decoded clips and voices.
newMesh constructs immutable procedural geometry synchronously. loadGLTF decodes static glTF 2.0 and GLB scenes on the worker, including their images, metallic-roughness materials, alpha masks, and BLEND modes. Both produce CPU-owned data that the mesh domain consumes when it becomes resident. A model with a BLEND material requires a mesh domain created with transparency = true.
Lifetime
Application installs and shuts down the process-wide worker, and runtime.poll updates it. Headless code must call install, pump with tecs.runtime.poll or wait with waitAll, then call shutdown. Drain wanted results before shutdown because in-flight futures otherwise remain pending.
Cancel a pending future to release one caller's interest. Release a ready payload when its consumer takes ownership. The final release frees the decoded memory.
Overlapping image loads for one path share a decode. Each caller receives its own future and a hold on the same image. A later load after settlement decodes again. Sound loads never share.
loadString reads a complete file into a binary-safe string without interpreting it. Image loads accept PNG, JPEG and static SVG. SVG renders at its intrinsic dimensions. It uses the bundled JetBrains Mono for all text and ignores external image references, so installed fonts and the worker's current directory cannot change its pixels. tecs.audio.decoders() reports the sound formats linked into the current build. Sound mode "resident" decodes the complete clip, "stream" opens a source for each voice, and "auto" chooses from the duration threshold.
Use Future.all to join independent loads. Release each ready payload after its consumer takes ownership.
Module contents
Constructors
| Constructor | Description |
|---|---|
newMesh |
Builds procedural mesh data in the renderer's fixed vertex layout. |
Types
| Type | Kind | Description |
|---|---|---|
Image |
record | Represents decoded pixels and the caller's hold on them. |
Mesh |
record | Describes one immutable indexed triangle mesh before GPU registration. |
MeshOptions |
record | Supplies procedural geometry to newMesh. |
Model |
record | Represents one decoded static glTF 2.0 scene. |
ModelDraw |
record | Describes one static primitive instance in a decoded glTF scene. |
ModelMaterial |
record | Describes one decoded glTF material before GPU registration. |
Sound |
record | Represents a loaded clip and the caller's hold on it. |
Functions
| Function | Kind | Description |
|---|---|---|
install |
Static | Starts the loading worker. |
installed |
Static | Reports whether the loading worker is running. |
loadGLTF |
Static | Queues static glTF 2.0 or GLB decoding on the asset worker. |
loadImage |
Static | Queues an image for loading and answers a future for it immediately. |
loadSound |
Static | Queues a sound for loading and answers a future for it immediately. |
loadString |
Static | Queues a complete file read and answers a future for its bytes immediately. |
pending |
Static | Returns the number of loads still waiting on the worker. |
shutdown |
Static | Stops the loading worker. |
update |
Static | Takes finished loads and settles their futures. |
waitAll |
Static | Blocks until every queued load has finished. |
Constructors
tecs.assets.newMesh Static
Builds procedural mesh data in the renderer's fixed vertex layout.
function tecs.assets.newMesh(options: MeshOptions): MeshArguments
| Name | Type | Description |
|---|---|---|
options |
MeshOptions |
The caller supplies a name, interleaved vertices, and zero-based triangle indices. |
Returns
| Type | Description |
|---|---|
Mesh |
Returns CPU geometry that renderer.meshes:registerMesh consumes or the caller releases. |
Types
tecs.assets.Image record
Represents decoded pixels and the caller's hold on them.
What a loadImage future settles to. Reference counts govern the pixels rather than owned outright, because two loads of one path that overlap share a decode: each caller holds the same Image, and the last caller to release it one that frees. Reading pixels after that is reading freed memory, which is why a released image reads nil there rather than looking uploadable. Read-only. Exposes the decoded image type.
record tecs.assets.Image
path: string
pixels: loader.CValue
width: integer
height: integer
pitch: integer
release: function(self)
endtecs.assets.Image.path field
Read-only. Contains the requested path unchanged. The loader does not resolve it, so it is whatever the caller passed.
tecs.assets.Image.pixels field
Read-only. Contains decoded RGBA pixels until the last release, then becomes nil.
tecs.assets.Image.width field
Read-only. Reports the width in pixels.
tecs.assets.Image.height field
Read-only. Reports the height in pixel rows.
tecs.assets.Image.pitch field
Read-only. Reports the row stride in bytes, which a decoder may pad beyond width * 4.
tecs.assets.Image:release Instance
Gives up this caller's hold on the pixels, and frees at the last.
Called once whatever needed them has taken a copy, which for an image means after the caller uploads it. Where several loads shared one decode, this releases one of them.
Releasing an image already down to nothing does nothing, so a shutdown path need not know whether something else got there first.
function tecs.assets.Image.release(self)Arguments
| Name | Type | Description |
|---|---|---|
self |
Image |
Returns
None.
tecs.assets.Mesh record
Describes one immutable indexed triangle mesh before GPU registration.
Vertices use one fixed interleaved layout: position xyz, normal xyz, tangent xyzw, and texture uv. Indices are zero-based unsigned 32-bit values. A mesh may therefore contain far more than 65,535 vertices, and a primitive is one indexed range rather than one triangle.
renderer.meshes:registerMesh copies both arrays into device-local storage and releases this object. The Lua-number constructor is intended for procedural and example geometry. A file decoder can build the same record directly without expanding a large mesh into Lua tables. Read-only. Exposes the immutable CPU mesh type.
record tecs.assets.Mesh
name: string
vertices: loader.CArray
indices: loader.CArray
vertexCount: integer
indexCount: integer
centerX: number
centerY: number
centerZ: number
radius: number
release: function(self)
endtecs.assets.Mesh.name field
Read-only. Contains the stable name used by meshId and snapshots.
tecs.assets.Mesh.vertices field
Read-only. Contains interleaved vertex floats until release runs.
tecs.assets.Mesh.indices field
Read-only. Contains zero-based unsigned 32-bit indices until release runs.
tecs.assets.Mesh.vertexCount field
Read-only. Reports the number of vertices, not floats.
tecs.assets.Mesh.vertexCount: integertecs.assets.Mesh.indexCount field
Read-only. Reports the number of indices. It is always a multiple of three.
tecs.assets.Mesh.indexCount: integertecs.assets.Mesh.centerX field
Read-only. Reports the local-space bounding-sphere center x.
tecs.assets.Mesh.centerY field
Read-only. Reports the local-space bounding-sphere center y.
tecs.assets.Mesh.centerZ field
Read-only. Reports the local-space bounding-sphere center z.
tecs.assets.Mesh.radius field
Read-only. Reports the non-negative local-space bounding-sphere radius.
tecs.assets.Mesh:release Instance
Gives up the CPU geometry. Calling it again does nothing.
function tecs.assets.Mesh.release(self)Arguments
| Name | Type | Description |
|---|---|---|
self |
Mesh |
Returns
None.
tecs.assets.MeshOptions record
Supplies procedural geometry to newMesh.
tecs.assets.MeshOptions.name field
Caller-writable. Supplies the stable non-empty mesh name.
tecs.assets.MeshOptions.name: stringtecs.assets.MeshOptions.vertices field
Caller-writable. Supplies interleaved position xyz, normal xyz, tangent xyzw, and texture uv floats.
tecs.assets.MeshOptions.vertices: {number}tecs.assets.MeshOptions.indices field
Caller-writable. Supplies zero-based triangle indices.
tecs.assets.MeshOptions.indices: {integer}tecs.assets.Model record
Represents one decoded static glTF 2.0 scene.
Meshes use the same fixed vertex layout as newMesh. Images remain decoded CPU pixels. renderer.meshes:registerModel consumes all of them and returns spawnable primitive records. Skinning, morph targets, animation are deliberately outside this static lane. Read-only. Exposes the decoded static glTF scene type.
record tecs.assets.Model
path: string
meshes: {Mesh}
images: {Image}
materials: {ModelMaterial}
draws: {ModelDraw}
release: function(self)
endtecs.assets.Model.path field
Read-only. Contains the requested .gltf or .glb path unchanged.
tecs.assets.Model.meshes field
Read-only. Contains unique decoded primitive geometry.
tecs.assets.Model.images field
Read-only. Contains unique decoded source images.
tecs.assets.Model.materials field
Read-only. Contains decoded metallic-roughness material descriptions.
tecs.assets.Model.materials: {ModelMaterial}tecs.assets.Model.draws field
Read-only. Contains the selected scene's flattened static draws.
tecs.assets.Model:release Instance
Releases every CPU mesh and image not already consumed. Calling it again does nothing.
function tecs.assets.Model.release(self)Arguments
| Name | Type | Description |
|---|---|---|
self |
Model |
Returns
None.
tecs.assets.ModelDraw record
Describes one static primitive instance in a decoded glTF scene. Read-only. Exposes one flattened glTF primitive instance.
record tecs.assets.ModelDraw
mesh: integer
material: integer
x: number
y: number
z: number
rotationX: number
rotationY: number
rotationZ: number
rotationW: number
scaleX: number
scaleY: number
scaleZ: number
endtecs.assets.ModelDraw.mesh field
Read-only. Selects a one-based entry in Model.meshes.
tecs.assets.ModelDraw.material field
Read-only. Selects a one-based entry in Model.materials, or zero for the neutral material.
tecs.assets.ModelDraw.x field
Read-only. Reports world translation x from the selected glTF scene.
tecs.assets.ModelDraw.y field
Read-only. Reports world translation y.
tecs.assets.ModelDraw.z field
Read-only. Reports world translation z.
tecs.assets.ModelDraw.rotationX field
Read-only. Reports world quaternion x.
tecs.assets.ModelDraw.rotationY field
Read-only. Reports world quaternion y.
tecs.assets.ModelDraw.rotationZ field
Read-only. Reports world quaternion z.
tecs.assets.ModelDraw.rotationW field
Read-only. Reports world quaternion w.
tecs.assets.ModelDraw.scaleX field
Read-only. Reports world scale x.
tecs.assets.ModelDraw.scaleY field
Read-only. Reports world scale y.
tecs.assets.ModelDraw.scaleZ field
Read-only. Reports world scale z.
tecs.assets.ModelMaterial record
Describes one decoded glTF material before GPU registration. Read-only. Exposes one glTF material description.
record tecs.assets.ModelMaterial
name: string
model: integer
alphaMode: integer
baseColorImage: integer
normalImage: integer
metallicRoughnessImage: integer
occlusionImage: integer
emissiveImage: integer
alphaCutoff: number
baseR: number
baseG: number
baseB: number
baseA: number
emissiveR: number
emissiveG: number
emissiveB: number
metallic: number
roughness: number
normalScale: number
occlusionStrength: number
endtecs.assets.ModelMaterial.name field
Read-only. Contains the stable material name.
tecs.assets.ModelMaterial.name: stringtecs.assets.ModelMaterial.model field
Read-only. Selects metallic-roughness PBR at zero or unlit at one.
tecs.assets.ModelMaterial.model: integertecs.assets.ModelMaterial.alphaMode field
Read-only. Selects opaque, masked, or blended rendering with a MeshDomain.ALPHA_* integer constant.
tecs.assets.ModelMaterial.alphaMode: integertecs.assets.ModelMaterial.baseColorImage field
Read-only. Selects a one-based entry in Model.images, or zero.
tecs.assets.ModelMaterial.baseColorImage: integertecs.assets.ModelMaterial.normalImage field
Read-only. Selects a tangent-space normal image, or zero.
tecs.assets.ModelMaterial.normalImage: integertecs.assets.ModelMaterial.metallicRoughnessImage field
Read-only. Selects a glTF metallic-roughness image, or zero.
tecs.assets.ModelMaterial.metallicRoughnessImage: integertecs.assets.ModelMaterial.occlusionImage field
Read-only. Selects an occlusion image, or zero.
tecs.assets.ModelMaterial.occlusionImage: integertecs.assets.ModelMaterial.emissiveImage field
Read-only. Selects an emissive image, or zero.
tecs.assets.ModelMaterial.emissiveImage: integertecs.assets.ModelMaterial.alphaCutoff field
Read-only. Reports the alpha-mask cutoff. Zero keeps every fragment.
tecs.assets.ModelMaterial.alphaCutoff: numbertecs.assets.ModelMaterial.baseR field
Read-only. Reports the base-color red factor.
tecs.assets.ModelMaterial.baseR: numbertecs.assets.ModelMaterial.baseG field
Read-only. Reports the base-color green factor.
tecs.assets.ModelMaterial.baseG: numbertecs.assets.ModelMaterial.baseB field
Read-only. Reports the base-color blue factor.
tecs.assets.ModelMaterial.baseB: numbertecs.assets.ModelMaterial.baseA field
Read-only. Reports the base-color alpha factor.
tecs.assets.ModelMaterial.baseA: numbertecs.assets.ModelMaterial.emissiveR field
Read-only. Reports the emissive red factor.
tecs.assets.ModelMaterial.emissiveR: numbertecs.assets.ModelMaterial.emissiveG field
Read-only. Reports the emissive green factor.
tecs.assets.ModelMaterial.emissiveG: numbertecs.assets.ModelMaterial.emissiveB field
Read-only. Reports the emissive blue factor.
tecs.assets.ModelMaterial.emissiveB: numbertecs.assets.ModelMaterial.metallic field
Read-only. Reports the metallic factor.
tecs.assets.ModelMaterial.metallic: numbertecs.assets.ModelMaterial.roughness field
Read-only. Reports the roughness factor.
tecs.assets.ModelMaterial.roughness: numbertecs.assets.ModelMaterial.normalScale field
Read-only. Reports tangent-space normal strength.
tecs.assets.ModelMaterial.normalScale: numbertecs.assets.ModelMaterial.occlusionStrength field
Read-only. Reports sampled occlusion strength.
tecs.assets.ModelMaterial.occlusionStrength: numbertecs.assets.Sound record
Represents a loaded clip and the caller's hold on it.
What a loadSound future settles to. Two overlapping loads of one path do not share, unlike images, so a Sound normally has one holder; the count is here so that releasing twice frees once rather than twice. Read-only. Exposes the loaded sound type.
record tecs.assets.Sound
path: string
audio: loader.CValue
resident: boolean
durationMs: integer
release: function(self)
endtecs.assets.Sound.path field
Read-only. Contains the requested path unchanged.
tecs.assets.Sound.audio field
Read-only. Contains the loaded clip until the last release. It is nil for one that streams, which holds nothing, and nil once released.
tecs.assets.Sound.resident field
Read-only. Reports whether memory holds the decoded audio. It is false for a clip each voice reads from the file for itself.
tecs.assets.Sound.durationMs field
Read-only. Reports the length in milliseconds, or -1 when the container cannot say.
tecs.assets.Sound.durationMs: integertecs.assets.Sound:release Instance
Gives up this caller's hold on the clip, and frees at the last.
A voice reads a clip where it lies, so release a clip when nothing will play it again. A track holds its own reference, so releasing one that is still sounding is safe: the mixer drops it when the last track using it does.
Releasing a clip already down to nothing does nothing.
function tecs.assets.Sound.release(self)Arguments
| Name | Type | Description |
|---|---|---|
self |
Sound |
Returns
None.
Functions
tecs.assets.install Static
Starts the loading worker.
Installing twice is installing once. Spawning unconditionally would leave the first thread running with both its channels and nothing reading them, and every queued decode would answer into an abandoned channel, so a load in flight across the second call would never resolve.
function tecs.assets.install(luaPath: string)Arguments
| Name | Type | Description |
|---|---|---|
luaPath |
string |
package.path for the worker's own state, which shares no loaded modules with this one. Defaults to this state's, which is what makes the worker resolve the same modules the game does. |
Returns
None.
tecs.assets.installed Static
Reports whether the loading worker is running.
For a subsystem that loads an asset of its own and has no way of knowing whether the game has started the worker yet.
function tecs.assets.installed(): booleanArguments
None.
Returns
| Type | Description |
|---|---|
boolean |
Whether the worker exists, which is a fact about the process rather than about any world. False means a load raises rather than queueing, so this is the guard rather than an optimization. |
tecs.assets.loadGLTF Static
Queues static glTF 2.0 or GLB decoding on the asset worker.
External buffers and images resolve relative to the model path. Data URIs and embedded GLB resources are supported. Triangle primitives, static node transforms, metallic-roughness PBR, normal, occlusion, emissive, alpha-mask, alpha-blended, and unlit materials are decoded. A skinned, morphed, animated, sparse-accessor, or non-triangle input settles as failed instead of being loaded incompletely. Registering a model containing alpha blending requires a mesh domain created with transparency = true.
Arguments
| Name | Type | Description |
|---|---|---|
path |
string |
The caller supplies a .gltf or .glb asset path. |
Returns
| Type | Description |
|---|---|
Future<Model> |
Returns a pending future that owns its ready model. |
tecs.assets.loadImage Static
Queues an image for loading and answers a future for it immediately.
Two loads of one path that overlap share a decode, because decoding the same PNG twice at once duplicates work without producing another result. They share the Image, so the last caller to release it one that frees, and each of them holds a future of its own, so one canceling is invisible to the others. A load that starts after the first has settled decodes again: nothing here is a cache.
Arguments
| Name | Type | Description |
|---|---|---|
path |
string |
The caller supplies a PNG, JPEG or static SVG path. The loader renders SVG at its intrinsic width and height, uses bundled JetBrains Mono for text, and ignores external image references. Another format fails during decoding. |
Returns
| Type | Description |
|---|---|
Future<Image> |
A pending future, never nil. Raises instead when install has not run. Failures reach it as a failed settlement, not through this call: a missing file is a future that fails once the worker has looked. Canceling it gives up this caller's interest in the decode, and the last caller to do so abandons it. |
tecs.assets.loadSound Static
Queues a sound for loading and answers a future for it immediately.
Whatever the mixer's decoders can read loads, so the format is the file's business rather than the caller's.
mode is "resident", "stream", or "auto", and auto keeps anything shorter than streamMs resident.
This call initializes the library instead of the worker because MIX_Init does not support concurrent calls. Initialization before sending the task puts it in order ahead of every decode without a lock.
Two overlapping loads of one path do not share, unlike images: each gets its own clip, because that is what MIX_LoadAudio produces. So this answers the root future rather than a link over one, and the Sound it settles to has exactly one holder.
Arguments
| Name | Type | Description |
|---|---|---|
path |
string |
|
mode |
string |
"resident", "stream" or "auto". Any other value selects "stream". |
streamMs |
integer |
The boundary "auto" decides on, in milliseconds. Read only under "auto", and a file whose length the container cannot state streams whatever it says. |
Returns
| Type | Description |
|---|---|
Future<Sound> |
A pending future, or a failed future when mixer initialization fails. This is the only failure reported without waiting for the worker. |
tecs.assets.loadString Static
Queues a complete file read and answers a future for its bytes immediately.
The returned string preserves embedded NUL bytes. Overlapping reads of one path share the worker task while each caller receives its own future, so one caller may cancel without affecting another. A later read after settlement reads the file again.
function tecs.assets.loadString(
path: string, kind: string
): Future<string>Arguments
| Name | Type | Description |
|---|---|---|
path |
string |
The caller supplies an absolute path or one from assetPath. |
kind |
string |
The caller supplies the content kind used by file watching, or omits it to record a document. |
Returns
| Type | Description |
|---|---|
Future<string> |
Returns a pending Future. A missing or unreadable file settles it as failed. |
tecs.assets.pending Static
Returns the number of loads still waiting on the worker.
function tecs.assets.pending(): integerArguments
None.
Returns
| Type | Description |
|---|---|
integer |
Every load in flight in this process, not one caller's. A load every caller has canceled is still counted until the worker answers for it, because the address it sends still has to be taken and destroyed. |
tecs.assets.shutdown Static
Stops the loading worker.
Blocks until the thread exits. This call drops loads still in flight and leaves their futures pending forever, so drain with waitAll first when their results matter. Releasing an Image or a Sound that already settled still works afterwards; this frees nothing.
function tecs.assets.shutdown()Arguments
None.
Returns
None.
tecs.assets.update Static
Takes finished loads and settles their futures. Call once per frame.
Polls, never blocks, and drains everything the worker has answered rather than one result. Nothing settles without this, so a game that stops calling it has loads that stay pending forever.
function tecs.assets.update(): integerArguments
None.
Returns
| Type | Description |
|---|---|
integer |
How many loads settled this call, which is zero when nothing has finished and also zero when no worker exists. |
tecs.assets.waitAll Static
Blocks until every queued load has finished.
For startup and tests. A frame should poll update instead.
Not a join over futures a caller holds, and no join expresses it: this waits on every load in this process, including ones a subsystem the caller has never heard of started, which is what a reload or a startup path wants.
function tecs.assets.waitAll(timeoutMs: number)Arguments
| Name | Type | Description |
|---|---|---|
timeoutMs |
number |
Wall-clock milliseconds, defaulting to 5000. Running out is not an error and is not reported, so read assets.pending afterwards to tell the two endings apart. |