Koya/Assets
Virtual filesystem and asset loading for scripts and renderer.
Virtual filesystem for bundled content. Multiple sources can be mounted into a single namespace and accessed uniformly from scripts.
Import #
import * as Assets from 'Koya/Assets';Functions #
- Path is resolved by the internal asset system (e.g.,
/rom/...). - Throws if the asset does not exist.
getText(path: string): Promise<string>- Path is resolved by the internal asset system (e.g.,
/rom/...). - Throws if the asset does not exist.
getBinary(path: string): Promise<number[]>- The lookup is recursive for nested subdirectories.
- Useful for discovering available assets at runtime.
readDir(path: string): Promise<string[]>- The destination defaults to
/rom. - Relative sources use the process working directory; use
mountAdjacentfor application sidecars. - The Promise rejects when the source or virtual destination is invalid.
mount(source: string, destination?: string): Promise<boolean>- The destination defaults to
/rom. - The source must be relative and cannot contain parent traversal.
- Use this during bootstrap for plugin and data packs distributed beside the executable.
mountAdjacent(source: string, destination?: string): Promise<boolean>Virtual filesystem #
- If the running executable has a ZIP appended to it, Koya automatically mounts that application archive at
/rombefore loading the bootstrap. - No system, user, or working-directory assets are mounted implicitly.
--default-mountsopts into the legacy XDG, user configuration, and./assetslocations. -mcan be repeated for explicit startup mounts. Explicit mounts and bootstrap-time mounts override the application archive on collisions.- Once mounted, access assets via the virtual path, e.g.
/rom/index.js,/rom/shaders/vertex.spv,/rom/images/logo.png.
./bin/koya -m ./assets -m ./extra_pack.zip -i apps/demo.js
./bin/koya --default-mounts -i index.jsArchive and executable mounts #
A mount source may be a directory, a ZIP archive, or an executable with an unmodified ZIP appended to it. At startup Koya discovers its own executable and automatically mounts an appended application archive at /rom. A minimal embedded bootstrap can then mount independently distributed plugin or data-pack ZIPs from beside the executable.
# The appended archive contains /rom/index.js
./my-koya-app -- /path/to/application/input- ZIP64 archives, empty files, and long member names are supported.
- Archive paths are normalized into the virtual namespace; absolute paths and traversal members such as
../fileare rejected. - Native hosts can use
Helix::Assets::mountArchiveRangewhen the ZIP occupies a known byte range inside a larger file, including files with data after the archive. - Later mounts override earlier mounts when their virtual paths collide.
import * as Assets from 'Koya/Assets';
// Resolve from the executable directory, independent of the working directory.
await Assets.mountAdjacent('plugins/markdown.zip', '/rom/plugins/markdown');
await Assets.mountAdjacent('plugins/themes.zip', '/rom/plugins/themes');Packaging a Koya application #
Package the application bootstrap and essential assets into one ZIP, then append that ZIP byte-for-byte to a copy of the Koya runtime. The ZIP root becomes /rom, so index.js must be at the archive root rather than inside an extra app/ directory. Keep independently updated plugins as sidecar ZIPs.
source/
├── app/
│ ├── index.js
│ ├── shell/
│ └── images/
└── plugins/
├── markdown/
│ ├── manifest.json
│ └── index.js
└── themes/
├── manifest.json
└── index.jsBuild the Linux distribution #
set -eu
APP_ROOT=/path/to/source
KOYA_RUNTIME=/path/to/koya
DIST=/path/to/dist/MyApp
PACKAGE_TMP=$(mktemp -d)
APP_ZIP=$PACKAGE_TMP/application.zip
mkdir -p "$DIST/plugins"
# Archive contents, not the app directory itself: index.js must be at ZIP root.
(
cd "$APP_ROOT/app"
zip -q -r "$APP_ZIP" .
)
install -m755 "$KOYA_RUNTIME" "$DIST/my-app"
dd if="$APP_ZIP" of="$DIST/my-app" oflag=append conv=notrunc status=none
# Create independently replaceable sidecar packs.
(
cd "$APP_ROOT/plugins/markdown"
zip -q -r "$DIST/plugins/markdown.zip" .
)
(
cd "$APP_ROOT/plugins/themes"
zip -q -r "$DIST/plugins/themes.zip" .
)
rm -rf "$PACKAGE_TMP"Mount sidecar plugins #
// app/index.js inside the executable archive
import * as Assets from 'Koya/Assets';
export default async function main(args)
{
await Assets.mountAdjacent('plugins/markdown.zip', '/rom/plugins/markdown');
await Assets.mountAdjacent('plugins/themes.zip', '/rom/plugins/themes');
// Dynamically import plugin code only after its pack is mounted.
const markdown = await import('/rom/plugins/markdown/index.js');
await markdown.activate?.();
}Ship the resulting directory as a tarball or through a platform package. Native Module/* libraries cannot be loaded directly from ZIP data: place libhx-<name>.so beside the application executable or provide a real directory with -n. The target system must still provide Koya's dynamically linked system libraries unless the chosen distribution format bundles them.
MyApp/
├── my-app # Koya runtime + appended application ZIP
├── libhx-editor.so # optional native module
└── plugins/
├── markdown.zip
└── themes.zip- Create an ordinary ZIP; do not rebase it with
zip -A. - Build the final executable before appending the archive. Any later relink replaces the appended data.
- Run the packaged application from a different working directory to verify that it relies only on self-discovery and
mountAdjacent. - Later plugin mounts override earlier files, so mount base packs before optional patches.
const cfg = await Assets.getText('/rom/config.json');What uses /rom #
All engine- and script-level asset paths point into the mounted namespace:
- Helix/UserInterface —
setTexture(windowId, id, '/rom/...'),setShader(windowId, id, { vert: '/rom/shaders/....spv', frag: '/rom/shaders/....spv' }),setTextFont(windowId, id, '/rom/fonts/....ttf') - Shaders — Use SPIR-V files under
/rom/shaders/*.spvfor custom pipelines. - Images and atlases — Box/panel textures and sprite atlases load from
/rom/...paths. - Bootstrap and modules — The
-ibootstrap file is resolved under/rom, and your JS can freely read other/romfiles.
This keeps scripts portable: swap or add mounts (-m) to switch themes, assets, or shader packs without changing code.
Examples #
Load text and binary assets from the virtual filesystem and discover available files at runtime. This covers the three common patterns: reading structured data (JSON config), loading raw bytes (image/binary), and enumerating a directory to find available resources.
import * as Assets from 'Koya/Assets';
import * as Log from 'Helix/Log';
const shader = await Assets.getText('/rom/shaders/vertex.glsl');
await Log.info('Shader length=' + shader.length);
const configJson = await Assets.getText('/rom/config.json');
const config = JSON.parse(configJson);
await Log.debug('Config keys: ' + Object.keys(config).join(','));
const bytes = await Assets.getBinary('/rom/textures/logo.png');
await Log.debug('Binary size=' + bytes.length);
const files = await Assets.readDir('/rom/shaders');
await Log.debug('Shader files: ' + files.join(', '));Notes #
- Paths that do not exist will throw from the engine side; ensure presence in the asset bundle.