tecs.gfx.particles

GPU particle effects, emitter playback, pool sizing, and rendering limits.

An entity represents an emitter. The GPU owns individual particles, so game code controls an effect and its emitter rather than inspecting particles one at a time.

const sparks = tecs.gfx.particles.newEffect({
    name = "game.sparks",
    capacity = 256,
    schedule = {rate = 120, duration = 0.3},
    spawn = {shape = "disc", width = 4, outward = true},
    initial = {
        lifetime = {min = 0.2, max = 0.5},
        speed = {min = 40, max = 120},
        size = 3,
    },
    update = {
        drag = 2.0,
        size = tecs.gfx.particles.newCurve({
            {0.0, 1.0},
            {1.0, 0.0},
        }),
    },
})

tecs.gfx.particles.plugin(world, {capacity = 4096})

world:spawn(
    tecs.ecs.Transform2D(120, 80, 0, 1, 0, 1, 1),
    tecs.gfx.particles.ParticleEmitter({effect = sparks})
)

An immutable Effect describes schedule, spawn, initial state, updates, and rendering. A ParticleEmitter names the effect and carries playback state. Effect names form a snapshot compatibility surface.

Emission and motion#

Schedules combine rate, delay, duration, looping, and timed bursts. duration = 0 keeps a cycle open. An emission that finds no free effect slot either drops the new particle or replaces the oldest, according to overflow.

Spawn shapes include point, line, rectangle, rectangle edge, disc, ring, and cone. World-space particles detach from later emitter movement. Local-space particles follow the emitter for their full life. inheritVelocity applies only in world space.

Curves and gradients sample normalized age from zero to one. Numeric properties accept a constant or a {min, max} range.

Playback#

play starts or resumes emission. stop resets the schedule while live particles drain. pause holds the schedule and live field. clear kills the field without changing emission. restart resets schedule and randomness. burst queues a count for the next step.

finished derives its answer from the schedule and the longest lifetime. estimatedCount integrates the schedule over the mean lifetime, and may differ after overflow or a truncated burst. Game code cannot inspect, move, kill, or count individual particles.

Pool and snapshots#

The plugin fixes world capacity and maximum emitters at installation. Each emitter reserves its effect capacity. An emitter that cannot fit draws nothing and logs the refusal.

Snapshots store effect name, seed, configuration, and playback state. They do not store individual particles, so a restored emitter starts empty and refills.

Blending#

render.blend selects "alpha", "additive", or "opaque", and defaults to "alpha". The first two draw in the forward pass over the composited image, depth tested and not written, so alpha means what it says: a gradient that ends transparent fades out, and "additive" adds light instead of covering, which is what fire, glow, and sparks want. "opaque" keeps the effect in the G-buffer.

A blended effect is not in the G-buffer, so it casts no shadow and no light's occluder mask sees it, and it is not shadowed by one either. It also draws after compositing, so all of one effect's particles composite as one group in pool-slot order rather than being sorted against each other: an effect names one layer and its particles take one depth within it. Sorting within a pool is a separate feature.

The GPU runs emit, spawn and simulate passes, followed by ordered visibility compaction and indirect draws. A paused, unchanged field skips simulation and reuses its visibility list while the camera is unchanged. Installing no pool allocates no particle storage.

Run the interactive example with nupp task ex-particles: Space pauses, B bursts, R restarts and C clears. A pool can reserve up to four million slots its frame packet contains emitter records rather than individual particles.

Module contents

Constructors

ConstructorDescription
newCurveCompiles keyframes into a scalar curve.
newEffectRegisters an effect and answers the handle an emitter names it by.
newGradientCompiles keyframes into a color gradient.

Types

TypeKindDescription
ColortypeA color, as rrggbb, rrggbbaa, 0xrrggbbaa, or up to four numbers in zero to one.
CurvetypeStores a curve over normalized age as a compiled lookup table.
CurveKeytypeOne key of a curve or a gradient.
EffecttypeRepresents an immutable registered effect shared by its emitters.
EffectOptionstypeDefines a complete effect.
EmitterrecordAn emitter is an ECS entity; its individual particles exist only on the GPU.
EmitterOptionstypeConfigures one independently controlled emitter before it is spawned.
GradienttypeStores a color gradient over normalized age as a compiled lookup table.
InitialOptionstypeConfigures a particle's initial properties.
PoolrecordRetains one world's GPU field allocation and emitter schedule.
PoolOptionstypeConfigures a world's GPU pool; capacity is fixed until the world is dropped.
RangetypeDefines a uniform distribution between two bounds.
RenderOptionstypeConfigures how particles draw.
ScheduleBursttypeDefines one scheduled burst.
ScheduleOptionstypeConfigures when particles emit.
SpawnOptionstypeConfigures where particles spawn.
UpdateOptionstypeConfigures how a particle changes over its normalized lifetime.
ValuetypeA property that takes either a constant or a range.

Functions

FunctionKindDescription
pluginfunctionInstalls a GPU particle pool.
poolOffunctionReturns the installed pool, or nil for a world without GPU particles.
sectionfunctionSerializes emitter configuration for the native renderer.

Values

ValueKindDescription
ParticleEmittervariableSpawns an effect emitter; requires a Transform2D.

Constructors#

newCurveconstructor#

function newCurve(keys: {CurveKey}): Curve

Compiles keyframes into a scalar curve.

Keys are { age, value } with age in zero to one, placed wherever the author wants them rather than spaced evenly.

Arguments

NameTypeDescription
keys{CurveKey}

nonempty age/value keys with ages in zero through one

Returns

TypeDescription
Curve

compiled scalar samples

Raises

  • when keys contain invalid ages or values

newEffectconstructor#

function newEffect(options: EffectOptions): Effect

Registers an effect and answers the handle an emitter names it by.

Arguments

NameTypeDescription
optionsEffectOptions

The caller supplies a named schedule, spawn, simulation and rendering recipe.

Returns

TypeDescription
Effect

Returns the shared effect for ParticleEmitter.

Raises

  • When the name is duplicated or any option is invalid.

newGradientconstructor#

function newGradient(keys: {CurveKey}): Gradient

Compiles keyframes into a color gradient.

Keys are { age, color }. The alpha channel fades a blended effect and is inert on one whose render.blend is "opaque", where a fade to transparent writes opaque and the only fade that works is the size curve going to zero.

Arguments

NameTypeDescription
keys{CurveKey}

nonempty age/value keys with ages in zero through one

Returns

TypeDescription
Gradient

compiled RGBA samples

Raises

  • when keys contain invalid ages or values

Types#

Colortype#

type Color = string | number | {number}

A color, as #rrggbb, #rrggbbaa, 0xrrggbbaa, or up to four numbers in zero to one. A channel the list leaves out reads as one, so {1, 0, 0} is opaque red, and a string that is neither six nor eight hex digits raises.

Curvetype#

type Curve = {
    --- Read-only. Provides evenly spaced samples over normalized age from zero
    --- to one.
    samples: {number}
}

Stores a curve over normalized age as a compiled lookup table.

CurveKeytype#

type CurveKey = {any}

One key of a curve or a gradient. Heterogeneous by design: the first entry is a normalized age and the second is whatever the table is of, so this is one of the few places a list of any is the honest declaration rather than a missing one.

Effecttype#

type Effect = {
    --- Read-only. Reports the effect name and the only property that outlives the
    --- process. Everything a save, a tool or a person identifies an effect by
    --- is this.
    name: string,

    --- Read-only. Reports where its record sits in the assembled effect table, counting
    --- from one. A position in registration order, so it means nothing outside the
    --- process that assigned it and nothing stores one.
    index: integer,

    --- Read-only. Reports how many live particles one emitter can hold.
    capacity: integer,

    --- Read-only. Reports the blend mode the effect resolved to, which is one of
    --- the three `render.blend` names and never nil. The pool reads it to say how
    --- many of its slots may reach the forward pass.
    blend: string,

    --- Read-only. Reports the longest particle lifetime in seconds, which decides when
    --- an emitter that has stopped emitting has finished.
    maxLifetime: number,

    --- Read-only. Reports the mean particle lifetime in seconds, halfway between the
    --- bounds `initial.lifetime` was authored with. This is the window `estimatedCount`
    --- integrates the schedule over, and it is the steady-state live count divided by
    --- the emission rate.
    meanLifetime: number,

    --- Read-only. Reports seconds from `play` to the last emission, or -1 for an effect
    --- that never stops emitting.
    emitFor: number,

    --- Read-only. Reports the continuous emission rate used by `finished` and
    --- `estimatedCount`.
    rate: number,

    --- Read-only. Reports the emission delay in seconds.
    delay: number,

    --- Read-only. Reports the emission duration in seconds.
    duration: number,

    --- Read-only. Reports whether the emission cycle repeats.
    looping: boolean,

    --- Read-only. Provides the compiled burst schedule.
    bursts: {number},

    --- Engine-owned. Stores the compiled effect record. Ordinary game code
    --- should ignore this field.
    _record: {number},

    --- Engine-owned. Stores curve and gradient samples. Ordinary game code
    --- should ignore this field.
    _extra: {number},

    --- Engine-owned. Stores the size-curve offset. Ordinary game code should
    --- ignore this field.
    _sizeCurveAt: integer,

    --- Engine-owned. Stores the color-gradient offset. Ordinary game code
    --- should ignore this field.
    _colorGradientAt: integer,

    --- Engine-owned. Keeps the authored image and clipping sources for reloads.
    _render: RenderOptions
}

Represents an immutable registered effect shared by its emitters.

EffectOptionstype#

type EffectOptions = {
    --- Caller-writable. Sets the required process-wide unique effect name.
    --- Snapshots use this name to identify the effect.
    name: string,

    --- Caller-writable. Sets how many live particles one emitter can hold.
    --- Defaults to 256, and under one raises. A steady-state emitter needs at
    --- least `rate * maxLifetime`; a smaller capacity registers, drops the
    --- emissions that find no slot, and logs a warning saying so, because a
    --- spray thinner than the one authored is otherwise found by eye.
    capacity: integer?,

    --- Caller-writable. Selects `"drop"` or `"replace"` when an emission arrives
    --- with no free slot: give way, or take the oldest live particle's. Defaults
    --- to `"replace"`, and anything else raises.
    overflow: string?,

    --- Caller-writable. Configures when the effect emits particles. An absent
    --- section takes every default below it.
    schedule: ScheduleOptions?,

    --- Caller-writable. Configures where the effect spawns particles. An absent
    --- section takes every default below it.
    spawn: SpawnOptions?,

    --- Caller-writable. Configures each particle's initial properties. An absent
    --- section takes every default below it.
    initial: InitialOptions?,

    --- Caller-writable. Configures how particles change over their lifetime. An
    --- absent section leaves a particle on its launch velocity.
    update: UpdateOptions?,

    --- Caller-writable. Configures how particles draw. An absent section draws
    --- white quads on layer one.
    render: RenderOptions?
}

Defines a complete effect.

Emitterrecord#

record Emitter
    effect: Effect
    state: string = "playing"
    seed: number = 1
    rateScale: number = 1
    sizeScale: number = 1
    timeScale: number = 1
    tint: Color? = nil
    play: function(exclusive self: Emitter): nil
    stop: function(exclusive self: Emitter): nil
    pause: function(exclusive self: Emitter): nil
    clear: function(exclusive self: Emitter): nil
    restart: function(exclusive self: Emitter): nil
    burst: function(exclusive self: Emitter, count: integer): nil
    finished: function(borrows self: Emitter): boolean
    estimatedCount: function(borrows self: Emitter): integer
end

An emitter is an ECS entity; its individual particles exist only on the GPU.

Methods

play#
play: function(exclusive self: Emitter): nil

Starts or resumes emission. A stopped emitter starts its schedule again.

Arguments
NameTypeDescription
exclusive selfEmitter
Returns
TypeDescription
nil

The operation returns no value.

stop#
stop: function(exclusive self: Emitter): nil

Stops emission while live particles drain.

Arguments
NameTypeDescription
exclusive selfEmitter
Returns
TypeDescription
nil

The operation returns no value.

pause#
pause: function(exclusive self: Emitter): nil

Holds both the schedule and live particle field.

Arguments
NameTypeDescription
exclusive selfEmitter
Returns
TypeDescription
nil

The operation returns no value.

clear#
clear: function(exclusive self: Emitter): nil

Kills the live field without changing playback.

Arguments
NameTypeDescription
exclusive selfEmitter
Returns
TypeDescription
nil

The operation returns no value.

restart#
restart: function(exclusive self: Emitter): nil

Restarts the schedule and deterministic random sequence with an empty field.

Arguments
NameTypeDescription
exclusive selfEmitter
Returns
TypeDescription
nil

The operation returns no value.

burst#
burst: function(exclusive self: Emitter, count: integer): nil

Queues a burst for the next fixed step; stopped emitters ignore it.

Arguments
NameTypeDescription
exclusive selfEmitter
countinteger

a nonnegative count, truncated to the emitter's capacity

Returns
TypeDescription
nil

The operation returns no value.

finished#
finished: function(borrows self: Emitter): boolean

Answers whether the last possible particle has expired, without GPU readback.

Arguments
NameTypeDescription
borrows selfEmitter
Returns
TypeDescription
boolean

Returns whether the schedule and longest particle lifetime have elapsed.

estimatedCount#
estimatedCount: function(borrows self: Emitter): integer

Estimates the live count from the schedule and mean lifetime. Overflow and explicit bursts can make it differ from the GPU field.

Arguments
NameTypeDescription
borrows selfEmitter
Returns
TypeDescription
integer

Returns the estimated live count, which may differ after overflow.

Fields

effect#
effect: Effect

Caller-writable before spawning. Selects the immutable effect.

state#
state: string

Read-only playback state; use play, stop or pause to change it.

seed#
seed: number

Caller-writable random seed, an integer from zero through 16777215.

rateScale#
rateScale: number

Caller-writable multipliers for emission rate, size and simulation time.

sizeScale#
sizeScale: number

Caller-writable. Multiplies particle size and defaults to one.

timeScale#
timeScale: number

Caller-writable. Multiplies simulation seconds and defaults to one.

tint#
tint: Color?

Caller-writable color multiplier.

EmitterOptionstype#

type EmitterOptions = {
    --- Caller-writable. Selects the registered immutable effect.
    effect: Effect,

    --- Caller-writable. Starts playing, paused, or stopped and defaults to playing.
    state: string?,

    --- Caller-writable. Seeds deterministic emission with an integer from zero through
    --- 16777215.
    seed: number?,

    --- Caller-writable. Scales the scheduled emission rate and defaults to one.
    rateScale: number?,

    --- Caller-writable. Scales particle size and defaults to one.
    sizeScale: number?,

    --- Caller-writable. Scales simulation seconds and defaults to one.
    timeScale: number?,

    --- Caller-writable. Multiplies particle RGBA and defaults to white.
    tint: Color?
}

Configures one independently controlled emitter before it is spawned.

Gradienttype#

type Gradient = {
    --- Read-only. Provides evenly spaced RGBA samples over normalized age,
    --- with four floats per sample.
    samples: {number}
}

Stores a color gradient over normalized age as a compiled lookup table.

Alpha is carried and used: a blended effect fades with it, and an effect whose render.blend is "opaque" writes it to a target that has nowhere to put it.

InitialOptionstype#

type InitialOptions = {
    --- Caller-writable. Sets particle lifetime in seconds. Defaults to one, and
    --- its upper bound is the effect's `maxLifetime`.
    lifetime: Value?,

    --- Caller-writable. Sets initial speed in world units per second. Defaults to
    --- zero.
    speed: Value?,

    --- Caller-writable. Sets initial size in world units. Defaults to one.
    size: Value?,

    --- Caller-writable. Sets initial rotation in radians. Defaults to zero.
    rotation: Value?,

    --- Caller-writable. Sets initial angular velocity in radians per second.
    --- Defaults to zero.
    angularVelocity: Value?,

    --- Caller-writable. Sets constant horizontal acceleration. Independent axes
    --- make a plume drift apart instead of translating as a block. Defaults to
    --- zero.
    accelerationX: Value?,

    --- Caller-writable. Sets constant vertical acceleration. Defaults to zero.
    accelerationY: Value?,

    --- Caller-writable. Sets the color every particle starts with, multiplied by
    --- the gradient and by the emitter's tint. Defaults to opaque white.
    color: Color?
}

Configures a particle's initial properties.

Poolrecord#

record Pool
    capacity: integer
    maxEmitters: integer
end

Retains one world's GPU field allocation and emitter schedule.

Fields

capacity#
capacity: integer

Read-only maximum number of simultaneous particle slots in this world.

maxEmitters#
maxEmitters: integer

Read-only maximum number of simultaneous emitters, including draining ones.

PoolOptionstype#

type PoolOptions = {
    --- Caller-writable. Reserves 1 to 4000000 slots and defaults to 16384.
    capacity: integer?,

    --- Caller-writable. Reserves emitter records and defaults to 256.
    maxEmitters: integer?
}

Configures a world's GPU pool; capacity is fixed until the world is dropped.

Rangetype#

type Range = {
    --- Caller-writable. Sets the inclusive lower bound.
    min: number,

    --- Caller-writable. Sets the inclusive upper bound.
    max: number
}

Defines a uniform distribution between two bounds.

RenderOptionstype#

type RenderOptions = {
    --- Caller-writable. Selects the material that shades the quad by name. An
    --- absent value samples the image array and covers the whole quad.
    material: string?,

    --- Caller-writable. Passes one number to the material, clamped to zero
    --- through `0.999` and ignored unless `material` names one. The instance
    --- carries the material as `id + param` in one float, and the vertex shader
    --- reads the integer part as the selector and the fraction as the
    --- parameter, so `1.0` would arrive as the next material's id with a
    --- parameter of zero. Defaults to zero.
    materialParam: number?,

    --- Caller-writable. Selects a registered image from `renderer.sprites:sprite`. An
    --- absent value draws a white quad.
    sprite: rendercomponents.Sprite?,

    --- Caller-writable. Selects a sheet to animate instead of a still region.
    --- The cycle is played exactly once over each particle's own life and clamps
    --- at the end, so a randomized lifetime randomizes the playback speed.
    ---
    --- Uses the same frame table an animated entity does, so per-frame
    --- durations, reverse and pingpong all arrive already spent.
    sheet: sheet.Sheet?,

    --- Caller-writable. Selects the sheet tag to animate. An absent value
    --- animates the whole sheet. A name the sheet does not carry animates the
    --- whole sheet too, and the sheet reports it at error level under the
    --- `tecs.gfx` logger, naming the tags it does carry. Defining the effect
    --- still succeeds, so a sheet re-exported without a tag keeps drawing.
    tag: string?,

    --- Caller-writable. Selects the render layer from one to `layers.MAX`.
    --- Defaults to one, and anything outside the range raises.
    layer: integer?,

    --- Caller-writable. Sets the horizontal pivot as a fraction of the
    --- frame from its top left. Defaults to `0.5`, the middle.
    pivotX: number?,

    --- Caller-writable. Sets the vertical pivot as a fraction of the frame
    --- from its top edge. Defaults to `0.5`, the middle.
    pivotY: number?,

    --- Caller-writable. Selects `"fixed"` or `"velocity"` alignment. Velocity
    --- alignment adds the path's angle to the particle's own rotation, so a
    --- spinning spark aligned to its path still spins. Defaults to `"fixed"`,
    --- and anything else raises.
    alignment: string?,

    --- Caller-writable. Multiplies the along-path axis when aligned to
    --- velocity. Defaults to one.
    stretch: number?,

    --- Caller-writable. Selects `"alpha"`, `"additive"`, or `"opaque"`. Defaults
    --- to `"alpha"`, and anything else raises.
    ---
    --- `"alpha"` and `"additive"` draw in the forward pass over the composited
    --- image, depth tested and not written, so a particle is still hidden by
    --- geometry in front of it. `"alpha"` keeps that much less of what is behind
    --- it and `"additive"` adds to it, so a dark additive particle changes
    --- nothing and overlapping ones brighten.
    ---
    --- `"opaque"` draws into the G-buffer instead. What it gives up is alpha,
    --- which the G-buffer is written with replace and has nowhere to put: a fade
    --- to transparent writes opaque over what was behind it, and the only fade
    --- left is a size curve reaching zero. What it buys is everything that reads
    --- the G-buffer, which is the occluder mask and being shadowed by one.
    blend: string?,

    --- Caller-writable. Selects the clip region that contains the fragments.
    --- Defaults to zero, which is no clipping.
    clip: integer?
}

Configures how particles draw.

ScheduleBursttype#

type ScheduleBurst = {
    --- Caller-writable. Sets the burst time in seconds from the cycle start.
    time: number,

    --- Caller-writable. Sets how many particles the burst emits.
    count: number
}

Defines one scheduled burst.

ScheduleOptionstype#

type ScheduleOptions = {
    --- Caller-writable. Sets the continuous emission rate in particles per
    --- second. Defaults to zero, which emits only what `bursts` asks for.
    rate: number?,

    --- Caller-writable. Sets the emission cycle duration in seconds. Defaults to
    --- zero, which leaves the cycle open.
    duration: number?,

    --- Caller-writable. Controls whether the emission cycle repeats. Defaults to
    --- false.
    looping: boolean?,

    --- Caller-writable. Sets the delay before emission starts in seconds.
    --- Defaults to zero.
    delay: number?,

    --- Caller-writable. Defines at most four timed burst counts measured from
    --- the start of the cycle. A muzzle flash is one burst at time zero.
    --- Defaults to none, and a fifth raises.
    bursts: {ScheduleBurst}?
}

Configures when particles emit.

SpawnOptionstype#

type SpawnOptions = {
    --- Caller-writable. Selects `"point"`, `"line"`, `"rectangle"`,
    --- `"rectangleEdge"`, `"disc"`, `"ring"`, or `"cone"`. Defaults to
    --- `"point"`, and anything else raises.
    shape: string?,

    --- Caller-writable. Sets width for a line or rectangle and radius for a
    --- disc, ring, or cone. Defaults to zero.
    width: number?,

    --- Caller-writable. Sets height for a rectangle. Every other shape ignores
    --- it. Defaults to zero.
    height: number?,

    --- Caller-writable. Sets the sampled sector of a disc, ring, or cone in
    --- radians. Defaults to a full turn.
    arc: number?,

    --- Caller-writable. Rotates the sampled area in radians, independently of
    --- launch direction. Defaults to zero.
    rotation: number?,

    --- Caller-writable. Selects `"uniform"` or `"normal"` sampling. A gaussian
    --- spread makes a column read as a column rather than as a slab. Defaults to
    --- `"uniform"`, and anything else raises.
    distribution: string?,

    --- Caller-writable. Sets the launch direction in radians and centers the
    --- full cone on it, so a particle leaves within plus or minus half of
    --- `spread`. Half-angle is the other plausible reading of `spread` and a
    --- mismatch is silent, so it is said here. Defaults to zero.
    direction: number?,

    --- Caller-writable. Sets the full cone width in radians. Defaults to zero,
    --- which launches every particle along `direction`.
    spread: number?,

    --- Caller-writable. Launches along the shape's outward normal rather than
    --- along `direction`. Meaningful for the shapes that have one: disc, ring,
    --- cone and a rectangle's edge. Defaults to false.
    outward: boolean?,

    --- Caller-writable. Selects `"local"` or `"world"` space. Local particles
    --- follow the emitter's transform for their whole life; world particles read
    --- it once, at spawn, and then move independently. Neither is a good
    --- universal default, so it is explicit and defaults to `"world"`. Anything
    --- else raises.
    space: string?,

    --- Caller-writable. Sets how much emitter velocity a particle inherits, from
    --- zero to one. Defaults to zero, and world space is the only space it means
    --- anything in: a local particle is already carried by the emitter, so
    --- inheriting would carry it twice.
    inheritVelocity: number?
}

Configures where particles spawn.

UpdateOptionstype#

type UpdateOptions = {
    --- Caller-writable. Sets damping in units of one over seconds, applied as
    --- `velocity = velocity / (1 + drag * dt)`, so it composes with any step.
    --- Defaults to zero.
    drag: number?,

    --- Caller-writable. Sets acceleration away from the emitter. This and
    --- `tangentialAcceleration` are the difference between a fountain and a
    --- vortex. Defaults to zero.
    radialAcceleration: Value?,

    --- Caller-writable. Sets acceleration at right angles to the emitter.
    --- Defaults to zero.
    tangentialAcceleration: Value?,

    --- Caller-writable. Multiplies each particle's size over its life. An absent
    --- curve multiplies by one, so leaving it out costs nothing.
    size: Curve?,

    --- Caller-writable. Multiplies each particle's color over its life. An absent
    --- gradient multiplies by white, so leaving it out costs nothing. Its alpha
    --- fades a blended effect and is ignored by an effect whose `render.blend` is
    --- `"opaque"`.
    color: Gradient?
}

Configures how a particle changes over its normalized lifetime.

Valuetype#

type Value = number | Range

A property that takes either a constant or a range. 300 and { min = 180, max = 420 } are both accepted everywhere one of these is, and cost the same: the shader draws between two bounds either way, and equal bounds are a constant. Bounds the wrong way round are swapped rather than refused, and a non-finite bound raises with the field named.

Functions#

pluginfunction#

function plugin(exclusive world: ecs.World, options: PoolOptions?): nil

Installs a GPU particle pool. No plugin means no allocations or dispatches.

Arguments

NameTypeDescription
exclusive worldecs.World

The caller supplies the world that owns the emitters.

optionsPoolOptions?

The caller sets fixed pool limits, defaulting to 16384 slots and 256 emitters.

Returns

TypeDescription
nil

The operation returns no value.

Raises

  • When pool limits are outside the supported range.

poolOffunction#

function poolOf(borrows world: ecs.World): Pool?

Returns the installed pool, or nil for a world without GPU particles.

Arguments

NameTypeDescription
borrows worldecs.World

The caller supplies the world to inspect.

Returns

TypeDescription
Pool?

Returns the pool and its configured limits, or nil when uninstalled.

sectionfunction#

function section(borrows world: ecs.World): string

Serializes emitter configuration for the native renderer. Particle state is absent: it never leaves the GPU. Multiple views share a pool and sequence.

Arguments

NameTypeDescription
borrows worldecs.World

The host supplies the world being rendered.

Returns

TypeDescription
string

Returns a packet, or an empty string when no pool is installed.

Values#

ParticleEmittervariable#

Spawns an effect emitter; requires a Transform2D. Snapshots name the effect and preserve playback, then refill an empty GPU field after restoration.