feat(blueprint): make User Defined Structs editable after creation - #138
Open
Alexbeav wants to merge 2 commits into
Open
feat(blueprint): make User Defined Structs editable after creation#138Alexbeav wants to merge 2 commits into
Alexbeav wants to merge 2 commits into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
create_user_defined_structauthors 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_textand parsingVariablesDescriptionsout 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.
FStructureEditorUtilsis already included inMonolithBlueprintStructActions.cppand already drivescreate_user_defined_struct—AddVariable,RenameVariable,ChangeVariableType,ChangeVariableDefaultValue,GetVarDesc,CompileStructure. It was simply unreachable for an existing asset.Fields are targeted by display name (
Mobility) rather than the serializedVarName(Mobility_36_31089BED…), though either resolves; a miss lists the names that do exist.get_struct_fieldsreports types in the same grammar the writers parse, soget→addround-trips.Three behaviours that are deliberate
RemoveVariablehardcodesbAllowToMakeEmpty = falseand returnsfalsefor both "would empty the struct" and "no such field", distinguishing them only in a log line.remove_struct_fieldpre-checks the field exists and then the count, so the caller is told which case occurred instead of getting an unexplained failure.RenameVariablepreservesVarGuid, which is why a rename does not disconnect existing Break/Make pins.set_struct_field_typedoes disconnect them, and is documented as a migration rather than a rename.FScopedTransaction, so these actions open none — nesting would only widen the undo scope.add_struct_fieldtakes an optionalafterto position the new member, resolving the anchor's GUID before mutating, becauseAddVariablereallocates the description array and a pointer taken earlier would dangle.The second commit
PinTypeToStringdrops the container prefix — that is a separate helper, and pairing the two is the convention the module already uses (SerializePin). The first cut reported anarray:intfield as plainint, 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.TypeRoundTripis what caught it, failing onarray: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 ofStructureEditorUtils.cppwhile 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
BuildPlugin, BUILD SUCCESSFUL, 0 errors, 0 warningsMonolith.StructFields.*andMonolith.ProjectSearch.*Note on how it was verified
BuildPluginon currentmasterfails inMonolithAudioRuntime(GEngineundeclared,IMPLEMENT_MODULEsyntax error) before reachingMonolithBlueprint. #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
FStructureEditorUtilsalso exposesChangeTooltipandMoveVariableas a standalone reorder. Happy to addset_struct_field_tooltip/move_struct_fieldif useful, but left out here to keep the PR to one concern.