Checkpoint 4: harden native paths, authoring workflows and Player lifecycle
This commit is contained in:
@@ -74,9 +74,17 @@ mesh datablocks remain shared. Repair deliberately duplicated material IDs in
|
||||
Blender's Custom Properties. Save the `.blend` again.
|
||||
|
||||
Removing an exported output produces an import conflict. Faset keeps the old
|
||||
generation active. Inspect the diagnostics and update affected references before
|
||||
explicitly importing with `allow_removed_outputs: true`. This accepts removal;
|
||||
it does not automatically remap references. A normal GLB without persistent custom
|
||||
generation active. In **Jobs**, choose **Review removals** to open **Conflicts**.
|
||||
Review the removed stable IDs and their last-good names; **Copy removed IDs** helps
|
||||
locate references. Update affected references, then use **Accept reviewed removal**.
|
||||
If source data or the active generation has changed since review, the acceptance
|
||||
fails safely; use **Reimport / review again** and inspect the new result.
|
||||
|
||||
The same explicit API action uses `allow_removed_outputs: true`, with
|
||||
`expected_generation` and `expected_active_generation` taken from the reviewed
|
||||
job's `result.generation` and `result.previous_generation`. Use the same source and
|
||||
settings. This accepts removal; it does not automatically remap references or delete
|
||||
scene components. A normal GLB without persistent custom
|
||||
IDs uses structural matching, which cannot guarantee identity after rename or
|
||||
restructuring.
|
||||
|
||||
@@ -118,3 +126,7 @@ For an end-to-end example, open `examples/projects/collect-3d`. Its exit arch in
|
||||
a `.blend`, reproducible Blender script and a published bundle. The repository's
|
||||
`tools/verify_blender_roundtrip.py` runs the actual Blender helper and Editor imports
|
||||
to check rename, geometry changes, removal conflicts and failure preservation.
|
||||
Pass `--ui-probe build/linux-debug/faset_blender_editor_probe` to also test two
|
||||
instances in one live editor session: both receive new geometry while preserving
|
||||
placement, tint, opaque gameplay fields and the authoring revision. This additional
|
||||
probe requires Vulkan and produces before/after captures.
|
||||
|
||||
@@ -80,9 +80,12 @@ and package execution tests; that is separate from physical GPU-driver testing.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Unknown component or schema version:** enable/register the missing runtime module
|
||||
and rebuild. The Editor preserves its data as opaque authoring fields, but export
|
||||
requires a matching runtime implementation.
|
||||
**Unknown component:** enable/register the missing runtime module and rebuild. A
|
||||
data-only custom component still needs a schema; it does not need callbacks.
|
||||
|
||||
**Schema version mismatch:** match the linked gameplay schema to the document or
|
||||
apply an explicit data migration, then rebuild. The Editor preserves future or
|
||||
missing component data as opaque fields; export requires a matching schema.
|
||||
|
||||
**Missing asset:** import the original source or Blender manifest again. Preserve its
|
||||
sidecar so the AssetId remains stable. A cache copied from another project is not a
|
||||
|
||||
@@ -23,8 +23,9 @@ resolution, validation activation, fixed ticks and timestep. A bounded run advan
|
||||
one synthetic fixed timestep per frame; it does not reproduce a real-time input
|
||||
session. Keep that distinction when comparing runs.
|
||||
|
||||
Startup starts at `main()` and ends at the first completed frame. OS process loading
|
||||
before `main()` is excluded. Frame wall times exclude writing the final profile and
|
||||
Startup starts at the Player application entry after platform argument normalization
|
||||
and ends at the first completed frame. OS process loading and Windows `wmain` UTF-8
|
||||
argument conversion are excluded. Frame wall times exclude writing the final profile and
|
||||
capture files. Simulation and scene-snapshot times are separate from the renderer
|
||||
call. Renderer CPU wall duration includes GPU waits and readback; it is **not CPU
|
||||
utilization**. GPU timestamps measure the submitted graphics work and can be null
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Editor workspace
|
||||
|
||||
The Editor follows the window's display scale. Text is rasterized at that scale;
|
||||
panel dimensions are saved in logical units so the layout remains usable when
|
||||
moving between displays. A scale change preserves focused text and cancels an
|
||||
unfinished numeric, divider or gizmo drag without committing an edit.
|
||||
|
||||
The Editor edits saved scene data and previews it in a Vulkan viewport. **Play**
|
||||
opens a separate Player process. Gameplay movement and spawned objects stay in that
|
||||
process; stopping it leaves the authoring scene unchanged.
|
||||
|
||||
@@ -67,6 +67,21 @@ Use an x64 Visual Studio Developer shell with the Windows SDK, MSVC runtime libr
|
||||
LLVM `clang-cl`, Ninja, CMake, and the Vulkan SDK available. Then use the
|
||||
`windows-debug` or `windows-release` presets.
|
||||
|
||||
Enable **Win32 long paths** on the Windows development machine before starting the
|
||||
build shell. Faset's executable manifest declares long-path support, and its direct
|
||||
file IO uses wide extended paths; external CMake/Ninja/compiler tools also need a
|
||||
compatible host policy for deeply nested build/cache directories. An administrator
|
||||
can enable the policy once in PowerShell:
|
||||
|
||||
```powershell
|
||||
New-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' `
|
||||
-Name LongPathsEnabled -Value 1 -PropertyType DWORD -Force
|
||||
```
|
||||
|
||||
Open a new build shell afterwards; Windows may require a restart for existing
|
||||
processes. See Microsoft's [long-path requirements](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation).
|
||||
Windows CI enables and records this developer profile explicitly.
|
||||
|
||||
```powershell
|
||||
py tools/fetch_slang.py
|
||||
cmake --preset windows-debug
|
||||
|
||||
@@ -4,7 +4,7 @@ Include `<faset/runtime/Runtime.hpp>` and use namespace `faset::runtime`. This i
|
||||
|
||||
## Register behavior
|
||||
|
||||
`void Runtime::registerBehavior(std::string componentType, Behavior behavior)` registers callbacks before scene loading. An empty or duplicate type, registration during a callback, and registration after entities have loaded are rejected.
|
||||
`void Runtime::registerBehavior(std::string componentType, Behavior behavior)` registers callbacks before scene loading. An empty or duplicate type and registration while entities are present or a callback is running are rejected. An empty or cleared world can register additional types.
|
||||
|
||||
`Behavior::Callback` is `std::function<void(Runtime&, EntityHandle, double)>`. Assign it to any of `onStart`, `fixedUpdate`, `update`, `lateUpdate`, and `onDestroy`. Unassigned members do nothing. `Behavior::onCollision` instead accepts `(Runtime&, EntityHandle, const CollisionEvent&)`.
|
||||
|
||||
@@ -63,6 +63,13 @@ Immediate validation errors throw. Deferred failures are recorded in diagnostics
|
||||
|
||||
`Runtime(RuntimeConfig = {})` constructs the controller. `load(const nlohmann::json&)` validates and prepares a scene, creates its entities, then calls initial callbacks. Invalid scene data leaves the preceding world intact. `clear()` destroys the current entities and invalidates their session handles.
|
||||
|
||||
Direct callers can include `<faset/runtime/schema.hpp>` and call
|
||||
`validate_scene_schemas(scene, gameplaySchema)` before `load`. The Player does this
|
||||
automatically: each custom TypeId/version must match its linked schema. Generic
|
||||
`Runtime` accepts opaque custom data with positive versions, including data-only
|
||||
components; built-ins use version 1. This helper verifies identity/version, without
|
||||
automatic migrations or authoring-style custom-field constraint validation.
|
||||
|
||||
`FrameStats advance(double elapsedSeconds, InputState = {})` advances fixed ticks, frame callbacks, interpolation, and late callbacks. `singleStep(InputState = {})` advances one fixed tick. `setPaused(bool)` clears accumulated time and resets presentation history; `paused()` reports this local state. Do not call load/clear/advance recursively from a callback.
|
||||
|
||||
`RuntimeConfig` defaults to `fixedDelta = 1.0 / 60.0`, `maxCatchUpTicks = 4`, `physicsSubsteps = 4`, and `gravity = {0, -9.81f, 0}`. `FrameStats` reports fixed ticks performed, dropped time, interpolation fraction, and total tick count.
|
||||
|
||||
@@ -42,7 +42,7 @@ Read the callback from top to bottom:
|
||||
4. `transform` gives a pose copy. Multiplying metres per second by seconds gives a displacement in metres.
|
||||
5. `setTransform` publishes the changed non-physical pose.
|
||||
|
||||
The `[](...) { ... }` expression is a C++ lambda: a function stored in `Behavior::update`. Empty brackets mean it captures no local variables. `registerBehavior` takes ownership of the callback object. Register before calling `load`; registration after a world has loaded is rejected.
|
||||
The `[](...) { ... }` expression is a C++ lambda: a function stored in `Behavior::update`. Empty brackets mean it captures no local variables. `registerBehavior` takes ownership of the callback object. Register before calling `load`; registration while entities exist or a callback is running is rejected.
|
||||
|
||||
The `schema()` function describes editable configuration. It does not create a runtime object. `tutorial.move_x` is the stable `TypeId`; `speed` is a stable `FieldId` within that type. Keep these IDs when changing a display label. Changing a field's meaning or units needs an explicit data migration, not just a new label.
|
||||
|
||||
@@ -56,7 +56,10 @@ The example scene is a complete, loadable document:
|
||||
|
||||
The sprite is visible because it has `faset.sprite`. It moves because it also has `tutorial.move_x`. The configuration field is `speed`; the type string must match the registration exactly. `rotation` uses radians, and the default coordinate system is Y-up.
|
||||
|
||||
After your project's schema is exported and loaded by the Editor, the type can be described through the same authoring schema used by the Inspector. The direct Player command above is useful before building an Editor workflow around your component.
|
||||
After **Build C++** succeeds in the Editor, choose **Add component** in the Inspector
|
||||
and select the registered type. Its schema supplies editable fields, defaults and
|
||||
constraints. The direct Player command above is useful for testing the same behavior
|
||||
independently of an editor session.
|
||||
|
||||
## Make a change and verify it
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ A gameplay directory contains `Gameplay.hpp` and `Gameplay.cpp`. It provides two
|
||||
|
||||
The Player calls registration before loading a scene. The separate SchemaExporter calls `schema()` without creating a game world or running gameplay callbacks. The Editor reads the resulting declaration; it does not load the gameplay binary into the Editor process.
|
||||
|
||||
Every custom component in a played scene must have a matching TypeId and exact
|
||||
positive version in the linked gameplay `schema()`. The Player checks this before
|
||||
loading the world, including `--validate`. A data-only custom component still needs
|
||||
a schema, even when it registers no callbacks. Built-in TypeIds are reserved. The
|
||||
Editor preserves missing or future component data for recovery, but the Player
|
||||
rejects it until the corresponding module/schema is available or the data is migrated.
|
||||
|
||||
A scene object receives a behavior by containing a component whose `type` matches the string passed to `registerBehavior`. Registering a behavior does not attach it to every object. A component can contain data without having any callbacks.
|
||||
|
||||
## Data, poses, and state
|
||||
|
||||
@@ -10,6 +10,12 @@ Each ordinary callback receives `(Runtime& game, EntityHandle self, double delta
|
||||
|
||||
`onDestroy` runs while that object's handle and allowed component data are still valid. Clean up subscriptions or external C++ state there. After removal, `valid(oldHandle)` returns false. Removing a behavior component also runs that component's `onDestroy`. Clearing or replacing a scene runs destruction callbacks; a new scene uses a new session identity.
|
||||
|
||||
Normal Player shutdown and the Editor's **Stop** request also run destruction callbacks.
|
||||
Stop gives the Player a bounded grace period to finish. An unresponsive native callback
|
||||
can force the Editor to terminate the process; no application can guarantee cleanup
|
||||
callbacks after forced termination or a crash. Such fallback is reported in Console.
|
||||
Exceptions caught from callbacks, including shutdown callbacks, appear in Player logs.
|
||||
|
||||
Registering a callback does not make captured pointers safe. A lambda that stores a reference to a stack variable in `registerGameplay` will outlive that variable. The [timed-despawn example](examples.md#spawn-and-destroy-on-safe-boundaries) uses shared ownership for captured state and removes each object's entry on destruction.
|
||||
|
||||
## One fixed tick
|
||||
@@ -58,4 +64,4 @@ The Player reads this optional object from the scene document. The settings are
|
||||
}
|
||||
```
|
||||
|
||||
This is an excerpt, not a complete scene. The tutorial scenes contain complete examples. `fixed_delta` is seconds, gravity is metres per second squared, and substeps are solver subdivisions inside one fixed tick. These do not create additional gameplay callbacks. Invalid configuration is rejected before the Player starts. Editing these JSON settings is implemented; an Editor settings panel should only be relied on where the current UI exposes it.
|
||||
This is an excerpt, not a complete scene. The tutorial scenes contain complete examples. `fixed_delta` is seconds, gravity is metres per second squared, and substeps are solver subdivisions inside one fixed tick. These do not create additional gameplay callbacks. Invalid configuration is rejected before the Player starts. The Editor's **Simulation** dialog edits these scene settings through normal Undo transactions; **Project settings** edits the project's name, dimension and start scene separately.
|
||||
|
||||
Reference in New Issue
Block a user