Repository navigation
Description
Follow-up to #380 / #450. The fourth slice (#450) generates SceneSerializer.cpp serialize/deserialize blocks for the all-trivial components (every member a primitive / glm::vec* / std::string). The harder cases were deliberately left hand-written and are tracked here.
Shipped
- First slice —
AssetHandle/UUIDfields (feat(codegen): SceneSerializer blocks for AssetHandle/UUID-field components (#451) #467): the codegen now classifiesAssetHandle/UUID(au64wrapper) as a trivial-ish field type and round-trips it (static_cast<u64>on write,.as<u64>+ the implicitUUID(u64)ctor on read — format-identical to the hand-written handle blocks). A component whose only non-primitive fields are asset handles / entity-reference UUIDs is now fully generated;UIWorldAnchorComponentmigrated off its hand-written block. Four components that newly classify as trivial are kept hand-written viakComponentsCustomSerialize:DialogueComponent/IKTargetComponent/PerceptionComponent(Sanitize/clamp deserialize the round-trip would drop) andIDComponent(entity identity — serialized as the top-levelEntity:line; auto-gen would emit a bogus sub-map + double-AddComponenton load, and neither coverage test catches that, so it's a hard exclusion). - Second slice —
enum/enum classfields (feat(codegen): SceneSerializer enum-field support (#451 slice) #477): the classifier now recognises an enum field by matching the member type's leaf name against a scan of everyenum/enum classdefinition underOloEngine/src(CollectEnumTypesinmain.cpp) and round-trips it as anint(static_cast<int>on write,.as<int>on read, cast back to the member's own type viastatic_cast<decltype(comp.member)>— so a nested enum likeAnimationStateComponent::Stateneeds no qualified spelling). Regenerating flipped several components to all-trivial that then neededkComponentsCustomSerializeexclusions for omitted runtime fields / clamps / non-m_keys:Rigidbody3DComponent(enum keyedBodyType, runtimem_RuntimeBodyToken+ omitted fields),StreamingVolumeComponent(runtimeIsLoaded+ radius clamps),FogVolumeComponent(Shape/Priority clamps),UIButtonComponent(runtimem_State),UISliderComponent(runtimem_IsDragging). - Fourth slice — per-field
OLO_SERIALIZE(Skip)(codegen: OLO_SERIALIZE(Skip) field annotation for SceneSerializer (#451) #500): a new marker macro (Scene/ComponentReflection.h, alongsideOLO_PROPERTY) that drops a member from the generated (de)serialize and stops it marking the component non-trivial — so an otherwise-all-trivial component whose only obstacle is a runtime-only field is now fully generated (the field reloads at its ctor default) instead of a whole-componentkComponentsCustomSerializeexclusion. The field parser peels a leadingOLO_SERIALIZE(...)(Skip/Skip = truedrops the field;Skip = falseor a futureClamp/Min/Maxpeels the marker and serializes normally); it composes withOLO_PROPERTYon the same field (e.g. a runtime field still script-exposed), and theOLO_PROPERTYscanner skips anOLO_SERIALIZE(line so order doesn't matter. MigratedUIButtonComponent(m_State) andUISliderComponent(m_IsDragging) offkComponentsCustomSerializewith byte-identical on-disk format. This is the "field-level annotation → per-field control" direction below (theSkiphalf;Clamp/Min/Maxremain). Motivation: thevector<struct>/ nested-struct classes have no clean beneficiary today — every candidate must stay hand-written for an independent reason (private membersColliderMaterial, deserialize sanitizeNavMeshBoundsComponent, runtime-onlyDialogueStateComponent, flattened formatLODGroup) — so widening the classifier there would emit zero live blocks; the annotation direction does have beneficiaries. - Third slice — glm-math / small-int /
std::vector<primitive>fields (feat(codegen): SceneSerializer glm-math / small-int / std::vector<primitive> support (#451 slice) #482): the classifier now also handlesglm::quat/mat3/mat4/ivec2/ivec3/ivec4(via newEncode/Decode+Emitter<<helpers inCore/YAMLConverters.h; float components finiteness-validated), the small intsu8/u16/i8/i16(widened tou32/i32on emit — yaml-cpp'sconvert<unsigned char>::encodewrites a raw char, not a number — and read via.as<decltype(member)>), andstd::vector<E>whereEis any trivial type (round-tripped as a YAML sequence). The field parser now also tracks access level so a non-public member marks the component non-trivial (fail-safe: stays hand-written, no compile error / silent drop — e.g.TransformComponent's privateRotation/euler-sync now needs no exclusion). Migrated off hand-written blocks:InstancePortalComponent(u8),QuestGiverComponent(vector<string>),RelationshipComponent(UUID +vector<UUID>). Three components newly flipped to all-trivial but kept hand-written viakComponentsCustomSerialize:LightProbeVolumeComponent(runtimem_Dirty/m_ShowDebugProbes+ clamps),NavAgentComponent(runtime path/target state),PhysicsJoint3DComponent(runtimem_RuntimeConstraintToken+ dozens of clamps). - Fifth slice —
Ref<T>asset-handle fields (this PR): the classifier now recognises aRef<T>member as serializer-trivial whenTtransitively derives fromAsset(CollectAssetTypesinmain.cpp, walkingclass X : public Yinheritance chains — one hop of indirection matters, sinceMaterial/Model/EnvironmentMapderive from the intermediateRendererResource : public Assetrather thanAssetdirectly). Round-trips as a"<Key>Handle"u64written only when the Ref is non-null and actually asset-manager-registered (GetHandle() != 0), resolved back viaAssetManager::GetAsset<T>on read — format-identical to the hand-writtenMeshComponent::m_MeshSource->"MeshSourceHandle"idiom. Regenerating flipped 16 components to all-trivial; every one stayed hand-written viakComponentsCustomSerialize, for one of three reasons (see the exclusion set's comment inmain.cppfor the full per-component breakdown): on-disk FORMAT INCOMPATIBILITY (DecalComponent,SpriteRendererComponent,TextComponent,UIImageComponent,UIPanelComponent,UITextComponent,UIInputFieldComponent,UIDropdownComponentpersist textures/fonts by file PATH, not handle), DROPPED CUSTOM LOAD LOGIC (ModelComponentReload(),MeshComponentprimitive-range validation + mesh-from-primitive fallback,SubmeshComponent'sm_Meshreconstructed from its parent at runtime,ProceduralSkyComponent/StarNestSkyComponent/ReflectionProbeComponentpositivity/range guards), or RUNTIME-ONLY FIELDS auto-gen would newly persist (CinematicComponent's playback state,ReflectionProbeComponent's baked cubemap). One component migrated cleanly:EnvironmentMapComponent(itsRef<EnvironmentMap>cache is always a no-op — never asset-manager-registered — so the round-trip is behaviorally identical to the hand-written block; bonus fix:m_EnvironmentMapAsset, anAssetHandleSaveGame already persists, was being silently dropped from scene YAML and is now round-tripped too).
Out of scope of #450 (still hand-written in SceneSerializer.cpp)
A component stays hand-written today if it has any non-trivial field, or is in kComponentsCustomSerialize. The field types / patterns the generator does not yet handle:
✅ Done in feat(codegen): SceneSerializer blocks for AssetHandle/UUID-field components (#451) #467 (see Shipped above).AssetHandle/UUIDfields — serialized asstatic_cast<u64>(...), deserialized with re-resolution. Pervasive (materials, meshes, colliders, dialogue, streaming, …).✅ Done (see Shipped above).Ref<T>runtime handles (textures, meshes, fonts, environment maps, players) — usually paired with anAssetHandlethat is the persisted form; theRefis rebuilt on load.enums — currently✅ Done in feat(codegen): SceneSerializer enum-field support (#451 slice) #477 (see Shipped above).static_cast<Enum>(node.as<int>(...)); the generator classifies any enum field as non-trivial. (Needs an enum-detection or annotation mechanism.)std::vectorof a non-trivial element —std::vector<primitive>done in feat(codegen): SceneSerializer glm-math / small-int / std::vector<primitive> support (#451 slice) #482 (scalars / small-ints / glm-math / strings / handles / enums);std::vector<trivialstruct>done in the nested-struct slice. What remains is astd::vectorof a non-trivial struct.✅ Done — unordered_map/set slice. Astd::unordered_map/std::unordered_setstd::unordered_set<E>is trivial whenEis a sortable trivial scalar (bool/int/u32/u64/small-int/AssetHandle/std::string/enum — notfloator glm types, which have no meaningfuloperator<); astd::unordered_map<std::string, V>is trivial for anyVthe vector path accepts. Both round-trip through a sorted temporary, because unordered iteration order is implementation-defined and would otherwise break the serialize→deserialize→serialize identical-output guarantee. MigratedPrefabComponentfully off hand-written code.nested structs✅ Done — nested-struct slice. A leaf-name→body registry of every struct (CollectStructBodies) feeds a recursiveClassifyStruct/ParseComponentFieldspair (with avisitedcycle-guard): an all-trivial nested struct emits a sub-map, astd::vectorof one emits a sequence of sub-maps, nesting to any depth. (✅ done in feat(codegen): SceneSerializer glm-math / small-int / std::vector<primitive> support (#451 slice) #482.) The structs originally listed here stay hand-written for other, independent reasons —glm::quat/mat/ivecSceneCamerais setter-only,ColliderMaterialhas private members (a non-public member now correctly marks a component non-trivial),LODGroup's hand-written form flattens to top-level keys.
Still genuinely unhandled by the classifier: std::array, a non-std::string-keyed std::unordered_map, a std::unordered_set of a non-sortable element, a std::vector of a non-trivial struct, Ref<T> of a non-Asset-derived type.
trivial-but-custom components in✅ Done — vec3-Clamp slice (this PR):kComponentsCustomSerialize—BuoyancyComponent,NoiseAnimationComponent(vec3 range clamps)OLO_SERIALIZE(Clamp, Min=…, Max=…)now also acceptsglm::vec3fields (clamped per-component viaglm::clamp/glm::max/glm::min, mirroring the hand-writtenSanitizeVec3Clampedidiom), and both components migrated fully offkComponentsCustomSerialize— see the code comment onkComponentsCustomSerializeintools/OloHeaderTool/main.cppfor detail. Other trivial-but-custom components remain (range clamps /Sanitize*/ helper delegation), e.g.SphereAreaLightComponent,VehicleComponent(runtime field omitted),ScriptComponent(external C# field map),TagComponent/PhaseComponent/UIResolvedRectComponent(identity / runtime-only), plus the four added by the feat(codegen): SceneSerializer blocks for AssetHandle/UUID-field components (#451) #467 slice —DialogueComponent/IKTargetComponent/PerceptionComponent(Sanitize/clamp) andIDComponent(entity identity), plus the sixteen added by theRef<T>slice (see Shipped above — format-incompatible path-based asset resolution, dropped custom load logic, or runtime-only fields). Each would need an opt-in for codegen with custom validation/skip hooks.
Possible directions
- Field-level annotation (
OLO_SERIALIZE(Skip=..., Clamp=..., Min=..., Max=...)) so the generator can emit validation and skip runtime fields — turningkComponentsCustomSerializefrom "all-or-nothing per component" into "per-field control".Skipdone in codegen: OLO_SERIALIZE(Skip) field annotation for SceneSerializer (#451) #500; scalarClamp/Min/Maxdone in Add clamping functionality to component serialization and deserializa… #536;vec3✅ done — this PR (see Shipped above) — that unblockedClampBuoyancyComponent/NoiseAnimationComponent. Remaining clamp-excluded components need a cross-field invariant beyond a per-field range clamp (LightProbeVolumeComponent,NavMeshBoundsComponent,StreamingVolumeComponent,PhysicsJoint3DComponent). Also a candidate for migrating some of theRef<T>-slice's runtime-only exclusions (CinematicComponent,ReflectionProbeComponent) offkComponentsCustomSerializeviaOLO_SERIALIZE(Skip)on their runtime fields. Enum detection (scan✅ Done in feat(codegen): SceneSerializer enum-field support (#451 slice) #477 (enum classdefinitions) or an explicit enum-field marker.CollectEnumTypesleaf-name scan).A registry of✅Encode/Decodehelpers for known nested structs /Ref<T>+handle pairs, keyed by type name.Ref<T>half done (see Shipped above); nested-struct helpers remain (see "nested structs" above).- A follow-up slice could migrate the 8 format-incompatible
Ref<T>-slice exclusions (DecalComponent,SpriteRendererComponent,TextComponent,UIImageComponent,UIPanelComponent,UITextComponent,UIInputFieldComponent,UIDropdownComponent) offkComponentsCustomSerializeby first migrating their on-disk format from a"*Path"string key to a handle-based key — a deliberate, separate on-disk-format-change slice, not a codegen-only one.
Also tracked separately
SaveGame SaveGameComponentSerializer::Serialize() / RegisterAll() body codegen (the lists are already generated by #405; the per-component bodies are still hand-written) — noted in #380.
Audit note (2026-07-27). The two bullets above were stale: the
std::unordered_map/std::unordered_setand nested-struct slices had both shipped while this
list still described them as remaining (verified againsttools/OloHeaderTool/main.cpp—
CollectStructBodies,ClassifyStruct, and the map/set parse are all present). Corrected above.This issue is not exhausted.
kComponentsCustomSerializestill holds 39 components, and
concrete slices remain — smallest first:
CinematicComponent/ReflectionProbeComponent→ migrate off the set with
OLO_SERIALIZE(Skip)on their runtime-only fields (mechanical).- The 8 format-incompatible
Ref<T>exclusions (DecalComponent,SpriteRendererComponent,
TextComponent,UIImageComponent,UIPanelComponent,UITextComponent,
UIInputFieldComponent,UIDropdownComponent) → needs their on-disk format migrated from a
"*Path"string key to a handle key; a deliberate format-change slice, not codegen-only.LightProbeVolumeComponent/NavMeshBoundsComponent/StreamingVolumeComponent/
PhysicsJoint3DComponent→ need a cross-field invariant (Min≤Max, hysteresis) that no
per-field annotation can express.Note
SphereAreaLightComponent/ProceduralSkyComponentmust stay hand-written regardless:
they reject an out-of-range value and keep the constructor default, which is a different
semantic fromClamp's clamp-to-bound — generating one for the other would silently change what
a corrupted save does to the field.
Score
capability: 1
craft: 1
stability: 2
decay: 1
effort: 3
confidence: 0.5
learning: 3
fun: 1
kano: table-stakes
blocked_by: []
blocks: []
Rated per issue-scoring · score = confidence × (capability + craft + stability + decay) / effort, derived by the picker.
Activity
coderabbitai commentedon Jun 28, 2026
🔗 Related PRs
#299 - Feature/reflective skybox [merged]
#405 - Codegen: generate the SaveGame component capture/restore lists (
#380) [merged]#467 - feat(codegen): SceneSerializer blocks for AssetHandle/UUID-field components (
#451) [merged]#477 - feat(codegen): SceneSerializer enum-field support (
#451slice) [merged]#482 - feat(codegen): SceneSerializer glm-math / small-int / std::vector support (
#451slice) [merged]#511 - feat(codegen): nested-struct + vector SceneSerializer codegen (
#451) [merged]#569 - feat(codegen): SceneSerializer unordered_set/unordered_map field support (
#451slice) [merged]#589 - perf(scene): full-coverage binary sidecar cache for scene loads (
#525) [merged]🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
drsnuggles8 commentedon Jun 29, 2026
First slice shipped in #467: the scene-serializer codegen now recognizes
AssetHandle/UUIDand round-trips it as au64.UIWorldAnchorComponentis fully generated off its old hand-written block;DialogueComponent/IKTargetComponent/PerceptionComponent(Sanitize/clamp) andIDComponent(entity identity — the coverage tests can't catch its mis-generation) are kept hand-written viakComponentsCustomSerialize. Remaining field types (enums, vector/map/set, nested structs,Ref<T>,glm::quat/mat/ivec) tracked above as follow-up slices.Merge pull request #467 from drsnuggles8/feature/codegen-serializer-a…
drsnuggles8 commentedon Jun 29, 2026
Advanced (not closed) by #467 (merged 2026-06-29); worktree removed. First slice landed: OloHeaderTool's SceneSerializer codegen now treats AssetHandle/UUID (u64 wrappers) as trivial-ish, so handle/UUID-only components auto-generate their serialize/deserialize blocks (UIWorldAnchorComponent now fully generated; IDComponent + the Sanitize-clamp components kept hand-written via kComponentsCustomSerialize). Remaining epic scope still open: enums, std::vector/map/set, nested structs, Ref, glm::quat/mat/ivec. Leaving open.
drsnuggles8 commentedon Jun 30, 2026
Enum-field slice shipped in #477 (follows the AssetHandle/UUID first slice in #467).
The scene-serializer codegen now recognizes
enum/enum classmembers, so an otherwise-all-trivial component with enum fields round-trips through scene YAML with zero hand-written serializer code.How (
tools/OloHeaderTool/main.cpp):CollectEnumTypes()scans everyenum/enum classdefinition underOloEngine/src;ParseComponentFieldsclassifies a memberPropType::EnumwhenSceneSerTypereturnsUnknownand the member type's leaf name is a known enum (handles unqualifiedBodyType3Dand nestedComponent::Statealike).static_cast<int>(comp.member). Deserialize:static_cast<decltype(comp.member)>(node["K"].as<int>(static_cast<int>(comp.member))). Thedecltypecast-back (vs naming the enum) keeps it robust for nested enums not in scope by their unqualified name at the SceneSerializer level; a missing key keeps the ctor default.Migrated to generated code (hand-written blocks removed, keys/fields matched):
UICanvasComponent,UIProgressBarComponent,UIScrollViewComponent,UIGridLayoutComponent,NetworkIdentityComponent,NetworkLODComponent.Flipped but kept hand-written (added to
kComponentsCustomSerialize, documented inline) because the plain round-trip would lose/change data:Rigidbody3DComponent— enum keyed"BodyType"not"Type", runtimem_RuntimeBodyToken, several deliberately-omitted fieldsStreamingVolumeComponent— runtimeIsLoaded+ radius clampsFogVolumeComponent— Shape/Priority clamps +Sanitize*UIButtonComponent— runtimem_StateUISliderComponent— runtimem_IsDraggingTests: extended
ComponentRoundTrip.UICanvasComponentSurvivesYAMLRoundTripto cover its two enums;ComponentSerializerCoverageTest(existence + disjointness) validates the rest. Verified 72 + 86 + 483 tests green.Leaving open — remaining field-type slices still hand-written:
std::vector/map/set, nested structs,Ref<T>,glm::quat/mat/ivec.Merge pull request #477 from drsnuggles8/feature/codegen-serializer-e…
drsnuggles8 commentedon Jun 30, 2026
Enum / enum class SceneSerializer codegen slice delivered by #477 (merged 2026-06-30); worktree removed. Remaining complex-field slices still open:
std::vector/map/set, nested structs,Ref<T>,glm::quat/mat/ivec. Keeping this umbrella issue open.drsnuggles8 commentedon Jun 30, 2026
Updated the description: the enum-field slice shipped in #477 (merged 2026-06-30) — moved it from "Out of scope" to a "Shipped" entry (struck the stale bullet + the now-done "Enum detection" direction). Remaining open slices:
Ref<T>,std::vector/maps, nested structs (glm::quat/mat/ivec, etc.) — the latter is being picked up next on branchfeature/codegen-serializer-glm-fields.drsnuggles8 commentedon Jun 30, 2026
Third slice shipped in #482 — glm-math (
quat/mat3/mat4/ivec2-4), small ints (u8/u16/i8/i16), andstd::vector<primitive>, plus an access-level fix in the field parser (non-public members now fail safe to non-trivial). MigratedInstancePortalComponent/QuestGiverComponent/RelationshipComponent; keptLightProbeVolumeComponent/NavAgentComponent/PhysicsJoint3DComponenthand-written (runtime fields / clamps).Leaving this open for the remaining categories —
Ref<T>(rebuild-from-handle), nested structs /std::vector<struct>(recursive serialization), andstd::unordered_map/set— which need the Encode/Decode registry under Possible directions.Merge pull request #482 from drsnuggles8/feature/codegen-serializer-g…
drsnuggles8 commentedon Jul 1, 2026
Slice advanced by #482 (merged 2026-06-30); worktree removed. This slice widened the SceneSerializer codegen classifier to cover glm-math (
quat/mat3/mat4/ivec2-4), small ints (u8/u16/i8/i16), andstd::vector<primitive>, plus a parser access-level fix (private/protected members now correctly mark a component non-trivial). Still open per the PR:Ref<T>, nested structs /std::vector<struct>, andstd::unordered_map/setneed the recursive/registry design. Leaving this umbrella open.Merge pull request #500 from drsnuggles8/feature/codegen-serializer-c…
drsnuggles8 commentedon Jul 2, 2026
Advanced by #500 (merged 2026-07-01): the
OLO_SERIALIZE(Skip)per-field opt-out slice — lets SceneSerializer codegen auto-generate a component whose only obstacle is a runtime-only field (migratedUIButtonComponent/UISliderComponentoffkComponentsCustomSerialize). Worktree removed. The remaining hand-written classes —vector<struct>, map/set, nested structs, and theClamp/Min/Maxannotation keys — stay open; leaving this open for them.drsnuggles8 commentedon Jul 2, 2026
Shipped — Nested-struct /
std::vector<struct>slice (PR #511)The classifier now recursively handles an all-trivial nested
structand astd::vector<struct>of one (tools/OloHeaderTool/main.cpp):CollectStructBodiesbuilds a leaf-name→body map of every struct underOloEngine/src;ClassifyStruct(mutually recursive withParseComponentFields, with avisitedcycle-guard) classifies a member asPropType::Structiff its struct's every member is itself serializer-trivial and public. A scalar nested struct emits a nestedBeginMap…EndMapsub-map; astd::vector<struct>emits aBeginSeqof sub-maps. Emit is a recursive pair (EmitSerializeFields/EmitDeserializeFields) so nesting composes to any depth (e.g.LODGroup→Levels[]→ eachLODLevel).}, not only at;. An inline method body carries no trailing;, so without this it merged forward and swallowed a following member and an interveningprivate:label — which would have mis-seenColliderMaterial(friction getters thenprivate:floats) as all-public-trivial and emitted private-member-access code that won't compile. With the fix the private members are seen correctly and the struct is rejected, so the six 3D colliders stay hand-written.Diff-the-
.inl: 5 components flipped, all needed exclusionAs predicted in the #500 note ("the vector / nested-struct classes have no clean beneficiary — every candidate must stay hand-written for an independent reason"), regenerating flipped 5 components and every one required a
kComponentsCustomSerializeexclusion:LODGroupComponent— hand-written flattens theLODGroupsub-struct to top-levelBias/Levelskeys and omits runtimem_GeneratedLODHandles; a nestedLODGroup:sub-map would change the on-disk format.NavMeshBoundsComponent— deserializeSanitizeVec3s Min/Max, orders Min≤Max, drops non-finite links / clamps Radius.DialogueStateComponent/NoiseAnimationStateComponent/SpringBoneStateComponent— per-tick runtime state that must not be serialized (theIDComponentblind spot: no hand-written block, so only the exclusion stops an unwanted auto-generation).Net generated engine code is byte-identical to before (only header comments changed), so the emit refactor is behavior-preserving. Added
OloEngine/tests/Serialization/NestedStructSerializerCodegenTest.cpp— round-trips the realLODGroup/OffMeshLinkstructs through the generator's exact emit shape (the live path isn't compiled into the engine while all beneficiaries are excluded). All 14 serializer/coverage tests + 407 broader tests pass.Still remaining (parser skips them automatically)
Ref<T>runtime handles (rebuilt from a pairedAssetHandleon load).std::unordered_map/std::unordered_set.std::array(e.g.SHCoefficients).std::vectorof a non-trivial struct.Clamp/Min/Maxper-field annotation (theOLO_SERIALIZEdirection begun by codegen: OLO_SERIALIZE(Skip) field annotation for SceneSerializer (#451) #500'sSkip) — the highest-leverage next slice, since it would unblock the many clamp-excluded components.Leaving this issue open for the follow-up slices.
Merge pull request #511 from drsnuggles8/feature/codegen-serializer-n…
drsnuggles8 commentedon Jul 3, 2026
Clamp slice delivered by #536 (merged 2026-07-03, worktree removed): added
OLO_SERIALIZE(Clamp, Min=…, Max=…)and migrated SnowDeformerComponent/FogVolumeComponent/NavAgentComponent/SpringBoneComponent off hand-written serialize blocks. Still hand-written per CLAUDE.md: BuoyancyComponent/NoiseAnimationComponent (need vec3 range clamping), LightProbeVolumeComponent/NavMeshBoundsComponent/StreamingVolumeComponent (cross-field invariants), PhysicsJoint3DComponent (large/varied), SphereAreaLightComponent (reject-not-clamp semantics). Leaving open.drsnuggles8 commentedon Jul 4, 2026
Partial progress: #536 (merged 2026-07-03) shipped
OLO_SERIALIZE(Clamp, Min=…, Max=…)— the field-level annotation this issue's "Possible directions" section proposed. MigratedFogVolumeComponent,SnowDeformerComponent,SpringBoneComponent, andNavAgentComponent(whose runtime fields also gotSkip) offkComponentsCustomSerializeonto full codegen. Remaining scope is unchanged otherwise:std::vector<struct>,std::unordered_map/set, and the trivial-but-custom components still needing per-component opt-in (BuoyancyComponent,SphereAreaLightComponent,VehicleComponent,ScriptComponent, etc.). Leaving open.drsnuggles8 commentedon Jul 5, 2026
Sixth slice shipped in #569 —
std::unordered_set/std::unordered_mapfield support.The scene-serializer codegen now classifies a
std::unordered_set<E>(E a sortable trivial scalar —bool/int/u32/u64/small-int/AssetHandle/std::string/enum, excludingfloat/glm-vector/matrix/quat/struct since none of those define a meaningfuloperator<) and astd::string-keyedstd::unordered_map<std::string, V>as serializer-trivial. Both round-trip through a sorted temporary copy before emit — unlikestd::vector's already-deterministic declaration order, an unordered container's iteration order is implementation-defined and can vary run-to-run, which would otherwise break the "serialize → deserialize → serialize produces identical YAML" round-trip guarantee.Migrated:
PrefabComponent(threestd::unordered_set<std::string>override-tracking fields) fully off its hand-written block — it needed nokComponentsCustomSerializeentry either before or after, since it was hand-written solely becauseunordered_setwasn't a recognised type yet.Left hand-written:
MorphTargetComponent::Weights(std::unordered_map<std::string, f32>) is now individually trivial too, but the component stays hand-written for two unrelated reasons: aRef<MorphTargetSet>field whereMorphTargetSetisn'tAsset-derived, and a per-value[0, 1]clamp on load the plain round-trip doesn't express.No live component exercises an
AssetHandle/enum-element set or theunordered_mappath yet, soOloEngine/tests/Serialization/ContainerSerializerCodegenTest.cppmirrors the generator's exact emitted shape for those cases (same pattern asNestedStructSerializerCodegenTest.cppfor the earlier nested-struct slice) to give them compile + runtime coverage ahead of a real beneficiary.Remaining open per the "Out of scope" section:
std::array, a non-std::string-keyed map,std::vectorof a non-trivial struct, and the handful ofkComponentsCustomSerializecomponents needing per-component opt-in. Leaving this umbrella issue open.Merge pull request #569 from drsnuggles8/feature/codegen-serializer-c…
drsnuggles8 commentedon Jul 6, 2026
Serializer-codegen slice completed by #569 (merged 2026-07-06): the field classifier now recognizes
std::unordered_set<E>(sortable trivial scalar element) andstd::string-keyedstd::unordered_map<std::string, V>, both round-tripping through a sorted temporary to preserve YAML determinism. MigratedPrefabComponent's three override-trackingunordered_set<std::string>fields fully onto the generated path. Worktree removed. Leaving this umbrella open — remaining unhandled field shapes:Ref<T>of a non-Asset type,std::array, a non-std::string-keyed map, anunordered_setof a non-scalar element, and astd::vectorof a non-trivial struct.drsnuggles8 commentedon Jul 6, 2026
Surveyed the remaining scope (
std::array, non-std::string-keyedunordered_map, non-scalarunordered_setelements,Ref<T>of non-Asset types) against every component currently inkComponentsCustomSerializeplus a fullstd::array<grep across all component headers: no component is blocked by exactly one of these 4 shapes today — every hand-written exclusion is for a clamp/Sanitize/runtime-field-omission/entity-identity reason already handled by other mechanisms (Skip/Clamp/nested-struct/etc.), andstd::arrayhas zero real component users. The one non-string-keyedunordered_mapin the engine (TerrainComponent::m_VoxelMeshes) is already correctly excluded for unrelated non-AssetRef<T>reasons, so adding map support wouldn't flip it to auto-trivial anyway. This umbrella's remaining scope is currently hypothetical future-proofing, not blocking any real component — flagging so it doesn't keep floating to the top of the ranked backlog as if it were actionable.Merge pull request #580 from drsnuggles8/feature/codegen-vec3-clamp
drsnuggles8 commentedon Jul 7, 2026
Another slice shipped in #580 (merged 2026-07-07):
OLO_SERIALIZE(Clamp, ...)now acceptsglm::vec3fields (per-component clamp viaglm::clamp/glm::max/glm::min, mirroring the hand-writtenSanitizeVec3Clampedidiom). MigratedBuoyancyComponentandNoiseAnimationComponentfully offkComponentsCustomSerializeonto the generated path — both were previously flagged in CLAUDE.md as needing this exact follow-up. Worktree removed.Leaving this umbrella open — the still-open survey items (
std::array, non-std::string-keyed map, non-scalarunordered_setelement,Ref<T>of a non-Asset type) remain unblocked-by-any-real-component per the 2026-07-06 survey.drsnuggles8 commentedon Jul 9, 2026
Swept during /start-work triage (2026-07-09), confirming the 2026-07-06 survey comment: the remaining field-shape gaps (std::array, non-string-keyed unordered_map, non-scalar unordered_set element, Ref of a non-Asset type) were surveyed against every component in kComponentsCustomSerialize plus a full std::array<' grep across all component headers — none is currently blocked by exactly one of these 4 shapes; every real exclusion has an independent, already-handled reason (Skip/Clamp/nested-struct/format-incompatibility/entity-identity). This umbrella keeps floating to the top of the score-ranked backlog (score 0.83) as if actionable, but there is no real component to migrate. Recommend closing, or re-scoring effort/confidence down to reflect that remaining scope is speculative future-proofing rather than blocking work — reopen if a new component ever needs one of these shapes.
drsnuggles8 commentedon Oct 15, 2026
Closed in favour of #1623, which keeps only the work that is left (three groups of kComponentsCustomSerialize exclusions). Every field-type slice tracked here has shipped.