@@ -58,7 +58,7 @@ static bool IsPlaying()
5858 if (IsPlaying()) \
5959 { \
6060 UE_LOG (PuertsEditorModule, Error, TEXT (" change the layout of class[%s] in PIE mode is forbiden!" ), \
61- *GeneratedClass-> GetName ( )); \
61+ *GetNameSafe (GeneratedClass )); \
6262 NeedSave = false ; \
6363 return false ; \
6464 }
@@ -67,11 +67,26 @@ static bool IsPlaying()
6767 if (IsPlaying()) \
6868 { \
6969 UE_LOG (PuertsEditorModule, Error, TEXT (" change the layout of class[%s] in PIE mode is forbiden!" ), \
70- *GeneratedClass-> GetName ( )); \
70+ *GetNameSafe (GeneratedClass )); \
7171 NeedSave = false ; \
7272 return ; \
7373 }
7474
75+ bool UPEBlueprintAsset::IsAssetReady (const TCHAR * InOperation)
76+ {
77+ if (IsValid (Blueprint) && IsValid (GeneratedClass))
78+ {
79+ return true ;
80+ }
81+
82+ // LoadOrCreate[WithMetaData] failed (or was refused in PIE mode) and left this asset half initialized. The
83+ // TypeScript side drives every Add*/Setup* call unconditionally, so bail out instead of dereferencing null.
84+ UE_LOG (PuertsEditorModule, Error, TEXT (" %s skipped: blueprint asset is not ready (blueprint[%s], generated class[%s])" ),
85+ InOperation, *GetNameSafe (Blueprint), *GetNameSafe (GeneratedClass));
86+ NeedSave = false ;
87+ return false ;
88+ }
89+
7590bool UPEBlueprintAsset::Existed (const FString& InName, const FString& InPath)
7691{
7792 FString BPPath = FString (TEXT (TS_BLUEPRINT_PATH )) / InName + TEXT (" .uasset" );
@@ -95,22 +110,87 @@ bool UPEBlueprintAsset::LoadOrCreate(
95110 if (Blueprint)
96111 {
97112 GeneratedClass = Blueprint->GeneratedClass ;
98- if (auto TypeScriptGeneratedClass = Cast<UTypeScriptGeneratedClass> (GeneratedClass))
113+ if (IsValid (GeneratedClass))
99114 {
100- HasConstructor = TypeScriptGeneratedClass->HasConstructor ;
115+ if (auto TypeScriptGeneratedClass = Cast<UTypeScriptGeneratedClass>(GeneratedClass))
116+ {
117+ HasConstructor = TypeScriptGeneratedClass->HasConstructor ;
118+ }
119+ Package = Cast<UPackage>(Blueprint->GetOuter ());
120+ if (Blueprint->ParentClass != ParentClass)
121+ {
122+ // Normal TypeScript reparent: old parent still loads, just retarget and let Save recompile.
123+ CanChangeCheckWithBoolRet ();
124+ Blueprint->ParentClass = ParentClass;
125+ NeedSave = true ;
126+ }
127+ else
128+ {
129+ NeedSave = false ;
130+ }
131+ return true ;
101132 }
102- Package = Cast<UPackage>(Blueprint->GetOuter ());
103- if (Blueprint->ParentClass != ParentClass)
133+
134+ // Stale asset: the parent class recorded in the .uasset failed to load, so GeneratedClass never came up
135+ // (see the LogLinker "both will fail to load" warnings). If TypeScript already points at a live ParentClass,
136+ // tear the broken asset down and fall through to recreate - otherwise users could not recover by changing
137+ // the TS base class. With no replacement parent there is nothing we can safely do.
138+ if (!IsValid (ParentClass))
104139 {
105- CanChangeCheckWithBoolRet ();
106- Blueprint->ParentClass = ParentClass;
107- NeedSave = true ;
140+ UE_LOG (PuertsEditorModule, Error,
141+ TEXT (" blueprint [%s] has no valid generated class and no replacement parent was provided, skip" ), *PackageName);
142+ Blueprint = nullptr ;
143+ GeneratedClass = nullptr ;
144+ NeedSave = false ;
145+ return false ;
108146 }
109- else
147+
148+ if (IsPlaying ())
110149 {
150+ UE_LOG (PuertsEditorModule, Error,
151+ TEXT (" blueprint [%s] is broken (missing generated class) but recreating in PIE mode is forbiden!" ), *PackageName);
152+ Blueprint = nullptr ;
153+ GeneratedClass = nullptr ;
111154 NeedSave = false ;
155+ return false ;
112156 }
113- return true ;
157+
158+ UE_LOG (PuertsEditorModule, Warning,
159+ TEXT (" blueprint [%s] has no valid generated class (recorded parent failed to load). Recreating under parent [%s]" ),
160+ *PackageName, *ParentClass->GetName ());
161+
162+ FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT (" AssetRegistry" ));
163+ AssetRegistryModule.Get ().AssetDeleted (Blueprint);
164+
165+ // Move the broken object out of the package so CreateBlueprint can reuse the same asset name.
166+ const FString TrashName = InName + TEXT (" __Broken_" ) + FGuid::NewGuid ().ToString (EGuidFormats::Digits);
167+ Blueprint->ClearFlags (RF_Standalone | RF_Public);
168+ Blueprint->Rename (*TrashName, GetTransientPackage (),
169+ REN_DontCreateRedirectors | REN_DoNotDirty | REN_ForceNoResetLoaders | REN_NonTransactional);
170+ Blueprint->RemoveFromRoot ();
171+ #if ENGINE_MAJOR_VERSION >= 5
172+ Blueprint->MarkAsGarbage ();
173+ #else
174+ Blueprint->MarkPendingKill ();
175+ #endif
176+ Blueprint = nullptr ;
177+ GeneratedClass = nullptr ;
178+ Package = nullptr ;
179+
180+ FString AssetFilePath = FString (TEXT (TS_BLUEPRINT_PATH )) / InPath / InName + TEXT (" .uasset" );
181+ if (AssetFilePath[0 ] == TEXT (' /' ) || AssetFilePath[0 ] == TEXT (' \\ ' ))
182+ {
183+ AssetFilePath = AssetFilePath.Mid (1 );
184+ }
185+ AssetFilePath = FPaths::ProjectContentDir () / AssetFilePath;
186+ IPlatformFile& PlatformFile = FPlatformFileManager::Get ().GetPlatformFile ();
187+ if (PlatformFile.FileExists (*AssetFilePath) && !PlatformFile.DeleteFile (*AssetFilePath))
188+ {
189+ UE_LOG (PuertsEditorModule, Error, TEXT (" Failed to delete broken TypeScript blueprint asset: %s" ), *AssetFilePath);
190+ NeedSave = false ;
191+ return false ;
192+ }
193+ // Fall through to the create path below with the caller's ParentClass.
114194 }
115195
116196 if (!ParentClass)
@@ -161,6 +241,14 @@ bool UPEBlueprintAsset::LoadOrCreate(
161241 // Mark the package dirty...
162242 Package->MarkPackageDirty ();
163243 GeneratedClass = Blueprint->GeneratedClass ;
244+ if (!IsValid (GeneratedClass))
245+ {
246+ UE_LOG (PuertsEditorModule, Error, TEXT (" blueprint [%s] was created without a valid generated class" ), *PackageName);
247+ Blueprint = nullptr ;
248+ GeneratedClass = nullptr ;
249+ NeedSave = false ;
250+ return false ;
251+ }
164252 return true ;
165253 }
166254 else
@@ -394,6 +482,12 @@ UClass* const GetOverrideFunctionClass(UBlueprint* Blueprint, const FName FuncNa
394482void UPEBlueprintAsset::AddFunction (FName InName, bool IsVoid, FPEGraphPinType InGraphPinType, FPEGraphTerminalType InPinValueType,
395483 int32 InSetFlags, int32 InClearFlags)
396484{
485+ if (!IsAssetReady (TEXT (" AddFunction" )))
486+ {
487+ ClearParameter ();
488+ return ;
489+ }
490+
397491 InSetFlags &= ~InClearFlags;
398492 InSetFlags &= ~FUNC_Native;
399493 const int32 NetMask = FUNC_Net | FUNC_NetMulticast | FUNC_NetServer | FUNC_NetClient | FUNC_NetReliable;
@@ -404,7 +498,7 @@ void UPEBlueprintAsset::AddFunction(FName InName, bool IsVoid, FPEGraphPinType I
404498
405499 UClass* SuperClass = GeneratedClass->GetSuperClass ();
406500
407- UFunction* ParentFunction = SuperClass->FindFunctionByName (InName);
501+ UFunction* ParentFunction = SuperClass ? SuperClass ->FindFunctionByName (InName) : nullptr ;
408502
409503 UFunction* Function = GeneratedClass->FindFunctionByName (InName, EIncludeSuperFlag::ExcludeSuper);
410504
@@ -856,6 +950,12 @@ void UPEBlueprintAsset::AddFunctionWithMetaData(FName InName, bool IsVoid, FPEGr
856950 * @brief
857951 * function body
858952 */
953+ if (!IsAssetReady (TEXT (" AddFunctionWithMetaData" )))
954+ {
955+ ClearParameter ();
956+ return ;
957+ }
958+
859959 if (IsValid (InMetaData))
860960 {
861961 InSetFlags |= static_cast <int32>(InMetaData->FunctionFlags );
@@ -895,6 +995,11 @@ void UPEBlueprintAsset::ClearParameter()
895995
896996void UPEBlueprintAsset::RemoveComponent (FName ComponentName)
897997{
998+ if (!IsValid (Blueprint) || !Blueprint->SimpleConstructionScript )
999+ {
1000+ return ;
1001+ }
1002+
8981003 auto SCS_Node = Blueprint->SimpleConstructionScript ->FindSCSNode (ComponentName);
8991004 if (SCS_Node)
9001005 {
@@ -970,6 +1075,11 @@ void UPEBlueprintAsset::RemoveComponent(FName ComponentName)
9701075
9711076void UPEBlueprintAsset::SetupAttachment (FName InComponentName, FName InParentComponentName)
9721077{
1078+ if (!IsValid (Blueprint))
1079+ {
1080+ return ;
1081+ }
1082+
9731083 if (Blueprint->SimpleConstructionScript )
9741084 {
9751085 auto SCS_Node = Blueprint->SimpleConstructionScript ->FindSCSNode (InComponentName);
@@ -1017,6 +1127,11 @@ void UPEBlueprintAsset::SetupAttachment(FName InComponentName, FName InParentCom
10171127
10181128void UPEBlueprintAsset::SetupAttachments (TMap<FName, FName> InAttachments)
10191129{
1130+ if (!IsAssetReady (TEXT (" SetupAttachments" )))
1131+ {
1132+ return ;
1133+ }
1134+
10201135 if (Blueprint->SimpleConstructionScript )
10211136 {
10221137 for (auto & KV : InAttachments)
@@ -1050,14 +1165,19 @@ void UPEBlueprintAsset::SetupAttachments(TMap<FName, FName> InAttachments)
10501165void UPEBlueprintAsset::AddMemberVariable (FName NewVarName, FPEGraphPinType InGraphPinType, FPEGraphTerminalType InPinValueType,
10511166 int32 InLFlags, int32 InHFlags, int32 InLifetimeCondition)
10521167{
1168+ if (!IsAssetReady (TEXT (" AddMemberVariable" )))
1169+ {
1170+ return ;
1171+ }
1172+
10531173 uint64 InFlags = (uint64) InHFlags << 32 | InLFlags;
10541174 FEdGraphPinType PinType = ToFEdGraphPinType (InGraphPinType, InPinValueType);
10551175
10561176 if (PinType.ContainerType == EPinContainerType::None)
10571177 {
10581178 if (auto ComponentClass = Cast<UClass>(PinType.PinSubCategoryObject ))
10591179 {
1060- if (Blueprint-> GeneratedClass ->IsChildOf <AActor>() && Blueprint->SimpleConstructionScript &&
1180+ if (GeneratedClass->IsChildOf <AActor>() && Blueprint->SimpleConstructionScript &&
10611181 PinType.PinCategory == UEdGraphSchema_K2::PC_Object &&
10621182 (ComponentClass == UActorComponent::StaticClass () || ComponentClass->IsChildOf <UActorComponent>()))
10631183 {
@@ -1183,6 +1303,11 @@ void UPEBlueprintAsset::AddMemberVariable(FName NewVarName, FPEGraphPinType InGr
11831303void UPEBlueprintAsset::AddMemberVariableWithMetaData (FName InNewVarName, FPEGraphPinType InGraphPinType,
11841304 FPEGraphTerminalType InPinValueType, int32 InLFlags, int32 InHFLags, int32 InLifetimeCondition, UPEPropertyMetaData* InMetaData)
11851305{
1306+ if (!IsAssetReady (TEXT (" AddMemberVariableWithMetaData" )))
1307+ {
1308+ return ;
1309+ }
1310+
11861311 if (IsValid (InMetaData))
11871312 { // handle the conflict here
11881313 EPropertyFlags InputFlags = static_cast <EPropertyFlags>((static_cast <uint64>(InHFLags) << 32 ) + InLFlags);
@@ -1356,38 +1481,54 @@ void UPEBlueprintAsset::Save()
13561481 FBlueprintEditorUtils::MarkBlueprintAsModified (Blueprint);
13571482 FKismetEditorUtilities::CompileBlueprint (Blueprint);
13581483
1359- for (TFieldIterator<UFunction> FuncIt (TypeScriptGeneratedClass, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
1484+ // Compiling may reinstance the class: the previous UClass is trashed while the blueprint points at a
1485+ // freshly created one. Refresh the cached pointer, otherwise derived TypeScript classes that take
1486+ // GeneratedClass as their parent keep a dead class.
1487+ GeneratedClass = Blueprint->GeneratedClass ;
1488+ TypeScriptGeneratedClass = Cast<UTypeScriptGeneratedClass>(GeneratedClass);
1489+
1490+ if (IsValid (TypeScriptGeneratedClass))
13601491 {
1361- auto Function = *FuncIt;
1362- Function->FunctionFlags &= ~FUNC_Native;
1492+ for (TFieldIterator<UFunction> FuncIt (TypeScriptGeneratedClass, EFieldIteratorFlags::ExcludeSuper); FuncIt;
1493+ ++FuncIt)
1494+ {
1495+ auto Function = *FuncIt;
1496+ Function->FunctionFlags &= ~FUNC_Native;
13631497
1364- auto FunctionFName = Function->GetFName ();
1365- FString FunctionName = Function->GetName ();
1498+ auto FunctionFName = Function->GetFName ();
1499+ FString FunctionName = Function->GetName ();
13661500
1367- static FString AxisPrefix (TEXT (" InpAxisEvt_" ));
1368- if (FunctionName.StartsWith (AxisPrefix))
1369- {
1370- auto FunctionNameWithoutPrefix = FunctionName.Mid (AxisPrefix.Len ());
1371- int32 SubPos;
1372- if (FunctionNameWithoutPrefix.FindChar (' _' , SubPos))
1501+ static FString AxisPrefix (TEXT (" InpAxisEvt_" ));
1502+ if (FunctionName.StartsWith (AxisPrefix))
13731503 {
1374- FunctionName = FunctionNameWithoutPrefix.Mid (0 , SubPos);
1504+ auto FunctionNameWithoutPrefix = FunctionName.Mid (AxisPrefix.Len ());
1505+ int32 SubPos;
1506+ if (FunctionNameWithoutPrefix.FindChar (' _' , SubPos))
1507+ {
1508+ FunctionName = FunctionNameWithoutPrefix.Mid (0 , SubPos);
1509+ }
13751510 }
1376- }
1377- static FString ActionPrefix (TEXT (" InpActEvt_" ));
1378- if (FunctionName.StartsWith (ActionPrefix))
1379- {
1380- auto FunctionNameWithoutPrefix = FunctionName.Mid (ActionPrefix.Len ());
1381- int32 SubPos;
1382- if (FunctionNameWithoutPrefix.FindChar (' _' , SubPos))
1511+ static FString ActionPrefix (TEXT (" InpActEvt_" ));
1512+ if (FunctionName.StartsWith (ActionPrefix))
13831513 {
1384- FunctionName = FunctionNameWithoutPrefix.Mid (0 , SubPos);
1514+ auto FunctionNameWithoutPrefix = FunctionName.Mid (ActionPrefix.Len ());
1515+ int32 SubPos;
1516+ if (FunctionNameWithoutPrefix.FindChar (' _' , SubPos))
1517+ {
1518+ FunctionName = FunctionNameWithoutPrefix.Mid (0 , SubPos);
1519+ }
1520+ }
1521+ if (FunctionAdded.Contains (*FunctionName))
1522+ {
1523+ TypeScriptGeneratedClass->FunctionToRedirect .Add (FunctionFName);
13851524 }
13861525 }
1387- if (FunctionAdded.Contains (*FunctionName))
1388- {
1389- TypeScriptGeneratedClass->FunctionToRedirect .Add (FunctionFName);
1390- }
1526+ }
1527+ else
1528+ {
1529+ UE_LOG (PuertsEditorModule, Error,
1530+ TEXT (" blueprint [%s] has no valid generated class after compiling, function redirection is skipped" ),
1531+ *GetNameSafe (Blueprint));
13911532 }
13921533
13931534 TArray<UPackage*> PackagesToSave;
0 commit comments