Skip to content

files

FreeBodyEngine.core.files #

ANIMATION_FILE = 'ANIMATION_FILE' module-attribute #

ASSET_WRITES_PERMITTED = 'ASSET_WRITES_PERMITTED' module-attribute #

FONT_FILE = 'FONT_FILE' module-attribute #

MATERIAL_FILE = 'LOAD_MATERIAL' module-attribute #

MODEL_FILE = 'MODEL_FILE' module-attribute #

SOUND_FILE = 'SOUND_FILE' module-attribute #

SPRITESHEET_FILE = 'SPRITESHEET_FILE' module-attribute #

SPRITE_FILE = 'LOAD_SPRITE' module-attribute #

TEXTURE_FILE = 'LOAD_TEXTURE' module-attribute #

TEXTURE_STACK_FILE = 'TEXTURE_STACK_FILE' module-attribute #

TOML_FILE = 'TOML_FILE' module-attribute #

AssetPack(data) #

Reads the .pak format written by build/builder.py's bundle_assets(): a MAGIC + version + entry-count header, an entry table (path, absolute data offset, data length), then the raw concatenated data. The whole table is parsed once at construction so any entry is then a direct slice - no per-read scanning.

Parses data (the whole .pak file's bytes) immediately - raises ValueError if it isn't a valid, current-version pak.

read(path) #

Returns the raw bytes stored at path - a direct slice of the already-loaded pack data, since init parsed the entry table up front. Raises KeyError if path isn't in this pack (check with in first).

AssetPackFileSystem(packs=None, asset_dir=None) #

Bases: FileSystem

The release-mode FileSystem: reads bundled .pak files sitting next to the frozen executable (see build/builder.py's run_pyinstaller, which ships dist/assets/*.pak as loose sibling files). user:// paths are the one exception - runtime-writable data (saves, logs) isn't part of any read-only pak, so those still hit the real filesystem, same as DevFileSystem does.

Loads every PACK_NAMES .pak found under asset_dir (default: an assets/ directory next to the frozen executable), unless packs is given directly (mainly for tests). Also loads the shared atlas's UV metadata out of the "data" pack, if present.

ATLAS_IMAGE_KEY = '_ENGINE_atlas.png' class-attribute instance-attribute #

ATLAS_METADATA_KEY = '_ENGINE_atlas.json' class-attribute instance-attribute #

PACK_NAMES = ('data', 'images', 'mesh') class-attribute instance-attribute #

atlas_uv = {} instance-attribute #

packs = packs if packs is not None else self._load_packs(base) instance-attribute #

user_files_path = self.sanitise_path(get_user_file_path()) instance-attribute #

ensure_path(path) #

Returns whether path exists as an entry in any loaded pack.

get_atlas_uv(path) #

Returns the (x, y, w, h) normalized UV rect path was packed into the shared atlas at, or None if it isn't an atlas-packed image (e.g. it's a non-image asset, or this is DevFileSystem/not release mode - other FileSystem classes simply don't define this method, callers should use getattr(fs, 'get_atlas_uv', None)).

get_file(path) #

Resolves path to a FileResource. user:// paths go straight to the real filesystem (same as DevFileSystem, since packs are read-only); anything else is looked up across every loaded pack, falling back to a region of the shared atlas image if path was atlas-packed at build time (see get_atlas_uv) rather than bundled as its own pack entry. Warns and returns None if path isn't found anywhere.

FileResource(id, data, file_path) #

A handle to one loaded file, returned by FileSystem.get_file() - the common, backend-agnostic interface loaders/callers use regardless of whether data is a DevFileStream, an AssetPackStream, or any other FileStream implementation.

Wraps data (a FileStream) as file file_path, tagged with id - stores the backend stream directly rather than copying its contents, so reads/writes below always reflect the stream's current state.

data = data instance-attribute #

file_path = file_path instance-attribute #

id = id instance-attribute #

clear() #

Clears the underlying file's contents (a no-op if the backing FileStream doesn't allow writes).

read(size=-1, offset=0, bytes=False) #

Reads size bytes starting at offset (the whole file by default). Decodes the result to str unless bytes=True is passed - an empty read decodes to "" rather than raising, since an empty byte string is valid UTF-8 but callers generally want a plain empty string back, not to special-case it themselves.

write(data, offset=-1) #

Writes data at offset (appending at the current end of file when offset is -1, the default). A no-op if the backing FileStream doesn't allow writes.

FileStream #

Base class for a FileSystem backend's raw byte-level IO (DevFileStream for a real file on disk, AssetPackStream for a read-only view into a bundled .pak) - every method here is a no-op stub, meant to be overridden; the default (write-nothing, read-nothing) FileStream is itself used as a placeholder for a resource with no real backing data (see AssetPackFileSystem.get_file()'s atlas-region case).

clear() #

Clears all bytes from a file.

read(size=-1, offset=0) #

Reads bytes from the IO stream.

remove(start, end) #

Removes bytes within a range

write(data, offset=0) #

Writes bytes to the IO stream.

FileSystem() #

Bases: Service

Base class for the engine's pluggable file backends (DevFileSystem for loose files on disk, AssetPackFileSystem for bundled release .paks) - registered as the "files" service, so callers go through get_service('files') rather than depending on a concrete subclass.

Registers this instance as the "files" service and sets up the empty resource registry subclasses' get_file() implementations add to via _add().

current_file_id = 0 instance-attribute #

resources = {} instance-attribute #

ensure_path(path) #

Returns whether path exists in this backend.

ensure_trailing_slash(path) #

Ensures a directory path ends with a forward slash. Assumes the path has already been sanitised

generate_file_id() #

Returns a fresh, unique id for a new FileResource - ids are assigned sequentially and never reused within this FileSystem's lifetime.

get_file(path) #

Resolves path (a virtual asset path, e.g. possibly prefixed with user:// or engine://) to a FileResource, or None if it can't be resolved.

sanitise_path(path) #

Converts file paths to use the forward slash standard.

FileWatcher(directory) #

Polls directory for files whose mtime changed since the last check and emits FILE_CHANGE(relative_path) for each one (a project-relative path using "/", matching the same path strings load_file()/get_file() use elsewhere) - dev-mode hot reload's only way of finding out a file changed at all (see core/files/hot_reload.py for what happens with that event, and DevFileSystem for how this gets its update() called).

A plain mtime-polling watcher (checked once every POLL_EVERY_N_FRAMES frames, not an OS-level file-events API like inotify/FSEvents/ ReadDirectoryChangesW) - works identically on every platform with no extra dependency, and a project's asset directory is small enough that a full os.walk() a couple of times a second is cheap. Not itself a registered Service - DevFileSystem owns one directly and drives its update() from its own on_initialize(), since a bare directory-watcher has nothing meaningful to depend on or be depended on by.

New files are recorded silently on the pass they're first seen (there's nothing live yet to reload for a file nobody has loaded before) - only a change to an already-known file's mtime fires the event.

Registers the FILE_CHANGE event and takes an initial silent scan of directory to record every existing file's mtime, so the first real scan (from update()) only reports genuine changes, not every file as "new".

POLL_EVERY_N_FRAMES = 30 class-attribute instance-attribute #

directory = directory instance-attribute #

update() #

Call once per frame; actually rescans the directory only every POLL_EVERY_N_FRAMES calls.

get_file(path) #

Shorthand for get_service('files').get_file(path).

get_file_system() #

Constructs the right FileSystem for how the engine is currently running: an AssetPackFileSystem (reading bundled .paks) unless the DEVMODE flag is set, in which case a DevFileSystem rooted at the current project's asset directory. Returns None on an unsupported platform (anything other than win32/darwin/linux/web/android).

DevFileSystem itself has no platform-specific code at all (see core/files/dev.py's open_file() - a plain builtin open(), which reads from Pyodide's own virtual filesystem transparently once a web dev build's bootstrap has unpacked the project's files into it - see build/builder.py's build_for_dev_web()) - "web" only needed adding to this tuple, not a whole new branch, for fb run --web dev builds to get a real FileSystem instead of silently None. A release build's AssetPackFileSystem path isn't reachable for "web" yet regardless (see Builder.build_for_web() - web release builds aren't implemented at all), so that half of this function is unchanged.

"android" needed adding for the same reason "web" did: Android's own per-app private storage (where build_for_dev_android() copies the project's assets - see its own docstring) is a completely ordinary POSIX filesystem underneath, so DevFileSystem's plain open() calls need nothing platform-specific here either - just a dispatch entry that wasn't there yet, not a new FileSystem implementation.

join_paths(*paths) #

Joins paths with "/" and normalizes the result the way os.path would (resolving . and .. segments, collapsing repeated slashes), but always on "/" regardless of host OS - these are virtual asset paths, not real filesystem paths, so using os.path itself would mangle them on Windows. A leading .. past the start of a relative join is kept (there's nothing to resolve it against yet); the same past the start of an absolute path is dropped instead, since going above / isn't meaningful. Preserves a trailing slash from the last non-empty argument. Empty arguments are ignored; an all-empty input returns ''.

load_file(path, file_type=None) #

Loads path (or, for loaders that accept multiple files at once - e.g. TEXTURE_STACK_FILE's layered images - a tuple of paths) through the registered loaders table in core/files/init.py, and returns whatever that loader produces.

If file_type is given (one of the *_FILE constants), only that loader is tried, and it must both accept every path's extension and (when multiple paths are given) support multiple files - otherwise this returns ''. If file_type is omitted, every registered loader is tried in loaders' insertion order and the first one whose supported extensions match every path's extension (and, again, supports multiple files if more than one path was given) wins - loader order matters when two loaders claim the same extension (see the comment on loaders).

Also returns '' for an empty path tuple, for any path with no extension (or whose last . falls before its last path separator, e.g. a dotted directory name with no extension on the filename itself), or if nothing matches.

path_exists(path) #

Returns whether path (a virtual asset path - see FileSystem. get_file()) resolves to a real file under the active FileSystem. get_file() itself already returns None rather than raising for a path that doesn't resolve, so this is just that check spelled out - added because engine_assets/default_main_file.py (the template every new project starts from via fb init) called a same-purpose path_exsists (note the typo) that had never actually existed here at all, on any platform: a fresh project with no actions.toml (the common case - it's optional input-action config) crashed with AttributeError the instant register_default_services() ran, before ever reaching main.run().