AppController
The AppController class defines the application lifecycle and input-event interface of the TypeScript runtime. It is the main extension point used to implement application behavior.
The default implementation does not provide any rendering or interaction logic. Its purpose is to declare the lifecycle and input callbacks that the application can override. In practice, applications typically define a subclass of AppController and implement only the methods they need.
An AppController instance is associated with a MainLoop, which injects the runtime context and forwards lifecycle and input events to the controller.
Purpose
Section titled “Purpose”AppController is responsible for handling:
- Initialization of application resources
- Resize notifications
- Per-frame updates
- Rendering
- Destruction and cleanup
- Keyboard, mouse, and touch input events
This design keeps application logic separate from the main loop and rendering surface.
Constructor
Section titled “Constructor”constructor()
Section titled “constructor()”Creates a new AppController instance.
The controller is initialized without an associated main loop. The MainLoop assigns itself to the controller during its own construction.
Properties
Section titled “Properties”mainLoop: any
Section titled “mainLoop: any”Gets or sets the MainLoop associated with this controller.
In normal usage, this property is assigned automatically by MainLoop, so application code usually reads it but does not set it manually.
canvas: any
Section titled “canvas: any”Read-only shortcut to the canvas associated with the current main loop.
Returns this._mainLoop?.canvas.
renderer: any
Section titled “renderer: any”Read-only shortcut to the renderer associated with the current canvas.
Returns this._mainLoop?.canvas?.renderer.
viewport: { width: number; height: number; aspectRatio: number }
Section titled “viewport: { width: number; height: number; aspectRatio: number }”Read-only access to the current viewport.
If the controller is not yet attached to a canvas, this property returns:
{ width: 0, height: 0, aspectRatio: 0 }Lifecycle Methods
Section titled “Lifecycle Methods”These methods define the main application lifecycle. They are intended to be overridden in subclasses.
async init(): Promise<void>
Section titled “async init(): Promise<void>”Called during application startup.
Override this method to initialize resources, create scene objects, allocate GPU resources, or perform any other startup logic required by the application.
reshape(width: number, height: number): void
Section titled “reshape(width: number, height: number): void”Called when the rendering surface changes size.
Override this method to update the viewport, projection matrices, render targets, or any size-dependent resources.
Parameters
Section titled “Parameters”width: New viewport widthheight: New viewport height
async frame(delta: number): Promise<void>
Section titled “async frame(delta: number): Promise<void>”Called once per frame update.
Override this method to implement time-dependent application logic such as animations, physics, state updates, or camera movement.
Parameters
Section titled “Parameters”delta: Elapsed time since the previous frame
display(): void
Section titled “display(): void”Called when the application must render a frame.
Override this method to issue rendering commands.
destroy(): void
Section titled “destroy(): void”Called when the application exits.
Override this method to release resources, detach references, or perform any necessary cleanup.
Input Event Methods
Section titled “Input Event Methods”These methods are invoked by the runtime when input events occur. All of them are optional and may be overridden as needed.
Keyboard
Section titled “Keyboard”keyDown(evt: Bg2KeyboardEvent): void
Section titled “keyDown(evt: Bg2KeyboardEvent): void”Called when a key is pressed.
keyUp(evt: Bg2KeyboardEvent): void
Section titled “keyUp(evt: Bg2KeyboardEvent): void”Called when a key is released.
mouseUp(evt: Bg2MouseEvent): void
Section titled “mouseUp(evt: Bg2MouseEvent): void”Called when a mouse button is released.
mouseDown(evt: Bg2MouseEvent): void
Section titled “mouseDown(evt: Bg2MouseEvent): void”Called when a mouse button is pressed.
mouseMove(evt: Bg2MouseEvent): void
Section titled “mouseMove(evt: Bg2MouseEvent): void”Called when the mouse moves.
mouseOut(evt: Bg2MouseEvent): void
Section titled “mouseOut(evt: Bg2MouseEvent): void”Called when the mouse leaves the canvas area.
mouseDrag(evt: Bg2MouseEvent): void
Section titled “mouseDrag(evt: Bg2MouseEvent): void”Called when the mouse moves while dragging.
This is a common place to request a redraw in manual update mode.
mouseWheel(evt: Bg2MouseEvent): void
Section titled “mouseWheel(evt: Bg2MouseEvent): void”Called when the mouse wheel is used.
Applications can stop event propagation if needed.
touchStart(evt: Bg2TouchEvent): void
Section titled “touchStart(evt: Bg2TouchEvent): void”Called when a touch interaction begins.
touchMove(evt: Bg2TouchEvent): void
Section titled “touchMove(evt: Bg2TouchEvent): void”Called when a touch interaction moves.
touchEnd(evt: Bg2TouchEvent): void
Section titled “touchEnd(evt: Bg2TouchEvent): void”Called when a touch interaction ends.
Overriding Strategy
Section titled “Overriding Strategy”A typical application does not implement every method. Instead, it subclasses AppController and overrides only the callbacks required for its behavior.
The most commonly implemented methods are:
init()reshape()frame()display()
Input methods are added depending on the interaction model of the application.
Example
Section titled “Example”import MainLoop, { FrameUpdate } from "bg2e-js/ts/app/MainLoop.ts";import Canvas from "bg2e-js/ts/app/Canvas.ts";import AppController from "bg2e-js/ts/app/AppController.ts";import WebGLRenderer from "bg2e-js/ts/render/webgl/Renderer.ts";import Bg2KeyboardEvent from "bg2e-js/ts/app/Bg2KeyboardEvent.ts";import Bg2MouseEvent from "bg2e-js/ts/app/Bg2MouseEvent.ts";import Bg2TouchEvent from "bg2e-js/ts/app/Bg2TouchEvent.ts";
class MyAppController extends AppController { async init() { console.log("init"); }
reshape(width: number, height: number) { console.log(`reshape - width:${width}, height:${height}`); const { gl } = this.renderer as WebGLRenderer; gl.viewport(0, 0, width, height); }
async frame(delta: number) { console.log(`frame - elapsed time: ${delta}`); }
display() { const { gl } = this.renderer as WebGLRenderer; gl.clearColor(0, 0, 0, 1); gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT); console.log("display"); }
keyDown(evt: Bg2KeyboardEvent) { console.log(`keyDown - key: ${evt.key}`); }
keyUp(evt: Bg2KeyboardEvent) { console.log(`keyUp - key: ${evt.key}`); }
mouseUp(evt: Bg2MouseEvent) { console.log(`mouseUp - mouse location: ${evt.x}, ${evt.y}`); }
mouseDown(evt: Bg2MouseEvent) { console.log(`mouseDown - mouse location: ${evt.x}, ${evt.y}`); }
mouseMove(evt: Bg2MouseEvent) { console.log(`mouseMove - mouse location: ${evt.x}, ${evt.y}`); }
mouseOut(evt: Bg2MouseEvent) { console.log(`mouseOut - mouse location: ${evt.x}, ${evt.y}`); }
mouseDrag(evt: Bg2MouseEvent) { console.log(`mouseDrag - mouse location: ${evt.x}, ${evt.y}`); this.mainLoop.postRedisplay(); }
mouseWheel(evt: Bg2MouseEvent) { console.log(`mouseWheel - mouse location: ${evt.x}, ${evt.y}, delta: ${evt.delta}`); evt.stopPropagation(); }
touchStart(evt: Bg2TouchEvent) { console.log(`touchStart`); }
touchMove(evt: Bg2TouchEvent) { console.log(`touchMove`); }
touchEnd(evt: Bg2TouchEvent) { console.log(`touchEnd`); }}
window.onload = async () => { const canvasElem = document.getElementById('gl-canvas') as HTMLCanvasElement; if (!canvasElem) { console.error("Cannot find canvas element with id 'gl-canvas'"); return; }
const canvas = new Canvas(canvasElem, new WebGLRenderer()); const appController = new MyAppController(); const mainLoop = new MainLoop(canvas, appController); mainLoop.updateMode = FrameUpdate.MANUAL; await mainLoop.run();};