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).
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".
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().