simvx.core.scene_io.loader

Module names

A loaded scene is a real module, and its name is a pure function of its resolved path: the same file always lands on the same :data:sys.modules key and two different files never share one. The readable part of the name is the path relative to the project the scene belongs to (the directory holding simvx.toml) or, failing that, to the scene’s own directory; a digest of the full resolved path follows it, so two projects that both contain levels/forest.py stay apart. The name carries no dots: a dotted key whose parent packages do not exist is one that :mod:pickle, and anything else reaching for a class through __import__, cannot resolve.

What sits beside a file scene

python scene.py can import helpers from beside itself only because the interpreter launcher puts the script’s directory on :data:sys.path, and no import API reproduces that. Scenes are ordinary Python files that must load from anywhere, so the loader supplies the same reach without the same cost. A permanent :data:sys.path entry would make every neighbour of every scene importable under its bare name for the rest of the process, which is how two scenes in two directories end up sharing one helpers module; levels/a.py and levels/b.py each keeping a util.py beside them is the most ordinary project layout there is, and it has to load.

So each scene directory gets a synthetic package of its own, named for the directory the way a scene module is named for its file. While a scene’s own top-level code runs, a bare import helpers resolves to helpers.py in that directory and the module is registered as a submodule of that package: a/util.py and b/util.py are two modules with two names, and a class defined in either carries a __module__ that still resolves. The bare name is bound alongside it for the length of the exec, which is what makes a second import of the same neighbour, and any import helpers.thing, find what the first one loaded; it is unbound afterwards, so nothing outside the scene ever sees it. Two scenes in the same directory share one set of neighbours, as they would under python.

The reach lasts as long as the scene’s own top-level code, which is where a scene states what it needs; a scene that defers import helpers to the inside of a method is asking for it after the scene has run, and should import at module level instead. Only Python sources and packages are found this way, not extension modules.

Folder scenes need none of this: they are packages, their files import each other relatively, and nothing about them goes on :data:sys.path either.

:data:sys.meta_path

The three finders below are installed on the first load, not on import, so a process that never loads a scene has an untouched import system. Once installed they stay: a scene module and its neighbours remain in :data:sys.modules for the life of the process, there is no unload, and a submodule of either, or a

func:

importlib.reload of a scene, still has to resolve afterwards. All three are inert for every name outside the two scene prefixes, so what they cost a host process after that is one dict lookup per import miss.

Freshness

Loading the same unchanged file twice returns the same class, so a repeat load keeps object identity; a load after an edit compiles the new text. Both come from one rule: the compiled result is kept against a digest of the file’s bytes, and a hit means the source has not changed since it was compiled. The digest is of the content itself, not of size and modification time, because those cannot tell a rewrite apart from what it replaced when the write lands on the same byte count and the timestamp does not move, which is one save on any filesystem that keeps whole-second times. A scene is no fresher than the neighbours it imported, so those count towards its signature too, and an edited neighbour is dropped from

data:

sys.modules so that the reload runs it again. For a folder scene the signature covers every .py under the folder.

Runtime loading: import a scene .py file or folder and instantiate its primary Node.

load_scene is the canonical way to take a path on disk and return a live

class:

~simvx.core.Node tree. For editing the source, use

class:

SceneFile / :class:SceneModule; this module is the runtime side.

Module Contents

Functions

load_scene

Load a scene from a .py file or scene-module folder.

import_file

Import path as a module of its own, leaving :data:sys.path alone.

Data

API

simvx.core.scene_io.loader.__all__

[‘import_file’, ‘load_scene’]

simvx.core.scene_io.loader.load_scene(path: str | pathlib.Path) simvx.core.node.Node[source]

Load a scene from a .py file or scene-module folder.

File path → runs the file as a module of its own and instantiates the primary :class:Node subclass. Modules beside the file are importable by bare name while it runs, as they would be under python scene.py. Folder path → runs the folder as a package and instantiates the primary class declared in __init__.py or, as a fallback, in <folder>/<folder>.py.

The primary class is instantiated directly: a scene IS a Node subclass in a .py file, so importing the module is all the “script loading” there is.

Raises: ValueError: If the path holds no usable Node subclass.

simvx.core.scene_io.loader.import_file(path: str | pathlib.Path) types.ModuleType[source]

Import path as a module of its own, leaving :data:sys.path alone.

The name is the same pure function of the path :func:load_scene uses, so a file reached either way is one module, and what sits beside it is importable by bare name while it runs. This is the import a tool wants when it needs the module rather than a node tree, such as watching a file for changes.

A file already loaded is returned as it stands; use :func:importlib.reload on the result to run it again.