Tecs Assets
Tecs Assets is a non-blocking asset loading and management system. It loads game assets like images, sounds, fonts, and shaders in background threads without freezing gameplay.
- Non-blocking threaded loading: Uses worker threads to load assets without blocking the main thread
- Automatic memory management: Uses weak references for cached assets, allowing automatic garbage collection
- Handle-based API: Returns promise-like handles that can block on first access or be checked for completion
Quickstart
You can access the asset manager from any system:
local tecs = require("tecs")
local assets = require("tecs2d.assets")
world:addSystem({
phase = tecs.phases.Startup,
run = function()
-- Get the asset manager instance
local manager = world.resources[assets]
-- Load assets (returns immediately with a handle)
local playerImage = manager:loadImage("sprites/player.png")
local jumpSound = manager:loadAudio("sounds/jump.wav", "static")
local mainFont = manager:loadFont("fonts/main.ttf", { size = 16 })
-- Check if a handle is complete
if playerImage.isComplete then
-- Access the value (will error if load failed)
love.graphics.draw(playerImage.value, 100, 100)
end
end,
})The game plugin automatically creates an asset manager with a root assets folder of "./assets". Use setRoot to change the asset root directory:
world.resources[assets]:setRoot("game-assets")Handles
All load methods return a Handle<T> immediately. Handles are smart references to assets being loaded.
local handle = manager:loadImage("player.png")
-- You can check if the handle has an error
if handle.err then
print("Failed to load:", handle.err)
end
-- You can check if the handle is done
if handle.isComplete then
local image = handle.value
endYou can access the handle value directly at any time, blocking until it's fully loaded:
local image = handle.value -- blocks!You can react when the handle is done loaded or has an error:
-- Listen for completion
handle:observe(function(h: assets.Handle<T>)
if h.err then
print("Error:", h.err)
else
print("Loaded successfully")
playerSprite = h.value
end
end)You can transform handles into other types using map():
-- Transform an image handle into a particle system
local particles = manager
:loadImage("particle.png")
:map(function(image: love.graphics.Image): love.graphics.ParticleSystem
return love.graphics.newParticleSystem(image)
end)Handle Properties
| Name | Type | Description |
|---|---|---|
isComplete | boolean | true if the operation has completed |
value | T | The loaded data. Blocks on first access, errors if failed |
err | string | Error message if loading failed, nil otherwise |
Blocking Behavior and Errors
When you access handle.value for the first time:
- If the asset is already loaded, it returns immediately
- If still loading, it blocks until complete
- Once loaded, the value is cached on the handle for instant future access
- If loading failed, accessing
valuethrows an error
Memory Management
Handles are cached in storage using a weak reference, allowing automatic garbage collection of unused resources.
-- Assets are cached automatically
local handle1 = manager:loadImage("player.png")
local handle2 = manager:loadImage("player.png") -- Returns same handle
-- When all references are released, the asset can be garbage collected
handle1 = nil
handle2 = nil
collectgarbage() -- Asset may be collected if no other references exist
-- Next load will create a new handle
local handle3 = manager:loadImage("player.png") -- New load operationUse the pin() method of a Handle to preload assets or to ensure it's never garbage collected:
-- This asset won't be garbage collected
manager:loadImage("player.png"):pin()Supported Asset Types
The asset manager provides built-in loaders for common asset types:
| Method | Returns | Description |
|---|---|---|
loadImage | Image | PNG, JPG, and other image formats |
loadSpriteSheet | SpriteSheet | Sprite sheets with animations |
loadStaticSheet | SpriteSheet | Single images as sprite sheets |
loadBMFont | BMFont | Bitmap font atlases (.fnt, .json) |
loadTiledMap | TilemapData | Tiled map editor exports |
loadFont | Font | TrueType fonts (.ttf, .otf) |
loadImageFont | Font | Image-based fonts |
loadAudio | Source | Audio files (WAV, OGG, MP3) |
loadShader | Shader | GLSL shader files |
loadVideo | Video | Video files (OGV) |
loadJson | any | JSON data files |
loadFile | string | Raw text file contents |
loadCompressedImage | CompressedImageData | Compressed image data (DDS, KTX, etc.) |
loadFileData | FileData | Raw binary file contents |
Game-Specific Assets
For sprite sheets, BMFonts, and Tiled maps, the loaders automatically load all required files concurrently:
- Sprite Sheets: Loads the image, JSON metadata, and optional material maps (
_n.png,_e.png,_s.png) - BMFonts: Loads the font definition and its atlas texture
- Tiled Maps: Loads the map, all tileset images, external tilesets, material maps, and image layers
Custom Asset Types
For game-specific asset types, use registerAssetHandler to create custom loaders that integrate with the caching and threading system.
Preloading assets
To preload assets during startup, load and pin them in a Startup system:
local tecs = require("tecs")
local assets = require("tecs2d.assets")
world:addSystem({
phase = tecs.phases.Startup,
run = function()
local manager = world.resources[assets]
-- Use :pin() to ensure assets stay in memory
manager:loadImage("player.png"):pin()
manager:loadAudio("music.ogg", "stream"):pin()
manager:loadFont("ui.ttf", { size = 16 }):pin()
end,
})InitialLoadComplete
The InitialLoadComplete event is emitted on the first call to update() when there are no pending load operations. This means all assets queued during startup will have finished loading before the event fires. The event is only emitted once per AssetManager instance and is emitted even if nothing was ever loaded.
world:observe(0, assets.InitialLoadComplete, function()
print("All startup assets are ready!")
end)This is useful for implementing loading screens or deferring gameplay until assets are available.
Loading Screens
Asynchronous asset loading can be used to implement animated loading screens with progress displays.
See working example
See the Loading Screen Example for a complete working example on GitHub.