Engine-Side Change Application (Generated C++)
April 20, 2026 ยท View on GitHub
Scope
This page explains the engine-side half of model sync at a design level: how generated C++ receives model messages, routes them through the model tree, and keeps the engine's mirrored state up to date.
Runtime Entry Points
Engine-side sync is handled in the model-sync command handler:
ModelInitRequestdeserializes the full project JSON into a C++Projecttree and runsinitialize(...)to wire parent/self links.ModelUpdateRequestis forwarded to the root model's generatedhandleModelUpdate(...), which applies a targeted in-place change.
ModelInitRequest is used when first loading the project model, and ModelUpdateRequest is used thereafter.
Generated C++ Pieces
For synced models, generation produces a few core pieces:
- data classes that can be serialized to and deserialized from JSON via reflect-cpp
- a model wrapper base (
<Model>Base) that inheritsAnthemModelBase - generated
initialize(...)andhandleModelUpdate(...)methods - optional per-field observer helpers
Collections are represented with AnthemModelVector<T> and AnthemModelUnorderedMap<K, V>, which participate in the same model-tree initialization contract.
Update Application Model
handleModelUpdate(...) is generated as a path-driven dispatcher.
At each step, it:
- reads the current accessor segment (
fieldName, and optionally index/key metadata) - resolves that segment to a concrete field
- either applies a value change at this level or forwards to a child model/collection
- returns early with logs when request shape and field type are incompatible
Type-Level Behavior
The generator uses different strategies by field category:
- scalar-like fields (primitives/enums/value types): leaf-only replacement
- collection fields (list/map): either structural element operations or whole-field replacement
- model-like fields (custom models/unions): leaf replacement or recursive forwarding into the active child
The important detail is not the exact branch code; it is that every update is interpreted as "apply here" vs "forward deeper", based on accessor depth and field category.
Message Contract Assumptions
The generated handler assumes:
fieldAccessesis a root-to-leaf path- each model-level segment identifies a field name
- list/map segments include index/key metadata
updateKindsemantics are consistent with the target (for example, list supports insert/remove semantics, map does not supportaddas a separate operation)
When these assumptions are violated, the update is dropped and logged.
Observability in C++
There are two observer surfaces:
- per-field observers on generated wrappers
- model-level observers on
AnthemModelBaseviaprocessChange(...)
Current behavior is coarse. Direct field assignments are surfaced reliably, while deep collection churn and forwarded descendant changes are not exposed with Dart-equivalent event richness.
Platform Gating and Variants
skipOnWasm affects generated C++ structure:
- model files can be omitted from wasm builds
- union branches for wasm-skipped model types are conditionally removed
This keeps desktop-only model types out of wasm targets without changing Dart-side model authoring.
Extension Pattern for Engine Logic
When engine behavior is needed beyond generated sync mechanics, models can provide:
cppBehaviorClassNamecppBehaviorClassIncludePath
Pattern:
- generated base owns synchronization and structure
- hand-written subclass owns domain behavior/side effects
This separation keeps sync rules centralized while preserving extension points for engine features.
Error Handling Approach
Generated handlers are defensive. Invalid or incomplete updates are rejected with logging (missing access metadata, bad JSON for the target type, invalid update-kind/field combinations, etc.), and handling returns early for that path.
The design preference is safety over partial best-effort mutation.
Source References
codegen/lib/generators/cpp/cpp_model_sync.dartcodegen/lib/generators/cpp/cpp_model_builder.dartcodegen/lib/generators/cpp/get_cpp_type.dartengine/src/modules/command_handlers/model_sync_command_handler.cppengine/src/modules/codegen_helpers/model_base.hengine/src/modules/codegen_helpers/model_vector.hengine/src/modules/codegen_helpers/model_unordered_map.hengine/src/modules/codegen_helpers/observability_helpers.hengine/src/generated/lib/model/project.hengine/src/generated/lib/model/project.cppengine/src/generated/lib/model/processing_graph/node.cpp