Skip to content

API Reference

Access the assets module:

teal
local assets = require("tecs2d.assets")

Module Functions

assets.InitialLoadComplete

Event type emitted when the initial asset load completes.

teal
assets.InitialLoadComplete: Event

This event is emitted on the first call to update() when there are no loading operations. This ensures that listeners have time to register before the event is emitted. The event is only emitted once per AssetManager instance.

teal
-- Register listener before any update calls
world:observe(0, assets.InitialLoadComplete, function(_e: assets.InitialLoadComplete)
    print("Initial loading complete!")
    -- Transition from loading screen to main menu
end)

-- The event will be emitted on the first update() when nothing is loading

assets.new

Creates a new asset manager with the specified root directory.

teal
function assets.new(root: string): AssetManager

Parameters:

  • root: The root directory for all asset paths

Returns:

  • A new AssetManager instance

Example:

teal
local manager = assets.new("assets")

assets.newAssetHandler

Creates a new asset handler for custom asset types.

teal
function assets.newAssetHandler<T>(): AssetHandler<T>

Returns:

  • A new AssetHandler<T> instance that can be used with load and registerAssetHandler

Example:

teal
local LEVEL_DATA_HANDLER <const>: AssetHandler<LevelData> = assets.newAssetHandler()

-- Register the handler
manager:registerAssetHandler(LEVEL_DATA_HANDLER, function(path: string): assets.Handle<LevelData>
    return manager:loadFile(path):map(parseLevelData)
end)

Types

AssetStats

Statistics returned by AssetManager:getStats().

teal
type AssetStats = {
    completedCount: integer
    runningCount: integer
    currentAssetCount: integer
    pinnedAssetCount: integer
}

Fields:

  • completedCount: Total number of assets that have finished loading since the manager was created
  • runningCount: Number of assets currently being loaded in background threads
  • currentAssetCount: Number of assets currently held in the cache (may be less than completedCount due to garbage collection)
  • pinnedAssetCount: Number of assets explicitly pinned to prevent garbage collection

FontConfig

Configuration for loading TrueType fonts.

teal
type FontConfig = {
    size?: number
    hinting?: HintingMode
    dpiscale?: number
}

Fields:

  • size (optional): Font size in pixels
  • hinting (optional): One of "normal", "light", "mono", "none"
  • dpiscale (optional): DPI scale factor for high-DPI displays

ImageFontConfig

Configuration for loading bitmap/image fonts.

teal
type ImageFontConfig = {
    glyphs?: string
    extraSpacing?: number
}

Fields:

  • glyphs (optional): String containing all glyphs in order as they appear in the image
  • extraSpacing (optional): Additional pixels between characters

AssetManager

The main asset loading and management class.

loadFile

Loads a text file and returns its contents as a string.

teal
function AssetManager:loadFile(path: string): Handle<string>

Parameters:

  • path: Path to the file relative to the root directory

Returns:

  • A Handle<string> for the file contents

Example:

teal
local configHandle = manager:loadFile("config.json")
-- Later...
local configText = configHandle.value  -- Blocks if needed

loadFileData

Loads a file as binary data.

teal
function AssetManager:loadFileData(path: string): Handle<FileData>

Parameters:

  • path: Path to the file relative to the root directory

Returns:

loadImage

Loads an image file.

teal
function AssetManager:loadImage(path: string): Handle<Image>

Parameters:

  • path: Path to the image file relative to the root directory

Returns:

Example:

teal
local playerSprite = manager:loadImage("sprites/player.png")
playerSprite:pin()  -- Keep in memory

loadFont

Loads a TrueType font.

teal
function AssetManager:loadFont(
    path: string,
    config?: FontConfig
): Handle<Font>

Parameters:

  • path: Path to the font file relative to the root directory
  • config: Optional font configuration

Returns:

Example:

teal
local uiFont = manager:loadFont("fonts/ui.ttf", {
    size = 16,
    hinting = "normal"
})

loadImageFont

Loads a bitmap/image font.

teal
function AssetManager:loadImageFont(
    path: string,
    config?: ImageFontConfig
): Handle<Font>

Parameters:

  • path: Path to the image font file relative to the root directory
  • config: Optional image font configuration

Returns:

loadAudio

Loads an audio file.

teal
function AssetManager:loadAudio(
    path: string,
    sourceType: SourceType,
    streamType?: string
): Handle<Source>

Parameters:

  • path: Path to the audio file relative to the root directory
  • sourceType: Either "static" (for sound effects) or "stream" (for music)
  • streamType (optional): Source of the stream data, either "file" or "memory"

Returns:

Example:

teal
local jumpSound = manager:loadAudio("sounds/jump.wav", "static")
local bgMusic = manager:loadAudio("music/theme.ogg", "stream")

loadShader

Loads a GLSL shader file.

teal
function AssetManager:loadShader(path: string): Handle<Shader>

Parameters:

  • path: Path to the shader file relative to the root directory

Returns:

loadVideo

Loads a video file for playback.

teal
function AssetManager:loadVideo(path: string): Handle<Video>

Parameters:

  • path: Path to the video file relative to the root directory

Returns:

Example:

teal
local intro = manager:loadVideo("cutscenes/intro.ogv")

-- Later, when you want to play it
local video = intro.value
video:play()
love.graphics.draw(video, 0, 0)

loadCompressedImage

Loads compressed image data without converting to a regular Image.

teal
function AssetManager:loadCompressedImage(path: string): Handle<CompressedImageData>

Parameters:

  • path: Path to the compressed image file relative to the root directory

Returns:

Example:

teal
local compressedTexture = manager:loadCompressedImage("textures/terrain.dds")

-- Use with shaders or create regular image
local imageData = compressedTexture.value
local image = love.graphics.newImage(imageData)

loadJson

Loads and parses a JSON file asynchronously.

teal
function AssetManager:loadJson(path: string): Handle<any>

Parameters:

  • path: Path to the JSON file relative to the root directory

Returns:

  • A Handle<any> for the parsed JSON data

Example:

teal
local configHandle = manager:loadJson("config.json")

-- Later, when you need the data
local config = configHandle.value as PlayerData
print(config.playerName)

JSON Parsing

This method uses the tecs.json library for parsing, a high-performance JSON parser.

loadSpriteSheet

Loads a sprite sheet from an Aseprite export (image + JSON file). See Sprite Sheets for full documentation on sprite sheet features, material maps, and the builder API.

teal
function AssetManager:loadSpriteSheet(
    path: string,
    options?: SpriteSheetLoadOptions
): Handle<SpriteSheet>

Parameters:

  • path: Path to the image file relative to the root directory (JSON file must have same base name)
  • options: Optional load options

Options:

  • generateNormal: If true, generate a normal map from luminance when no _n.png exists

Returns:

  • A Handle<SpriteSheet> for the loaded sprite sheet with all material maps

Example:

teal
local playerHandle = manager:loadSpriteSheet("sprites/player.png")

-- With auto-generated normal maps (for sync loading only)
local enemyHandle = manager:loadSpriteSheet("sprites/enemy.png", {
    generateNormal = true
})

Automatic Material Maps

This method automatically loads optional material maps alongside the main image:

  • player_n.png - Normal map
  • player_e.png - Emission map
  • player_s.png - Specular map

Missing material maps are silently ignored.

loadStaticSheet

Loads a single-image sprite sheet (no JSON required). See Sprite Sheets for more details.

teal
function AssetManager:loadStaticSheet(
    path: string,
    options?: SpriteSheetLoadOptions
): Handle<SpriteSheet>

Parameters:

  • path: Path to the image file relative to the root directory
  • options: Optional load options (same as loadSpriteSheet)

Returns:

  • A Handle<SpriteSheet> for a single-frame sprite sheet with material maps

Example:

teal
-- Load a static image as a sprite sheet
local backgroundHandle = manager:loadStaticSheet("backgrounds/forest.png")

-- Material maps (_n.png, _e.png, _s.png) are auto-loaded if they exist
local torchHandle = manager:loadStaticSheet("props/torch.png")

loadBMFont

Loads a BMFont from a .fnt or .json file. See Text for full documentation on bitmap font rendering.

teal
function AssetManager:loadBMFont(path: string): Handle<BMFont>

Parameters:

  • path: Path to the font definition file (.fnt or .json format)

Returns:

  • A Handle<BMFont> for the loaded font with its atlas texture

Example:

teal
local fontHandle = manager:loadBMFont("fonts/pixel.fnt")

-- JSON format from msdf-bmfont is also supported
local msdfHandle = manager:loadBMFont("fonts/title.json")

Format Detection

The format is automatically detected by file extension:

  • .fnt - BMFont text format
  • .json - BMFont JSON format (including MSDF output from msdf-bmfont)

The atlas image path is read from the font file and loaded automatically.

loadTiledMap

Loads a Tiled map from a .tmj or .json file. See Tiled Integration for full documentation on tilemap features.

teal
function AssetManager:loadTiledMap(path: string): Handle<TilemapData>

Parameters:

  • path: Path to the Tiled map JSON file

Returns:

  • A Handle<TilemapData> for the loaded map with all tileset images

Example:

teal
local mapHandle = manager:loadTiledMap("maps/level1.tmj")

-- Spawn the tilemap when loaded
mapHandle:observe(function(h)
    if not h.err then
        world:spawn(
            tiled.Tilemap.new({ data = h.value }),
            Transform(0, 0)
        )
    end
end)

Automatic Asset Loading

This method automatically loads:

  • All tileset images referenced in the map
  • External tileset files (.tsj, .tsx)
  • Material maps (_n.png, _e.png) for tilesets if they exist
  • Image layer images

getFont

Returns a previously loaded font handle by path.

teal
function AssetManager:getFont(path: string): Handle<Font>

This method is particularly useful for fonts since they often require configuration (size, hinting, DPI scale) that you don't want to repeat everywhere. Load the font once with your desired settings, then retrieve it by path anywhere else:

teal
-- Initial load with configuration
manager:loadFont("fonts/ui.ttf", {
    size = 16,
    hinting = "normal"
}):pin()

-- Later, anywhere else in your code
local uiFont = manager:getFont("fonts/ui.ttf")
love.graphics.setFont(uiFont.value)

Parameters:

  • path: Path to the font asset (must match the path used in loadFont or loadImageFont)

Returns:

Errors:

  • Throws an error if the font hasn't been loaded yet

Font Management Pattern

Since fonts typically need consistent configuration across your game (size, hinting, etc.), it's common to load all fonts during initialization with their specific settings, then use getFont throughout your code to retrieve them by path alone.

update

Processes completed load operations.

teal
function AssetManager:update(): boolean

Returns:

  • true if operations are still running, false if all complete

Example:

teal
while manager:update() do
    -- Still loading...
end

isLoading

Checks if any operations are currently loading.

teal
function AssetManager:isLoading(): boolean

Returns:

  • true if operations are still running, false if all operations are complete

wait

Blocks until all operations complete or timeout.

teal
function AssetManager:wait(timeout?: number): {string}

Parameters:

  • timeout: Optional maximum time to wait in seconds

Returns:

  • Array of error messages (empty if all operations succeeded)

Example:

teal
local errors = manager:wait(5.0)  -- Wait up to 5 seconds
if #errors > 0 then
    for _, err in ipairs(errors) do
        print("Load error:", err)
    end
end

getStats

Returns detailed loading statistics.

teal
function AssetManager:getStats(): AssetStats

Returns:

An AssetStats table containing:

  • completedCount: Number of completed loading operations
  • runningCount: Number of currently running operations
  • currentAssetCount: Number of assets currently in cache (including weak references)
  • pinnedAssetCount: Number of assets pinned to prevent garbage collection

Example:

teal
local stats = manager:getStats()
print(string.format("Loaded: %d/%d", stats.completedCount,
                    stats.completedCount + stats.runningCount))
print(string.format("Cached: %d (Pinned: %d)",
                    stats.currentAssetCount, stats.pinnedAssetCount))

load

Generic asset loading method using custom handlers.

teal
function AssetManager:load<T>(handler: AssetHandler<T>, path: string): Handle<T>

Parameters:

  • handler: Asset handler that determines the return type and loading logic
  • path: Path to the asset relative to the root directory

Returns:

  • A Handle<T> where T is determined by the handler type

Errors:

  • Throws an error if no loader is registered for the given handler

registerAssetHandler

Registers a custom asset handler for a specific handler type. Custom loaders should compose existing asset loaders using map() to leverage caching and background threading.

teal
function AssetManager:registerAssetHandler<T>(
    handler: AssetHandler<T>,
    loader: function(string): Handle<T>
)

Parameters:

  • handler: The handler object to associate with this loader
  • loader: Function that takes a file path and returns the loaded asset

Example:

teal
-- Create and register a custom CSV loader
local CSV_HANDLER <const> = assets.newAssetHandler<{{string}}>()
manager:registerAssetHandler(CSV_HANDLER, function(path: string): Handle<{{string}}>
    -- Load the contents of the CSV file in the background thread.
    return manager:loadFile(path):map(function(content: string): {{string}}
        -- The CSV is loaded now, so parse and return it.
        return parseCsv(content)
    end)
end)

setRoot

Changes the root directory for all asset paths.

teal
function AssetManager:setRoot(root: string)

Parameters:

  • root: The new root directory

Example:

teal
manager:setRoot("game-assets")

getRoot

Returns the current root directory.

teal
function AssetManager:getRoot(): string

Returns:

  • The current root directory string

resolvePath

Resolves a path relative to the root directory.

teal
function AssetManager:resolvePath(...: string): string

Parameters:

  • ...: Path segments to join with the root directory

Returns:

  • The full resolved path. If no arguments are given, returns the root directory.

Example:

teal
local fullPath = manager:resolvePath("sprites", "player.png")
-- Returns "assets/sprites/player.png" (if root is "assets")

shutdown

Stops all worker threads and cancels pending operations.

teal
function AssetManager:shutdown()

handleAll

Waits for multiple handles to complete, then transforms their values. Fails if any handle fails.

teal
function AssetManager:handleAll<T, U>(handles: {Handle<T>}, fn: function({T}): U): Handle<U>

Parameters:

  • handles: Array of handles to wait for
  • fn: Transform function that receives an array of values and returns U

Returns:

  • A Handle<U> that completes when all input handles succeed and the transform completes. If any handle fails, the handleAll handle fails with that error.

Example:

teal
local imageHandle = manager:loadImage("player.png")
local normalHandle = manager:loadImage("player_n.png")
local specularHandle = manager:loadImage("player_s.png")

local spriteHandle = manager:handleAll(
    {imageHandle, normalHandle, specularHandle},
    function(images: {love.graphics.Image}): Sprite
        return createSprite(images[1], images[2], images[3])
    end
)

For mixed types, use any:

teal
local imageHandle = manager:loadImage("player.png")
local dataHandle = manager:loadJson("player.json")

local spriteHandle = manager:handleAll(
    {imageHandle as assets.Handle<any>, dataHandle as assets.Handle<any>},
    function(results: {any}): Sprite
        local image = results[1] as love.graphics.Image
        local data = results[2] as PlayerData
        return createSprite(image, data)
    end
)

Handle

A handle to an asset being loaded or already loaded.

Properties

  • isComplete: boolean - Whether the operation has completed
  • value: T - The loaded asset (blocks on first access if not ready, errors if failed)
  • err: string - Error message if loading failed, nil otherwise

observe

Registers a callback for when the operation completes.

teal
function Handle:observe(listener: function(Handle<T>))

Parameters:

  • listener: Function called with the handle when complete

Example:

teal
imageHandle:observe(function(h: assets.Handle<love.graphics.Image>)
    if h.err then
        print("Failed:", h.err)
    else
        sprite = h.value
    end
end)

pin

Prevents this handle from being garbage collected.

teal
function Handle:pin()

Example:

teal
-- Keep essential assets in memory
manager:loadImage("player.png"):pin()
manager:loadFont("ui.ttf", { size = 16 }):pin()

map

Transforms this handle's value when it completes successfully, returning a new handle with the transformed result.

Important: The transform function is only called on success. If the original handle fails, the error propagates automatically to the mapped handle without calling the transform.

teal
function Handle:map<U>(transform: function(T): U): Handle<U>

Parameters:

  • transform: Function that takes the successful value and returns a transformed result

Returns:

  • A new Handle<U> with the transformed value

Example:

teal
local parsedHandle = manager
    :loadFile("settings.txt")
    :map(function(text: string): Config return parseConfig(text) end)
    :map(function(config: Config): Config return applyDefaults(config) end)

flatMap

Transforms this handle's value into another handle when it completes successfully, flattening the result. Use this when your transform function needs to load additional assets based on the first result.

Important: The transform function is only called on success. If the original handle fails, the error propagates automatically without calling the transform.

teal
function Handle:flatMap<U>(transform: function(T): Handle<U>): Handle<U>

Parameters:

  • transform: Function that takes the successful value and returns a new handle

Returns:

  • A new Handle<U> that completes when the inner handle completes

Example:

teal
-- Load a manifest, then load the texture it references
local textureHandle = manager
    :loadJson("manifest.json")
    :flatMap(function(manifest: Manifest): Handle<Image>
        return manager:loadImage(manifest.texturePath)
    end)

map vs flatMap

Use map when your transform returns a plain value. Use flatMap when your transform returns another Handle and you want to avoid nested handles (Handle<Handle<T>>).

teal
-- map: T -> U, wraps result in Handle
handle:map(function(x: number): number return x + 1 end)  -- Handle<number>

-- flatMap: T -> Handle<U>, flattens result
handle:flatMap(function(x: Manifest): Handle<Image>
    return manager:loadImage(x.path)
end)  -- Handle<Image>

handle

Handles both success and error cases when this handle completes. Unlike map, which only runs on success, handle is always called with both the value and error (one will be nil).

Use this to recover from errors or transform results with error awareness.

teal
function Handle:handle<U>(fn: function(T, any): U): Handle<U>

Parameters:

  • fn: Function receiving (value, err) that returns a new value. If the handle succeeded, value contains the result and err is nil. If it failed, value is nil and err contains the error message.

Returns:

  • A new Handle<U> with the value returned by fn

Example:

teal
-- Recover from error with a default value
local configHandle = manager:loadJson("config.json")
    :handle(function(config: any, err: any): Config
        if err then
            print("Config not found, using defaults")
            return { volume = 0.8, fullscreen = false }
        end
        return config as Config
    end)

-- Transform with error awareness
local statusHandle = imageHandle:handle(function(image: love.graphics.Image, err: any): {string: any}
    if err then
        return { loaded = false, error = err }
    end
    return { loaded = true, width = image:getWidth() }
end)