App lifecycle
An app is one app { } block: its window, the callbacks the host calls as it starts, draws each frame and shuts down, and the modules and scene it installs.
module: com.awakekt.awake.engine:bootstrap
Desktop · Android · iOS · Web
The app is shared code. A platform entry point, such as runVulkanDesktopGame on desktop or
launchWebGpuGame in the browser, creates the window and renderer and drives the app; see
Your first window.
Describe an app
window sets the title, size and backend. ready runs once the renderer exists, render runs every
frame, and dispose runs when the app shuts down.
val game = app {
window {
title = "Harbor Town"
size(1280, 720)
backend.vulkan()
}
ready { renderer -> log += "ready" }
render { frame -> log += "frame ${frame.delta}" }
dispose { log += "dispose" }
}
A host then calls the callbacks in order. A test can call them itself, with a stand-in renderer:
game.ready(renderer) // a host calls this once its renderer exists
game.update(delta = 0.016f, viewportWidth = 320f, viewportHeight = 240f)
game.dispose()
Split an app into modules
appModule { } holds services and callbacks without a window, so a feature can be written once and
installed into any app with module(…):
val stats = appModule {
val counter = FrameCounter()
service(FrameCounter::class, counter)
render { counter.frames += 1 }
}
val game = app {
window { title = "With stats" }
module(stats)
}
FrameCounter is an ordinary class:
appSpec { } takes the same block as app { } but returns the spec instead of a running app. Use it
in tests and tools that inspect the configuration or create the lifecycle themselves:
val spec = appSpec {
window { size(320, 240) }
module(stats)
}
val lifecycle = spec.createLifecycle()
Register scene systems
scene { } (or ecs { }) installs the scene runtime as a module. It owns the World and runs your
systems in two phases: fixed steps for simulation, and one pass per rendered frame.
val game = app {
scene("harbor-town") {
fixedSystem("physics") { OrderProbe("fixed", trace) }
frameSystem("camera") { OrderProbe("frame", trace) }
update { step, input -> trace += "update" }
onReady { trace += "ready" }
onDispose { trace += "dispose" }
}
}
In scene { } |
Runs | Use it for |
|---|---|---|
fixedSystem(name) { … } |
Zero or more times a frame, each with a delta of 1/60 s | Physics and gameplay that must not depend on frame rate. |
update { step, input -> } |
Once per fixed step, after the fixed systems | Small bits of gameplay without a system class. |
frameSystem(name) { … } |
Once per rendered frame, with the frame's delta | Cameras, input, animation, anything drawn. |
onReady { … } |
Once, after the scene's entities are created | Tagging entities, looking things up by name. |
onDispose { … } |
Once, when the app shuts down | Releasing what onReady took. |
A system factory runs once, when the renderer is ready and before the scene's entities are created.
Its receiver is the scene runtime, so it can read world, renderer and requireService(…); look
entities up by name later, in onReady or when the system runs. fixedSystem and frameSystem
return a handle; runtime.system(handle) gives back the instance.
Properties
window { }:
| Property | Type | Default | What it does |
|---|---|---|---|
title |
string | "Awake" |
Window title. |
size(width, height) |
pixels | 800 × 600 |
Initial window size. |
backend |
default() · vulkan() · webGpu() · openGl() |
default() |
Which renderer the host should use. |
presentMode |
PresentMode |
Auto |
How finished frames reach the display. A request; the host logs what it got. |
frameRateMode |
FrameRateMode |
Auto |
Target frame rate. |
throttleCpuWhenNotForeground |
boolean | true |
Slow down when the window is in the background. |
backgroundFrameRate |
frames per second | 15 |
The rate used while throttled. |
App callbacks, on app { } and appModule { }:
| Callback | Called |
|---|---|
ready { renderer -> } |
Once, when the renderer exists. Suspends. |
render { frame -> } |
Every frame. frame has delta, viewportWidth, viewportHeight, input and density. |
resize { width, height -> } |
When the surface changes size. |
pause { }, resume { } |
When the host pauses and resumes the app. |
dispose { } |
Once, at shutdown. |
service(type, value) |
Registers a value any module can look up with requireService(type). |
How it works
Every callback is added to a list, and each module adds to the same lists, so installing two modules
runs both of their render blocks every frame, in the order they were installed. dispose callbacks
run in reverse, so the last module installed is the first torn down. After them, services that are
AutoCloseable are closed.
Each frame, the scene runtime adds the frame's delta to an accumulator and runs one fixed step,
fixed systems then update, for every 1/60 s it holds. Then it runs the frame systems, then its own
transform and render systems, so a frame system's changes are drawn in the same frame. A fixed
system that implements InterpolatedSystem also gets interpolate(world, alpha) just before the
frame systems, with how far the clock is into the next step, so it can blend its last two states. A new scene
runs one frame pass with a delta of 0 right after onReady.
At most five fixed steps per frame
After a long frame the runtime runs five fixed steps and drops the rest of the backlog, so the simulation slows down instead of freezing the display.
One Input per app
Every app has one Input service, created with it. Look it up with requireService(Input::class);
see Input.
See also
- ECS for writing systems.
- Scene DSL for the entities inside
scene { }. - Your first window for the desktop entry point.