Help home · Guides · Language reference · Command reference · Parser sample · A* sample
The Blitz3D+ Job system runs finite CPU work and read-only file processing on an isolated background worker. The game or editor stays responsive while the main thread continues to update, render, play audio, and handle input. When the work finishes, the main thread explicitly takes a Bank containing the result.
Worker Function,
StartJob, and the Job commands are available in Extended language
mode. They do not change the meaning of existing Original-mode programs.| Main program | Background worker |
|---|---|
| Creates a request Bank. | Receives an immutable private copy. |
Calls StartJob and immediately continues. | Runs one compiler-checked Worker Function. |
| Updates the game, renders, and polls for completion. | Performs calculations or reads and processes files. |
| Takes the result Bank and creates or changes engine objects. | Returns one worker-created Bank; it never changes the world directly. |
The input and result Banks are messages, not shared memory. This keeps the ordinary Blitz programming model intact: worker code cannot race the main program's worlds, entities, graphics, audio, input, or global variables.
A public worker entry accepts one Bank handle and returns one Bank handle.
Declare it with Worker Function, then pass its name directly to
StartJob:
Worker Function DoubleValue(inputBank)
outputBank = CreateBank(4)
PokeInt outputBank, 0, PeekInt(inputBank, 0) * 2
Return outputBank
End Function
requestBank = CreateBank(4)
PokeInt requestBank, 0, 21
job = StartJob(DoubleValue, requestBank, 1L)
FreeBank requestBank
While Not JobFinished(job)
; Keep updating and drawing the game here.
Delay 1
Wend
If JobState(job) = JOB_SUCCEEDED
resultBank = TakeJobResult(job)
Print "Result: " + PeekInt(resultBank, 0)
FreeBank resultBank
Else
Print "Job failed: " + JobError(job)
EndIf
FreeJob job
StartJob copies the request before it returns, so immediately
changing, resizing, or freeing requestBank is safe. Passing zero
instead supplies a valid empty input Bank. A successful result is transferred
exactly once by
TakeJobResult.
The complete runnable version
is included with the language reference.
Jobs work best for bounded tasks which can be described by input bytes and produce output bytes:
A Job is not a raw thread and is not intended for a permanent game loop, socket receive loop, file watcher, or one Job per network packet. Keep Jobs finite and reasonably substantial; very tiny tasks usually cost more to submit and transfer than they save.
Engine and operating-system handles remain on the main thread. A worker can read and parse a model description, build terrain height data, decode a custom format, or prepare placement records in a Bank. The main thread then uses that Bank to create textures, meshes, terrains, entities, sounds, physics objects, or worlds.
; Main thread
job = StartJob(PrepareLevelData, requestBank, levelRevision)
; Continue showing a menu, loading animation, or the current level.
completed = PollJob()
If completed = job And JobState(job) = JOB_SUCCEEDED
levelData = TakeJobResult(job)
; CreateTerrain, CreateMesh, LoadTexture, and other engine calls go here.
FreeBank levelData
FreeJob job
job = 0
EndIf
LoadTexture, LoadMesh, LoadSound, or world
commands worker-safe. Use Jobs for the read/parse/prepare stage and publish the
finished data on the main thread. Purpose-built asynchronous resource and world
loaders can use the same native scheduler in later releases.PollJob
returns one completed Job handle, or zero when there is nothing to collect. It
is the usual choice inside a game or editor loop:
Repeat
completed = PollJob()
While completed
Select JobState(completed)
Case JOB_SUCCEEDED
resultBank = TakeJobResult(completed)
; Validate and apply the result on this main thread.
FreeBank resultBank
Case JOB_FAILED
DebugLog "Background Job failed: " + JobError(completed)
Case JOB_CANCELLED
DebugLog "Background Job was cancelled"
End Select
FreeJob completed
completed = PollJob()
Wend
; Update, RenderWorld, draw the interface, and Flip as normal.
Until KeyHit(1)
PollJob consumes only the completion notification. It does not
free the Job or take its result. You must still call
FreeJob.
JobFinished
inspects one known Job without consuming its notification.
WaitJob waits for
one Job, while
WaitAnyJob
waits for and consumes any completion. Waiting does not render, call
Flip, pump GUI events, or run a generated callback, so games should
normally poll instead.
A worker can publish progress with
SetJobProgress:
For item = 0 To itemCount - 1
; Process one item...
If (item And 63) = 0
SetJobProgress item, itemCount, "Building navigation data"
EndIf
Next
On the main thread,
JobProgress
returns a monotonic value from 0 to 1000. The done, total, and text queries
retain the worker's most recent snapshot. Progress is advisory: do not use it
as the completion test.
CancelJob
requests cooperative cancellation. The compiler inserts cancellation checks at
worker entry points and loop backedges. Long indivisible algorithm steps can
also test
JobCancelled
and return early. Cancellation is a request, so continue polling until the Job
reaches a terminal state, then free it.
Games and editors often start a newer request before an older one finishes.
Pass a revision, generation, or request sequence as the Long tag. Before
publishing a completion, compare
JobTag with the
current revision:
buildRevision = buildRevision + 1
job = StartJob(BuildPreview, requestBank, buildRevision)
; Later, after PollJob returns the handle...
If JobTag(job) = buildRevision And JobState(job) = JOB_SUCCEEDED
previewData = TakeJobResult(job)
; Publish the current preview.
FreeBank previewData
EndIf
FreeJob job ; A stale unclaimed result is discarded here.
Tags do not affect scheduling. They are application-owned values retained unchanged by the Job system.
Treat every request and result Bank as a small binary file format. For data which may evolve, start with a magic value, format version, record count, and validated offsets. Check sizes before reading variable-length records. Prefer integers, Longs, floats, and UTF-8 byte strings with explicit lengths.
| Resource | Owner and lifetime |
|---|---|
| Source request Bank | The main program owns it. StartJob copies it before returning. |
| Worker input Bank | Immutable and private to the Job; never free or return it. |
| Worker result Bank | Create it inside that Job and return it. Do not free it in the worker. |
| Taken result Bank | The main program owns it and must call FreeBank. |
| Job handle | Call FreeJob after collecting, rejecting, failing, or cancelling it. |
Returning zero, returning the input Bank, returning a freed Bank, taking a
result twice, or using a Job handle after FreeJob is an error.
Worker locals may use numeric values, Strings, fixed arrays, local Types,
Lists, Maps, Sets, Queues, Stacks, Banks, deterministic math and random
commands, and read-only files, directories, and streams. Helper functions
called by a worker must also be declared with Worker Function.
The compiler rejects access to main globals, dynamic arrays, ordinary Functions, Methods, constructors with initialisers, engine handles, graphics, worlds, entities, physics, audio, input, GUI, timers, sockets, multiplayer, UserLibs, DLLs, writable files, environment mutation, DATA/READ/Restore, Gosub, nested Jobs, and waits. Runtime checks provide a second boundary. A runtime command is main-only unless it is explicitly marked worker-safe.
A worker runtime error fails that Job without unwinding the main program or
opening the GUI debugger from the background thread. Inspect
JobError,
JobErrorFile,
JobErrorLine,
and
JobErrorFunction
after JOB_FAILED.
Every submitted Job should finish in one of these ownership paths:
PollJob rather than an infinite wait.StartJob and follow the linked command pages.