LoadWorldAsync lets a game continue updating and rendering its
current world while Blitz3D+ reads and prepares another one. The returned
handle is a normal world handle, but it cannot be selected, rendered, updated,
or used for audio until WorldReady is true. Loading is independent
of the GUI event system.
loadingWorld=CreateWorld()
SetWorld loadingWorld
camera=CreateCamera()
PositionEntity camera,0,0,-5
spinner=CreateCube()
nextWorld=LoadWorldAsync("levels/level2.world")
While Not WorldReady(nextWorld)
state=WorldLoadState(nextWorld)
If state=WORLD_LOAD_FAILED Then RuntimeError WorldLoadError(nextWorld)
If state=WORLD_LOAD_CANCELLED Then RuntimeError "Load cancelled"
TurnEntity spinner,.4,.8,.2
UpdateWorld
RenderWorld
Text 20,20,"Loading "+(WorldLoadProgress(nextWorld)/10.0)+"%"
Text 20,44,WorldLoadStage(nextWorld)
Flip
Wend
SetWorld nextWorld
SetAudioWorld nextWorld
FreeWorld loadingWorld
Flip, FlipCanvas, Delay, and
WaitTimer service budgeted main-thread finalization automatically.
Loops without those commands can call UpdateAsyncLoads. The
automatic calls share one deadline for the current presentation or wait
boundary. The default finalization budget is 2 milliseconds and can be changed with
SetAsyncLoadBudget.
By default the runtime permits 8 simultaneous world loads and caps aggregate
in-flight source data at 256 MiB, decoded CPU data at 1 GiB on Win64 or 512 MiB
on Win32, and upload staging at 128 MiB. Decoded data is counted only while a
load holds it: a decoded image until its texture is made, and a heightmap,
sound, shader or collider file until its world takes it over and counts it as
ready. So the decoded limit bounds the loads in progress, not the worlds
already loaded, though each loaded world keeps about a kilobyte counted for
every file it read. A world's required assets are all decoded before any is
finalized, so together they must fit within the decoded limit, beside any other
loads in progress. Ready resources, everything kept from loaded worlds, are
counted but not capped, so a world that LoadWorld can load also
loads asynchronously; a world too big for the machine fails when an allocation
fails, and only that load fails. Deployments and automated stress tests may
override those values before startup with BLITZ3D_ASYNC_MAX_LOADS,
BLITZ3D_ASYNC_SOURCE_BYTES,
BLITZ3D_ASYNC_DECODED_BYTES,
BLITZ3D_ASYNC_UPLOAD_STAGING_BYTES, and
BLITZ3D_ASYNC_READY_BYTES, which sets a cap on ready resources.
Memory values are byte counts. A load that would exceed a limit fails with the
requested, retained, and limit values in WorldLoadError.
| Constant | Meaning |
|---|---|
WORLD_LOAD_READY (0) | The world is fully published and selectable. |
WORLD_LOAD_QUEUED (1) | The worker request has been accepted. |
WORLD_LOAD_READING (2) | The source manifest is being read and validated. |
WORLD_LOAD_DECODING (3) | Required assets are being decoded. |
WORLD_LOAD_FINALIZING (4) | Main-thread resources and entities are being created under the time budget. |
WORLD_LOAD_FAILED (5) | The load ended with an actionable error. |
WORLD_LOAD_CANCELLED (6) | Cancellation completed without publishing a partial world. |
WORLD_LOAD_DEFAULT is 0 and
WORLD_LOAD_NO_STREAMING is 1. WORLD_LOAD_PLACEHOLDERS (8)
is for editors and tools: a model, picture, heightmap, sound or shader whose file
is missing or cannot be read loads as a placeholder (a magenta checkered box for
a model) instead of failing the world, and WorldAssetError says why. Asset-kind queries return
WORLD_ASSET_KIND_MODEL (1), TEXTURE (2),
HEIGHTMAP (3), SOUND (4), SHADER (5),
MATERIAL (6), PREFAB (7), or PHYSICS (8), all prefixed with
WORLD_ASSET_KIND_. Use the named constants in programs; the numeric values are documented for
logs, tools, and saved diagnostics.
Materials and prefabs use the reusable
resource V1 format. A world can reference an external descriptor or embed
the same document in an asset's inline member. Dependencies inherit
the strongest loading policy required by their consumers. A placed material or
prefab must be required. Materials are returned as independent brush instances
through WorldAssetMaterial.
CancelWorldLoad is cooperative. Poll until the state becomes
cancelled, or call FreeWorld to invalidate a loading handle
immediately and release its work when the worker unwinds. A failed or cancelled
load never changes the selected graphics or audio world.
A .world file is strict UTF-8 JSON. Relative asset paths are
resolved from the file containing them, not from the process working
directory. A document can contain placed entities, an asset-only preload list,
or both.
{
"format": "blitz3d.world",
"version": 1,
"id": "level.castle",
"settings": {
"ambient": [64,72,96],
"collisions": [
{"sourceType":1,"destinationType":11,
"method":"polygon","response":"slide"}
]
},
"assets": [
{"id":"height","kind":"heightmap","source":"height.bmp","loading":"required"},
{"id":"ground","kind":"texture","source":"ground.jpg","loading":"required"},
{"id":"castle","kind":"model","source":"castle.glb","loading":"required"}
],
"entities": [
{
"id":"land",
"transform":{"position":[-1000,-100,-1000],"scale":[7.8125,100,7.8125]},
"components":[
{"type":"terrain","heightmap":"height","detail":750,"morph":true,
"shading":true,"material":{"mode":"legacy","texture":"ground",
"textureScale":[10,10]}},
{"type":"collision","entityType":11,"pickMode":"polygon"}
]
},
{"id":"keep","components":[{"type":"model","asset":"castle"}]}
],
"extensions": {}
}
settings holds what belongs to the whole world: ambient
light, shadows, collision rules (collisions),
physics.gravity, the world's environment (an environment
asset applied when the world is published, as
ApplyWorldEnvironment
does), its soundscape (what the listener hears outside every zone:
music, ambience and sounds around it, as
soundscapes describes), and an
optional time-of-day cycle:
"cycle": {
"mode": "clock",
"dayLength": 1200,
"start": 9,
"sunrise": 6, "sunset": 20,
"sunPath": {"azimuth": 90, "tilt": 30},
"keys": [
{"time": 6.5, "environment": "sunset"},
{"time": 9, "environment": "clear-day"},
{"time": 19.5, "environment": "sunset"},
{"time": 21, "environment": "moonlit-night", "soundscape": "night"}
]
}
Each key shows an environment asset at a time of day, in hours. The world's
look blends the keys either side of its clock, which starts at
start and runs a whole day in dayLength seconds of game
time (0 stops it). UpdateWorld moves it on:
UpdateWorld 1 is a sixtieth of a second. In clock mode
the sun travels an arc from sunrise to sunset, rising
towards sunPath.azimuth (measured like a yaw: 0 is +Z) and leaning
sunPath.tilt degrees from overhead at noon, and the moon follows
the same arc by night; the keys give everything else. In looks mode
each key shows its environment as made, sun included, and fades in over its
fade hours (1 by default). A key can also name a
soundscape, heard instead of the world's from that time and blended
between keys the same way. A cycle's environments and soundscapes must be required
assets, and a world with a cycle declares the required
environment-blending-v1 extension, so a runtime that cannot blend
refuses it rather than ignoring the cycle.
SetWorldTimeOfDay,
WorldTimeOfDay and
SetWorldTimeSpeed
control the clock. samples/appearance/daynight.world is a complete
example.
World sources use Blitz3D's native left-handed local coordinate space:
positive X is right, positive Y is up, and positive Z is forward. Values are
ordinary Blitz units; version 1 performs no hidden unit conversion. A transform
defaults to position [0,0,0], scale [1,1,1], and an
identity quaternion [0,0,0,1]. Quaternion order is
x,y,z,w; the loader rejects a zero/non-finite quaternion and
normalizes every valid one. Scale follows ScaleEntity, including
finite zero or negative components.
Model assets are decoded in their normal Blitz import space: each format
gets the conversion LoaderMatrix starts with, so glTF and GLB
models have Z negated (their front, +Z in the file, faces -Z) and 3DS models
have Y and Z swapped, exactly as LoadMesh and
LoadAnimMesh load them in a program that leaves
LoaderMatrix alone. LoaderMatrix calls never change
how a world imports a model. Put an editor-authored conversion in the entity
transform instead. This keeps a world deterministic even when its worker starts
later than submission.
| Kind | Version 1 preparation |
|---|---|
model | B3D, X, 3DS, glTF, or GLB geometry and dependencies; use options.animated for an animated hierarchy. |
texture | CPU image decode followed by main-thread canvas creation; options.flags matches LoadTexture. |
heightmap | Immutable 16-bit samples used to construct an independent mutable terrain. |
sound | Worker-side RIFF/WAVE or single-stream mono/stereo Ogg Vorbis decode; options.spatial selects 3D sound behavior. |
shader | Source/dependency preparation on a worker and pipeline creation on the main thread. |
Version 1 also has twelve resource kinds: material,
prefab, physics, particles,
environment, postfx (camera effects),
sound-definition, soundscape, audiomix, animset,
terrain-definition and native-ui. Each is a
definition, inline or in a file of its own, that the loader reads and checks,
and that components and other resources use by ID. See
Reusable Resources and Materials and
resource project workflows.
loading is required, streaming, or
manual. Required assets gate WorldReady and may be
referenced by placed entities. Streaming assets start automatically after the
manifest is prepared and are reported by WorldStreaming. Manual
assets begin when FindWorldAsset first resolves them; poll
WorldAssetReady and WorldAssetError before using a
typed accessor.
Entities have stable IDs, optional parent IDs, local position/rotation/scale, visibility, enabled state, string metadata, and components. Version 1 has sixteen component types:
| Type | What it gives the entity |
|---|---|
model | A model asset, or a primitive: cube, sphere, cylinder, cone, quad (which older files call plane) or infinite-plane. It can also bind materials, an animation set and LOD levels. |
prefab | An instance of a prefab asset, with its inputs and overrides. |
terrain | A terrain from a heightmap or a terrain-definition asset. |
camera | A camera, with projection, clipping, clearing, fog, shadows and an optional camera-effects asset. |
light | A directional, point or spot light. |
listener | The 3D sound listener. |
collision | A collision entity type and pick mode. |
marker | Nothing more: a pivot for positions and metadata (also written metadata-only). |
attachment | Attaches the entity to another entity, or to a named bone of it. |
anchor | Keeps the entity on a point of another entity's terrain or mesh, as AnchorEntity does, so it stays on the surface when the surface changes. The entity's transform is measured from that point. The binding is the one AnchorBinding reports; an entity has an anchor or an attachment, not both. |
particles | A particle effect asset, with its seed and inputs. |
sound-emitter | A sound-definition asset that plays from the entity: manual emitters wait for PlaySoundEmitter, on-activate ones start with ActivateWorldSounds. |
physics | A physics definition asset for the entity and its body targets. |
physics-joint | A distance, hinge, ball, slider or fixed joint between two bodies. |
ground-shape | Shapes a terrain's ground under the entity: levels a pad for it with banks back to the hillside, digs or raises only, or keeps the terrain's scatter off it. The terrain's own heights are kept, so moving the entity brings the hillside back. |
zone | A box, a sphere or a painted terrain region with a fade: cameras inside show its environment and camera effects, and the listener inside hears its soundscape and audio mix, and tagged entities coming inside set off its trigger, as Zones below describes. ZoneWeight measures how far inside it any entity is. |
A zone component makes its entity a region of the
world with a look and sound of its own, such as a cave:
{"type": "zone", "shape": "box", "size": [20, 8, 30], "fade": 4, "priority": 1,
"environment": "cave", "postfx": "murk", "audioMix": "echo", "soundscape": "drips"}
shape is box (the default, with a size,
10 by 4 by 10 unless given) or sphere (with a radius, 5
unless given), centred on the entity in its own space, so a box turns and scales
with the entity and a sphere scales with it. A zone's weight is 1 inside the
shape and falls to 0 across fade world units outside it (2 by
default). Each camera shows the zone's environment mixed into the
world's look by its weight for that camera, after the time of day; zones with
a higher priority are mixed in last, so a cave inside a forest zone
looks like the cave. Of the zones with camera effects (postfx) that
a camera is at least half inside, the one with the highest priority replaces
the camera's own effects until the camera leaves. The listener's
audioMix switches the same way. The listener hears the zone's
soundscape mixed in by its weight, over the world's and the time
of day's: its beds grow louder, its one-shots come more often, and its music
takes over once it is heard most. Every part is optional, and the
assets a zone names must be required assets of their kinds. A world with a zone
declares the required environment-blending-v1 extension.
ZoneWeight measures how
far inside a zone any entity is, and
CameraZoneLooks keeps
a camera on the world's own look. samples/appearance/zones.world is a
complete example.
Outdoors, a zone can take the shape of a region painted on a terrain, such as a swamp with a ragged edge:
{"type": "zone", "shape": "region", "terrain": {"entity": "hills"}, "region": "swamp",
"height": 30, "fade": 8, "environment": "swamp-fog", "soundscape": "frogs"}
terrain names a terrain entity of the same world and
region one of the region IDs of its regionMaps (see
TerrainRegionMap). The zone covers that region's ground up to
height metres above it, 30 unless given, or with a negative height,
that far below it, for a cave under the swamp; the first half metre the other way
counts too, so anything standing on the ground is inside. The height is measured
from the ground under each point, so the zone climbs the region's hills and dips
into its valleys. The shape follows the terrain as it moves, turns and scales, and
changes at once when the region is repainted
(PaintTerrainRegion).
The zone's own entity does not move it: it is there to be hidden, picked and
named. A region zone's edges are as sharp as its region map: a pixel a cell on a
tiled terrain, and the region image's pixels on a classic one. Far from the
cameras, a tiled terrain's ground is measured from the coarse heights it always
keeps, so no tile loads to test it. A world with a region zone declares the
required region-zones-v1 extension. A zone whose terrain is freed, or
whose region its terrain no longer has, is never entered.
A zone's trigger runs actions when an entity
with a tag comes inside its shape (its fade does not count), a painted region's
included:
"trigger": {"enteredBy": "player", "once": false, "actions": [
{"do": "hide", "target": {"entity": "door"}},
{"do": "show", "target": {"entity": "lantern"}},
{"do": "playSound", "sound": "chime"}]}
enteredBy is the tag (player unless given) and
once stops the trigger after its first entry. Each action does
show, hide, enable, disable
or remove to its target, an entity of the world, or
playSound: a required sound-definition asset, heard from the
target when one is given and from the zone otherwise, and only in
the audio world when the sound is 3D. Actions that start and stop behaviours
are refused until the runtime has behaviours. Every
UpdateWorld tests the
positions of the tagged entities, runs the actions of any that came in, and
records an entered or left event for each crossing,
read with CountWorldEvents,
WorldEventName,
WorldEventEntity
(the zone) and WorldEventOther
(the entity). samples/appearance/triggers.world is a complete
example.
FindWorldEntity returns a published entity by ID.
WorldEntityMetadata reads authoring metadata without scanning entity
names. The metadata key tags holds an entity's tags, words of
letters, digits, _ and - separated by spaces, such as
"tags": "coin pickup": the loaded entity has them, for
EntityHasTag,
CountTaggedEntities,
GetTaggedEntity
and zone triggers.
Terrain supports a required heightmap, detail, morphing, shading, LOD ranges,
legacy texture scaling, and layered terrain materials. Once the world is ready,
the result is an ordinary terrain: TerrainY,
TerrainHeight, collisions, and ModifyTerrain work as
normal. A material uses either the legacy members
(texture and a positive textureScale) or the
layers members, never both. Layered materials contain one to four
textures with positive uvScale values. Version 1 maps these to the
uniform scale accepted by TerrainLayer, so both components must
match. More than one layer requires a splatMap. Sharing a heightmap
never shares later terrain deformation.
preload=LoadWorldAsync("asset-preload.world")
While Not WorldReady(preload)
If WorldLoadState(preload)=WORLD_LOAD_FAILED Then RuntimeError WorldLoadError(preload)
Flip
Wend
modelAsset=FindWorldAsset(preload,"ship")
textureAsset=FindWorldAsset(preload,"ship-paint")
ship=CreateWorldAsset(modelAsset)
paint=WorldAssetTexture(textureAsset)
EntityTexture ship,paint
Typed texture, sound, and shader accessors return ordinary public handles.
They remain valid after the source world is freed and must be released with the
matching FreeTexture, FreeSound, or
FreeShader. A created model entity belongs to its destination world.
Compatible texture loads and compatible assets requested by several worlds
reuse ready shared data where their options match.
WorldReady does not wait for assets declared
streaming. Use WorldStreaming and
WorldStreamingProgress if the game wants to show their progress.
Pass WORLD_LOAD_NO_STREAMING to disable automatic optional
streaming. Required content and manually requested assets are unaffected.
A world too big to load at once, or too slow to start, can stream. A
streaming world places every object as usual, so every handle, name, tag,
collider and behaviour is there from the first frame, but the heavy data behind
the objects - mesh geometry and textures - loads as cameras come near, and what
is far away unloads again when memory runs short. WorldReady comes
as soon as what the start view needs has loaded.
world=LoadWorldAsync("big.world",WORLD_LOAD_STREAMING)
While Not WorldReady(world)
If WorldLoadState(world)=WORLD_LOAD_FAILED Then RuntimeError WorldLoadError(world)
Flip
Wend
Turn it on with WORLD_LOAD_STREAMING, or in the world itself:
the World Editor's Loading settings write settings.streaming, and
then every load of the world streams unless the program passes
WORLD_LOAD_NO_STREAMING.
"settings": {
"streaming": { "enabled": true, "start": "spawn-1", "detail": 1.0,
"wait": 0.5, "fade": 0.25, "budget": 800 }
}
start is where the first view is: an entity ID, or
{"position": [x, y, z], "direction": [x, y, z]}. Without it the
world starts at a marker whose ID or name says "spawn", or else at its first
camera. detail scales how far away things load,
wait is how long a camera that jumps waits for what fills its
view, in seconds, and fade how long data that arrives in view takes
to fade in. budget, in megabytes, is optional: the world's own
memory budget. "enabled": false keeps the other members, so turning
streaming back on restores them.
Your program sees no difference apart from timing. Counts, sizes, vertices,
picks and collisions always give the same answers, and anything that reads data
that has not loaded - VertexX, LinePick,
TextureBuffer and the rest - loads it on the spot. An object loads where it would cover a few pixels,
worked out from its size and the camera's zoom and view, never beyond the
camera's range, and a moving camera reaches farther ahead. Nobody sets a
distance for each object; WorldStreamingDetail
scales them all.
Data unloads only when the memory budget is reached (SetStreamingBudget; by default the GPU's own budget), and only when it is well beyond where it is needed: first its GPU copy is freed (parked), then the rest, farthest and least recently needed first. Textures keep a small version that draws until the full one is back. Data that arrives while its object is in view fades in.
Sounds stream too. The samples of a sound a world's objects play (sound
emitter components) load where a listener could hear them, from how fast the
listener's sounds fade (CreateListener), and go a few seconds
after no emitter can be heard and none plays it, or sooner when memory runs
short. Every object that plays the sound shares its samples. Playing one whose
samples have gone loads them first. Sounds long enough to play from their
files, such as music, take no memory to stream.
A camera that jumps - a respawn, a teleporter, a cut - waits for what covers
much of its new view, up to wait seconds, and the rest fades in.
For a loading screen instead, call
PreloadWorldView
with the place before the jump, and wait until
WorldStreaming is
False. An entity with "keepLoaded": true
(EntityKeepLoaded)
stays loaded wherever it is, ready for a cut scene.
A model asset can say how big it is before it loads, with a
summary; streaming leaves a model unloaded at the start only when
it has one. The World Editor writes summaries whenever it saves, and
b3dworldc --summarise level.world adds them to a world made by
hand:
{ "id": "barrel", "kind": "model", "source": "models/barrel.glb",
"summary": { "bounds": [-0.4, 0, -0.4, 0.4, 1.1, 0.4], "surfaces": 2,
"vertices": 1840, "triangles": 2210, "bytes": 412000 } }
Without summaries a world still streams after the start, but loads every
model at the start to measure it. DrawRenderStats with flag 2048
shows what is loaded, parked and unloaded against the budget, and setting the
environment variable BLITZ3D_STREAMING_STRESS=1 streams every world
and unloads everything it can after every frame: a world that plays the same
under it plays the same streaming.
The shipped b3dworldc command validates the same format used by
the runtime:
b3dworldc level.world b3dworldc --no-files editor-draft.world b3dworldc --quiet worlds/*.world
The machine-readable contract is installed as
world/world-v1.schema.json. Runtime validation additionally rejects
duplicate keys, malformed UTF-8, non-finite values, excessive nesting and
document sizes, invalid references, parent cycles, unsupported required
extensions, and missing dependencies.
An editor may normalize whitespace and property order, but it must preserve
the order of asset, entity, component, layer, and collision arrays because that
order is observable. It must keep explicit IDs stable, retain unknown optional
properties and the complete JSON value of every unknown optional namespaced
extension, and refuse to silently discard an unknown extension whose
required member is true.
Keep relative asset paths relative to the .world file; do not
replace them with workstation-specific absolute paths. Emit strict UTF-8 JSON,
finite numbers, unique object keys, and quaternion components in
x,y,z,w order. Saving must not copy runtime terrain deformation or
other gameplay state back into the authoring source. Run b3dworldc
after every save and treat the runtime validator as authoritative where JSON
Schema cannot express duplicate keys, finite-number rules, path resolution, or
cross-reference semantics.
SetWorld, SetAudioWorld, update, render, or entity creation against a loading handle. Poll it first.WorldLoadError contains the dependency or validation summary for a failure.UpdateAsyncLoads -1 so main-thread finalization is not starved.b3dworldc --summarise on it: without summaries, every model loads at the start.WorldStreamingDetail; if they pop in after jumps, lengthen WorldStreamingWait or preload with PreloadWorldView.samples/worlds/Async_Loading_Castle.bb loads the classic Castle terrain and static environment while an animated loading world remains responsive.samples/worlds/Asset_Only_Preload.bb consumes an asset-only manifest through stable IDs and demonstrates public-handle lifetime.