Skip to content

FXRuntime Lifecycle ​

A water effect produced by one live FXRuntime instance

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 ​

MethodMeaning
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 ​

java
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 immediately

Emit 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.

Changed 2.2.11 When the Timeline has a release section, 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.11
java
runtime.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 tick

sections() 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.

`emit` spelling 2.2.0

emmit(...) remains deprecated for source compatibility. Migrate to emit(...); there is no behavior difference.

Playback Rate ​

Since 2.2.7
java
runtime.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 ​

QueryMeaning
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.

java
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.

Released under the MIT License.