Skip to content

Project API

TeoLim edited this page Oct 12, 2026 · 10 revisions

Project API

NWBLot projects plug into the loader through a small callback surface. The Testbed is the current project using that API, and it is the best reference for how projects should create a world, register systems, and request shutdown.

Namespace Ownership

Project classes and helpers belong to the application namespace. Testbed owns CoolStuff/Testbed/namespace.h, which defines TESTBED_BEGIN/TESTBED_END for namespace Testbed without including the engine namespace header. Testbed uses engine types through the relevant API headers; those headers may include root engine_namespace.h transitively.

The required functions below form the loader integration adapter and keep their declarations in NWB. Their application implementations delegate to project-owned code. Ordinary application types are not declared inside NWB or exposed through compatibility aliases. Public features under NWB::Impl, such as UI and renderer systems, remain valid application-facing engine APIs; private/detail and selected graphics-provider boundaries still apply.

Required Project Functions

The project-facing API consists of:

  • QueryProjectFrameClientSize
  • QueryProjectWindowTitle
  • ConfigureProjectRuntime
  • CreateProjectEntryCallbacks
  • CreateInitialProjectWorld
  • DestroyInitialProjectWorld

QueryProjectFrameClientSize returns a client-area size. Testbed returns 1280x900.

QueryProjectWindowTitle returns the platform window title. Testbed returns NWB Testbed.

ConfigureProjectRuntime(ProjectStartupContext&) runs after the loader creates its Core::Frame but before graphics/device initialization. The context provides graphics, the project object arena, and filesystemFactory. Use it for pre-device policies such as the required bindless heap ABI, an HDR10 request, or a project-owned IFilesystem factory; return false to stop startup. Testbed sets the bindless heap ABI and requests HDR10 through this callback, and leaves filesystemFactory empty so every mount uses VolumeFileSystem. See HDR10 Presentation and Filesystem.

CreateProjectEntryCallbacks returns an IProjectEntryCallbacks implementation. Testbed returns Testbed::Project.

CreateInitialProjectWorld(context) creates and wires the ECS world, returning Expected<UniquePtr<Core::ECS::World>, TStringView>. The admitted value owns the world; failure carries guaranteed borrowed diagnostic text. The loader checks admission before publishing the owner. ShaderPathResolveCallback returns Expected<Name> for the requested shader/variant/stage, without an output path parameter.

DestroyInitialProjectWorld unregisters render passes and frame graph contributors, waits for graphics work, clears the world, and releases it.

Callback Object

IProjectEntryCallbacks has three overridable callbacks:

  • onStartup
  • onShutdown
  • onUpdate(f32 delta)

The default implementation returns success and does nothing. Real projects normally use:

  • onStartup to create scene entities and register input handlers.
  • onUpdate to update project-side state and return whether runtime should continue.
  • onShutdown to release project-only resources.

Testbed creates the initial world in the Testbed::Project constructor, verifies the required systems are present, creates scene entities during onStartup, and destroys the world during shutdown/destruction.

Project Runtime Context

ProjectRuntimeContext provides engine services that project code is allowed to use:

  • graphics runtime
  • input dispatcher
  • clipboard and native text-input services
  • project object arena
  • frame-owned CpuTaskScheduler
  • frame-owned GpuTaskScheduler
  • loader-owned project CpuTaskScope
  • typed asset manager
  • mounted IFilesystem
  • frame graph registry
  • read-only perf session
  • shader path resolver
  • telemetry capture setter
  • telemetry upload flush callback
  • perf capture setter
  • request quit callback

Convenience methods:

  • setTelemetryCapture(options)
  • flushTelemetryUpload(clearAfterUpload)
  • setPerfCapture(options)
  • gpuTimingView()

The context is assembled by the loader after graphics initialization, cooked volume mounting, asset codec registration, asset manager creation, and shader archive loading. Project code can therefore assume those services exist when callbacks are created.

Testbed World Wiring

Testbed creates a Core::ECS::World using the context object arena and cpuTasks. It then adds these systems:

  • MeshSystem
  • RendererSystem
  • ModelSystem
  • MeshSkinningSystem
  • UiLayerSystem

The renderer is constructed with:

  • the world
  • Core::GraphicsRuntime
  • AssetManager
  • shader path resolver

The model system is constructed with model renderer hooks, so model expansion can create renderer-facing components.

The mesh skinning system is constructed with graphics, asset manager, the mesh system, and shader path resolver. It registers as a render pass before the main renderer, so skinned runtime mesh data is ready for renderer consumption.

The UI system (UiLayerSystem) is constructed with world, graphics, input, clipboard and text-input services, asset manager, shader path resolver, default skin reference, font references, and a UiLayerPresentation mode.

Render Pass Registration

Testbed registers render passes in this order:

  1. MeshSkinningSystem
  2. RendererSystem
  3. UiLayerSystem

That order is important:

  • skinning updates runtime mesh buffers before scene rendering
  • renderer draws the scene into deferred/transparency/composite targets
  • UI prepares an independent display-linear layer that final presentation blends with mapped scene color

Testbed uses UiLayerPresentation::Scene: the UI contributes its layer to the renderer graph and does not submit a separate backbuffer draw or presentation. Render-pass registration order establishes preparation and skinning inputs; graph resource dependencies join the UI layer at final presentation. The output-layer contributor declares independent raster work and exact-generation consumption at accepted final presentation.

Testbed also registers RendererSystem as a frame graph contributor so telemetry capture can include renderer frame-graph information.

World Destruction

DestroyInitialProjectWorld validates that expected systems still exist, then:

  1. unregisters renderer frame graph contribution
  2. removes skinning, renderer, and UI render passes
  3. calls GraphicsRuntime::waitForIdle() and requires either a successful join or terminal device loss
  4. joins the world's CPU task scope while clearing the ECS world
  5. releases the world pointer

This avoids destroying ECS-owned renderer state while backend command work may still reference it.

Shutdown Rule

Project code should request shutdown through:

ProjectRuntimeContext::requestQuit

Do not call platform-specific quit APIs from project code or smoke-test projects. The frame owns the platform loop and can exit without submitting another graphics frame when quit is requested during project update.

Testbed Scene Creation

During onStartup, Testbed creates:

  • an active camera entity
  • a camera entity positioned in front of the character
  • a 45-degree directional light
  • a point light
  • a skinned character model entity
  • a static ground plane model entity
  • a UI entity with a UiPaintComponent paint callback (drawCustomUiControls + drawUiControls)

The character and ground plane are model-driven. Their ECS entities contain ModelComponent and RendererComponent with typed asset references; model expansion and renderer systems do the rest.

Clone this wiki locally