FXRuntime Lifecycle

The authored FX remains reusable; each executor creates a runtime whose emit, tick, render, destroy, and finish states are independent.
An FX is loaded authored data. An FXRuntime is one playable object tree plus its own TimelinePlayer and runtime override slots.
Creating an Instance
| Method | Meaning |
|---|---|
fx.createRuntime() | Normal runtime copy; use for playback |
fx.createRuntime(true) | Deep-copy authored object data before runtime construction |
fx.createInternalRuntime() | Uses raw FXData; reserved for controlled internal/editor ownership |
Do not use createInternalRuntime() for ordinary gameplay. Mutating its object data can modify the loaded definition shared with later playbacks.
Emit and Destroy
FXRuntime runtime = fx.createRuntime();
runtime.emit(executor); // or emit(executor, delayTicks)
// Later:
runtime.destroy(false); // play the release section, if any, then let remnants drain
runtime.destroy(true); // remove visible remnants immediatelyEmit sends every object to the selected particle host and resets Timeline. A later emit() can revive a destroyed runtime, which the editor uses for replay; gameplay code usually creates a new runtime instead.
destroy(false) plays it first, and isReleasing() is true meanwhile. Particles that never die of age, with a Start Lifetime of 0 or a Loop or PingPong Lifetime Mode, now end with their emitter. Lights and light emission stay on while remnants drain.Timeline Sections
Since 2.2.11runtime.emit(executor, 0, "Idle"); // start from the start of a section instead of 0
runtime.sections().advance(); // stop repeating the current section
runtime.sections().jumpTo("Vanish"); // play from Vanish at the next ticksections() returns the SectionControl that jumps, queues, advances and releases the Timeline's sections. Built-in executors take a start section with setStartSection(name). See Sections.
emmit(...) remains deprecated for source compatibility. Migrate to emit(...); there is no behavior difference.
Playback Rate
Since 2.2.7runtime.setRate(0.5f); // half speed
runtime.setRate(0f); // frozen
runtime.setRate(1f); // normal
float rate = runtime.getRate();setRate sets how fast the whole playback runs. Values below 0 are clamped to 0.
- The rate scales the objects' simulation and the timeline's master clock together. The root's time scale is set to the rate and every descendant inherits it, so Activator, Control, Animation, Signal and Audio clips stay in step with the particles.
- Above
1, the simulation sub-steps:ceil(rate)steps per tick, at most 16. The cost grows with the simulated time. - The rate is not saved in the effect. Photon applies it again every tick, so it survives a Control clip that restarts the tree.
- A Speed Track that targets the root wins over the rate for the ticks it covers.
- Above
1, the timeline advances in steps longer than one tick, so a clip shorter than one step can be skipped. Signals are not skipped.
State Queries
| Query | Meaning |
|---|---|
isFinished() | Timeline has no future content and no object is still playing |
isAlive() | Convenience inverse of isFinished() |
isValid() | Runtime is emitted, not destroyed, and still tracked by its particle host |
isTracked() | Like isValid(), but still true while a destroyed runtime's remnants play out Since 2.2.11 |
isReleasing() | A non-forced destroy is still playing the release section Since 2.2.11 |
A looping emitter never finishes naturally. isValid() solves a different problem from isFinished(): world changes, /photon_client clear_particles, or another engine wipe can discard particles without letting the runtime finish. The check uses the particle host generation plus a root heartbeat and is cheap enough for a cached-runtime guard.
if (cachedRuntime == null || !cachedRuntime.isValid()) {
cachedRuntime = fx.createRuntime();
cachedRuntime.emit(executor);
}Before the first emit and after destroy, isValid() is false. After non-forced destroy, isFinished() becomes true only after the release section, if any, has played and visible remnants drain.