Game Loop
March 8, 2026 ยท View on GitHub
A grab-bag of B+ utilities you'll likely find useful.
Game Loop
Handles all the boilerplate of a basic game loop for you. Refer to the doc-string @game_loop, and the file Helpers/game_loop.jl, for detailed info on usage.
The loop handles all built-in B+ services for you: Input, GUI, and BasicGraphics
Within the loop you'll have access to the variable LOOP::GameLoop, which has the following fields which you can read but should not set:
delta_seconds::Float32: the amount of time elapsed since the last loop iteration.frame_idx::Int: the number of elapsed frames so far, plus 1.last_frame_time_ns::UInt64: the most recent timestamp, from the end of the last frame.context::Context: the OpenGL/GLFW contextservice_input: the Input service.service_basic_graphics: the Basic Graphics service.service_gui: the GUI service.
LOOP also has some fields which you can set to configure the loop. You can set them both in the SETUP phase and the LOOP phase.
max_fps::Optional{Int} = 300caps the game's framerate.max_frame_duration::Float32 = 0.1caps thedelta_secondsfield in the case of very slow frames. This prevents significant jumps in the game after a hang.
Basic Graphics
A B+ Context service that provides lots of basic resources: e which defines a bunch of useful GL resources:
screen_triangle: A 1-triangle mesh with 2D positions in NDC-space. When drawn, it will perfectly cover the entire screen, making it easy to spin up post-processing effects. The UV coordinates can be calculated from the XY positions (or from gl_FragCoord).screen_quad: A 2-triangle mesh describing a square, with 2D coordinates in NDC space (-1 to +1) This can be used for post-processing effects, but it's less efficient thanscreen_trianglefor technical reasons.blit: A simple shader to render a 2D texture (e.x. copy a Target to the screen). Refer tosimple_blit().- To make your own blit-like shader for
screen_triangleand/orscreen_quad, just usemake_vertex_shader_blit(), and callcustom_blit()instead ofsimple_blit().
- To make your own blit-like shader for
empty_mesh: A mesh with no vertex data, for dispatching entirely procedural geometry. UsesPrimitiveType.points.
To draw a texture, call simple_blit(tex_or_view; params...). It has the following named parmeters:
quad_transform::fmat3x3transforms the 2D quad.- Defaults to the identity transform.
color_transform::fmat4x4transforms the sampled color into an output color.- Defaults to the identity transform.
output_curve::Float32is an exponent that can be applied to all 4 output channels.disable_depth_test::Boolis a flag for automtically disabling depth tests before drawing the quad, then reinstating the depth test state afterwards.- By default it's true.
manage_tex_view::Boolis a flag for automatically callingview_activate()andview_deactivate()on the blitted texture/view if it's not already activated.- By default it's true.
To make your own blit-like shader that works with screen_triangle and screen_quad,
use make_vertex_shader_blit().
Refer to its doc-string for more details.
Then do the drawing by calling custom_blit(tex_or_view, shader_program),
which is similar to simple_blit() with the following parameters:
assume_full_screen::Boolcan be set to true to optimize from drawing a quad to drawing a screen-triangle.tex_name::Stringcan be set if the name of your shader's texture uniform is not"u_tex".disable_depth_test::Boolandmanage_tex_view::Boolwork the same as insimple_blit().
Cam3D
A representation of a movable, turnable 3D camera.
Cam3D{F}represents the camera state, usingFas the floating-point type (e.x.Float32).Cam3D_Settings{F}represents the camera's config.Cam3D_Inputs{F}represents the camera controls for a particular frame.
To update the camera, do (cam, settings) = cam_update(cam, settings, input, delta_seconds). Note that the settings may change too; this is the case if you support the speed_change input.
cam_basis(cam)andcam_rightward(cam)calculate the camera's facing vectors (the former gets all 3 axes, the latter gets just the rightward axis).- Get the view matrix with
cam_view_mat(cam). - Get the projection matrix with
cam_projection_mat(cam).- The camera's projection settings are either a
PerspectiveProjection{F}or anOrthographicProjection{F}.
- The camera's projection settings are either a
File Cacher
A system for reloading files from disk anytime they are changed. To use this system, do the following:
- Define the type of data that is cached. We will refer to this type as
TCached. For example, a texture cache could useBplusApp.GL.Texture. - Define a function which loads, or re-loads, an asset from disk. We will refer to this as
reload_response::Base.Callable.- The signature should be
(path::AbstractString[, old_data;:TCached]) -> new_data::TCached[, dependent_files].- If your loaded asset depends on other files beyond its own path, add a second return value which is an iterator of those files. Then a reload can be triggered by any of these files changing.
- This function is allowed to throw if the file can't be loaded for any reason.
- The signature should be
- Define a function which acknowledges an error from calling
reload_response. We will refer to this aserror_response::Base.Callable.- The signature should be
(path::AbstractString, exception, trace[, old_data::TCached]) -> [fallback_data::TCached]. - If you want to return a fallback instance (like an "error texture"), you can return it here. Otherwise, return anything other than a
TCached, such asnothing. - You may also want to log an error here, e.x.
@error "Failed to load $path" ex=(exception, trace)
- The signature should be
- Create an instance of the cacher with the above parameters:
FileCacher{TCached}(reload_response=reload_response, error_response=error_response, other_args...).- Pass
relative_path = pto change the relative path for files.- It defaults to
pwd(), a.k.a. the location Julia is running from.
- It defaults to
- Pass
check_interval_ms = a:bto change the randomized time interval for checking each file for changes.- The randomization prevents all cached files from being checked at the same time, which could cause a disk bottleneck.
- Default is
3000:5000, i.e. 3-5 seconds.
- Pass
- Update the cacher with
check_disk_modifications!(cacher)::Bool.- It returns true if any files have been reloaded.
- The naive way to call this is once every frame, but you can probably get away with doing it much less often since files are not reloaded very frequently.
- Get a file with
get_cached_data!(cacher, relative_or_absolute_path)::Optional{TCached}.- If your
error_responseprovides a fallback instance, then this function always returns aTCached.
- If your