Skip to content

feat(blueprint): make User Defined Structs editable after creation - #138

Open
Alexbeav wants to merge 2 commits into
tumourlove:masterfrom
Alexbeav:feat/struct-field-actions
Open

feat(blueprint): make User Defined Structs editable after creation#138
Alexbeav wants to merge 2 commits into
tumourlove:masterfrom
Alexbeav:feat/struct-field-actions

Conversation

@Alexbeav

Copy link
Copy Markdown
Contributor

The problem

create_user_defined_struct authors a struct once. There is no way to change one afterwards, and no way to read one at all.

Adding a single field to a struct that shipping Blueprints already break has to be done by hand in the editor, and recovering its schema means dumping raw T3D through project export_asset_text and parsing VariablesDescriptions out of it.

The change

Five actions: get_struct_fields, add_struct_field, remove_struct_field, rename_struct_field, set_struct_field_type.

No new engine surface was needed. FStructureEditorUtils is already included in MonolithBlueprintStructActions.cpp and already drives create_user_defined_structAddVariable, RenameVariable, ChangeVariableType, ChangeVariableDefaultValue, GetVarDesc, CompileStructure. It was simply unreachable for an existing asset.

Fields are targeted by display name (Mobility) rather than the serialized VarName (Mobility_36_31089BED…), though either resolves; a miss lists the names that do exist. get_struct_fields reports types in the same grammar the writers parse, so getadd round-trips.

Three behaviours that are deliberate

  • RemoveVariable hardcodes bAllowToMakeEmpty = false and returns false for both "would empty the struct" and "no such field", distinguishing them only in a log line. remove_struct_field pre-checks the field exists and then the count, so the caller is told which case occurred instead of getting an unexplained failure.
  • RenameVariable preserves VarGuid, which is why a rename does not disconnect existing Break/Make pins. set_struct_field_type does disconnect them, and is documented as a migration rather than a rename.
  • Every engine writer opens its own FScopedTransaction, so these actions open none — nesting would only widen the undo scope.

add_struct_field takes an optional after to position the new member, resolving the anchor's GUID before mutating, because AddVariable reallocates the description array and a pointer taken earlier would dangle.

The second commit

PinTypeToString drops the container prefix — that is a separate helper, and pairing the two is the convention the module already uses (SerializePin). The first cut reported an array:int field as plain int, which silently broke the documented round-trip for every container-typed field.

It compiled clean on both engines and read fine on review. Monolith.StructFields.TypeRoundTrip is what caught it, failing on array:int round-trips its container type.

Tests

The handlers resolve their target through FMonolithAssetUtils::LoadAssetByPath, so a true end-to-end test would have to create and delete real content assets. Instead:

  • Monolith.StructFields.EngineContract — locks the engine behaviours the handlers stand on: empty-struct refusal, GUID survival across rename, append ordering, MoveVariable(PositionBelow). Each was read out of StructureEditorUtils.cpp while writing the actions; a silent change to any would break a handler behind a green compile.
  • Monolith.StructFields.TypeRoundTrip — eight types through description → ToPinType() → rendered string → reparse.
  • Monolith.StructFields.Validation — the parameter paths that return before any asset load.

Verification

  • UE 5.8.1 (CL 56057345) — BuildPlugin, BUILD SUCCESSFUL, 0 errors, 0 warnings
  • UE 5.7.4 (CL 51494982), the stated floor — BUILD SUCCESSFUL, 0 errors, 0 warnings
  • Every changed/new file lands in the non-unity compile set under Adaptive Build
  • Automation: 9/9 green across Monolith.StructFields.* and Monolith.ProjectSearch.*

Note on how it was verified

BuildPlugin on current master fails in MonolithAudioRuntime (GEngine undeclared, IMPLEMENT_MODULE syntax error) before reaching MonolithBlueprint. #136 touches exactly those files, so this branch was built and tested with #136 merged on a throwaway branch. It does not depend on #136 functionally and does not include it.

Docs

CHANGELOG.md (Unreleased), Skills/unreal-blueprints (struct section 6 → 11 actions, with the removal/retype caveats spelled out).

Possible follow-up

FStructureEditorUtils also exposes ChangeTooltip and MoveVariable as a standalone reorder. Happy to add set_struct_field_tooltip / move_struct_field if useful, but left out here to keep the PR to one concern.

create_user_defined_struct authors a struct once. There was no way to
change one afterwards, and no way to read one at all -- adding a single
field to a struct that shipping Blueprints already break had to be done
by hand in the editor, and recovering its schema meant dumping raw T3D
through project export_asset_text.

Adds get_struct_fields, add_struct_field, remove_struct_field,
rename_struct_field and set_struct_field_type. No new engine surface was
needed: FStructureEditorUtils was already included here and already
driving create_user_defined_struct; it was simply unreachable for an
existing asset.

Fields are targeted by display name (Mobility) rather than the
serialized VarName (Mobility_36_31089BED...), though either resolves,
and a miss lists the names that do exist. get_struct_fields reports
types through PinTypeToString, the same grammar the writers parse, so
get -> add round-trips.

Three engine behaviours drive the design. RemoveVariable hardcodes
bAllowToMakeEmpty = false and returns false for BOTH 'would empty the
struct' and 'no such field', distinguishing them only in a log line, so
remove_struct_field pre-checks and reports which occurred. RenameVariable
preserves VarGuid, which is why a rename does not disconnect existing
Break/Make pins, whereas set_struct_field_type does and is documented as
a migration. Every engine writer opens its own FScopedTransaction, so
these open none.

add_struct_field takes an optional 'after' to position the new member,
resolving the anchor GUID before mutating because AddVariable
reallocates the description array.

Tests lock the engine contract the handlers depend on plus the
validation paths.
…eld types

PinTypeToString drops the container: an array:int field was reported as
plain int, so the documented get_struct_fields -> add_struct_field
round-trip was false for every container-typed field. The prefix is a
separate helper and pairing the two is the convention the module already
uses (SerializePin). Both get_struct_fields and set_struct_field_type's
previous_type go through one DescribePinType helper now.

Caught by Monolith.StructFields.TypeRoundTrip, which failed on
'array:int round-trips its container type'. It compiled clean and passed
review by eye.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants