raea/remote

A headless Bevy App you open, step and query from TypeScript, through Bevy's remote protocol.

View as Markdown

BevyApp.open starts a headless Bevy App with the plugins you name and hands back an object that speaks Bevy's remote protocol (BRP): spawn entities, insert and query components, set resources, trigger events and step frames. TypeScript systems run inside its schedules. It's how a test or a raea run script drives the engine without a composition.

TS
import { BevyApp } from "raea/remote";

const app = BevyApp.open({ plugins: [] });
const e = app.spawn();
app.step(3);
console.log(app.methods.length, e);
TS
import { BevyApp } from "raea/remote"

raea/remote exports 1 class, 1 type. Bevy's counterpart: bevy_remote. Source: sdk/raea/src/remote.ts.

Classes

BevyApp

A headless Bevy App driven from TypeScript through Bevy's remote protocol: open it with its plugins, then spawn, insert, query and step it, and add TypeScript systems to its schedules.

MemberWhat it does
BevyApp.open(o: OpenOptions): BevyAppA headless App with these plugins; Startup has run, no frame has stepped. TypeScript plugins are built in order once the App is open, then finished and cleaned up; their Startup systems run before the first frame.
BevyApp.stepTogether(apps: BevyApp[], frames?: number): number[]Apps stepped together, a frame at a time: a network's peers. Returns each one's frame.
.methods: string[]the methods this App answers (BRP's, raea's and its plugins')
.id: numberthe App's handle in the engine
.isPluginAdded(name: string): booleanWhether a plugin of this name is in the App (Bevy's is_plugin_added), Rust or TypeScript.
.addPlugins(...refs: PluginRef[]): BevyAppAdds TypeScript plugins and groups (Bevy's add_plugins): each built in order, then each finished, then each cleaned up. A Rust plugin is compiled in and must be named when the App opens; naming a new one here throws.
.addSystems(schedule: string, systems: ((app: BevyApp, frame: number) => void) | ((app: BevyApp, frame: number) => void)[], o?: SystemOptions): BevyAppSystems in a schedule (Bevy's add_systems), each as system adds one; one of the startup schedules (PreStartup, Startup, PostStartup) runs them once, before the App's next frame. Returns the App, to chain.
.insertResource<T>(r: Resource<T>, value: T): BevyAppBevy's insert_resource: the resource's value set (setResource).
.addObserver<T>(e: string | Event<T>, o?: { entity?: number | undefined; then?: Then[] | undefined; }): Observing<T>Bevy's add_observer: an observer of an event inside the App (observer).
.step(frames?: number): numberframes frames on. Returns the frame it's on. With TypeScript systems (system), each frame is run schedule by schedule and they run in theirs.
.system(schedule: string, run: (app: BevyApp, frame: number) => void, o?: SystemOptions): () => voidA system written in TypeScript: run is called every frame right after Bevy's own systems of schedule (one of the frame's: "First", "PreUpdate", "StateTransition", "RunFixedMainLoop", the fixed step's "FixedFirst", "FixedPreUpdate", "FixedUpdate", "FixedPostUpdate", "FixedLast" (one fixed step a frame: the clock's step is the timestep), "Update", "SpawnScene", "PostUpdate", "Last"), on that frame's World, before the next schedule's; it queries, changes and observes through the App's methods (query, get, insert, call("world.mutate_components"), observe...). Returns a function that removes it. o places it as Bevy's add_systems(...).run_if().before().after().in_set() would: when is its run condition (asked every frame before it runs; see conditions), before/after name other systems or sets of the same schedule it runs before or after (a cycle is refused), set puts it in a set that configureSet gives a condition and an order. Without an order, systems run in the order they were added.
.configureSet(name: string, o: { when?: Condition | undefined; before?: string | string[] | undefined; after?: string | string[] | undefined; }): voidA set of TypeScript systems (Bevy's SystemSet with configure_sets): when is asked once per schedule run, before its first member, and a false skips them all; before/after order the whole set against systems or sets.
.oneShot<R = void>(fn: (app: BevyApp, frame: number) => R): OneShot<R>A one-shot system (Bevy's register_system + run_system): run() runs it now, on the World as it is; queue() runs it once the current TypeScript system returns (as Commands::run_system applies at the next sync point), or at the end of the frame if called outside a step. remove() unregisters it.
.frameSchedules(): string[]The schedules a frame runs, in order (Bevy's MainScheduleOrder).
.call<A = unknown>(method: string, params?: unknown): AAny method of the App's table, run now: its answer, or a throw with the method's message.
.spawn(...components: With[]): numberA new entity with these components.
.insert(entity: number, ...components: With[]): voidAdds these components to an entity, replacing any it has of the same type.
.get<T>(entity: number, c: Component<T>): T | undefinedOne component of an entity, or undefined when it hasn't got one.
.query(o: { data?: Component<unknown>[] | undefined; has?: Component<unknown>[] | undefined; without?: Component<unknown>[] | undefined; }): { entity: number…Entities with every component in has (and none in without), with the data components' values.
.resource<T>(r: Resource<T>): TA resource's value.
.setResource<T>(r: Resource<T>, value: T): voidSets a resource's value, adding it if the app hasn't got one.
.trigger<T>(e: Event<T>, value: T): voidTriggers an event: its observers run now.
.write<T>(m: Message<T>, value: T): voidWrites a message, for its readers' next run.
.observe<T>(e: Event<T>, entity?: number | undefined): () => T[]Starts observing an event (BRP's world.observe+watch). Each call of the returned function gives the events triggered since the last call, in order. entity limits it to events aimed at that entity.
.hook(on: "Add" | "Insert" | "Discard" | "Remove" | "Despawn", component: string | Component<unknown>, then?: Then[]): Observing<unknown>A component hook as an observer inside the App: when component is added, inserted, discarded, removed or its entity despawned, then's calls (BRP methods, "$entity" in their params standing for the entity) run right there, in the same flush, before anything else sees the World. seen() gives the entities it fired for since the last call; stop() removes it.
.observer<T>(e: string | Event<T>, o?: { entity?: number | undefined; then?: Then[] | undefined; }): Observing<T>An observer inside the App for a reflected event: on each trigger (or each aimed at entity), then's calls run in the same flush, "$event" standing for the event's value and "$entity" for its target.
.state<T>(s: State<T>): T | nullA state's value (null before it exists).
.setState<T>(s: State<T>, value: T): voidA state's next value: it changes at the next frame's StateTransition.
.schema(crates?: string[] | undefined): Record<string, unknown>The App's registered types as JSON schema (BRP's registry.schema), filtered by crate.
.close(): voidShuts the app down; its methods fail after.

Types

OpenOptions

FieldTypeWhat it is
plugins (required)PluginRef[]the App's plugins, in order: Rust ones by the name this build registered them under (a declaration's name, or its halves: lightyear_server), compiled in and added before Startup; TypeScript ones (definePlugin) built after
fpsnumberframes a second: one step is 1/fps of Time (default 60)
servenumberserve BRP over HTTP on this port (a dev tool, a BRP client, curl)