1094 lines
36 KiB
C++
1094 lines
36 KiB
C++
#include "UnrealSharpEditor.h"
|
|
#include "AssetToolsModule.h"
|
|
#include "BlueprintCompilationManager.h"
|
|
#include "BlueprintEditorLibrary.h"
|
|
#include "CSUnrealSharpEditorCommands.h"
|
|
#include "DirectoryWatcherModule.h"
|
|
#include "CSStyle.h"
|
|
#include "CSUnrealSharpEditorSettings.h"
|
|
#include "DesktopPlatformModule.h"
|
|
#include "IDirectoryWatcher.h"
|
|
#include "IPluginBrowser.h"
|
|
#include "ISettingsModule.h"
|
|
#include "LevelEditor.h"
|
|
#include "SourceCodeNavigation.h"
|
|
#include "SubobjectDataSubsystem.h"
|
|
#include "UnrealSharpRuntimeGlue.h"
|
|
#include "AssetActions/CSAssetTypeAction_CSBlueprint.h"
|
|
#include "Engine/AssetManager.h"
|
|
#include "Engine/InheritableComponentHandler.h"
|
|
#include "Features/IPluginsEditorFeature.h"
|
|
#include "UnrealSharpCore/CSManager.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Interfaces/IMainFrameModule.h"
|
|
#include "Interfaces/IPluginManager.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Kismet2/DebuggerCommands.h"
|
|
#include "Logging/StructuredLog.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Plugins/CSPluginTemplateDescription.h"
|
|
#include "Slate/CSNewProjectWizard.h"
|
|
#include "TypeGenerator/Register/CSGeneratedClassBuilder.h"
|
|
#include "UnrealSharpProcHelper/CSProcHelper.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "TypeGenerator/CSClass.h"
|
|
#include "TypeGenerator/CSEnum.h"
|
|
#include "TypeGenerator/CSScriptStruct.h"
|
|
#include "UnrealSharpUtilities/UnrealSharpUtils.h"
|
|
#include "Utils/CSClassUtilities.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "FUnrealSharpEditorModule"
|
|
|
|
DEFINE_LOG_CATEGORY(LogUnrealSharpEditor);
|
|
|
|
FUnrealSharpEditorModule& FUnrealSharpEditorModule::Get()
|
|
{
|
|
return FModuleManager::LoadModuleChecked<FUnrealSharpEditorModule>("UnrealSharpEditor");
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::StartupModule()
|
|
{
|
|
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
|
AssetTools.RegisterAssetTypeActions(MakeShared<FCSAssetTypeAction_CSBlueprint>());
|
|
|
|
TArray<FString> ProjectPaths;
|
|
FCSProcHelper::GetAllProjectPaths(ProjectPaths);
|
|
|
|
for (const FString& ProjectPath : ProjectPaths)
|
|
{
|
|
FString Path = FPaths::GetPath(ProjectPath);
|
|
AddDirectoryToWatch(Path);
|
|
}
|
|
|
|
Manager = &UCSManager::GetOrCreate();
|
|
Manager->OnNewStructEvent().AddRaw(this, &FUnrealSharpEditorModule::OnStructRebuilt);
|
|
Manager->OnNewClassEvent().AddRaw(this, &FUnrealSharpEditorModule::OnClassRebuilt);
|
|
Manager->OnNewEnumEvent().AddRaw(this, &FUnrealSharpEditorModule::OnEnumRebuilt);
|
|
|
|
FEditorDelegates::ShutdownPIE.AddRaw(this, &FUnrealSharpEditorModule::OnPIEShutdown);
|
|
|
|
TickDelegate = FTickerDelegate::CreateRaw(this, &FUnrealSharpEditorModule::Tick);
|
|
TickDelegateHandle = FTSTicker::GetCoreTicker().AddTicker(TickDelegate);
|
|
|
|
if (ProjectPaths.IsEmpty())
|
|
{
|
|
IMainFrameModule::Get().OnMainFrameCreationFinished().AddLambda([this](TSharedPtr<SWindow>, bool)
|
|
{
|
|
SuggestProjectSetup();
|
|
});
|
|
}
|
|
|
|
// Make managed types not available for edit in the editor
|
|
{
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
|
|
IAssetTools& AssetToolsRef = AssetToolsModule.Get();
|
|
|
|
Manager->ForEachManagedPackage([&AssetToolsRef](const UPackage* Package)
|
|
{
|
|
AssetToolsRef.GetWritableFolderPermissionList()->AddDenyListItem(Package->GetFName(), Package->GetFName());
|
|
});
|
|
}
|
|
|
|
FCSStyle::Initialize();
|
|
|
|
RegisterCommands();
|
|
RegisterMenu();
|
|
RegisterPluginTemplates();
|
|
|
|
UCSManager& CSharpManager = UCSManager::Get();
|
|
CSharpManager.LoadPluginAssemblyByName(TEXT("UnrealSharp.Editor"));
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::ShutdownModule()
|
|
{
|
|
FTSTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle);
|
|
UToolMenus::UnRegisterStartupCallback(this);
|
|
UToolMenus::UnregisterOwner(this);
|
|
UnregisterPluginTemplates();
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnCSharpCodeModified(const TArray<FFileChangeData>& ChangedFiles)
|
|
{
|
|
if (IsHotReloading())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const UCSUnrealSharpEditorSettings* Settings = GetDefault<UCSUnrealSharpEditorSettings>();
|
|
|
|
if (FPlayWorldCommandCallbacks::IsInPIE() && Settings->AutomaticHotReloading == OnScriptSave)
|
|
{
|
|
bHasQueuedHotReload = true;
|
|
return;
|
|
}
|
|
|
|
for (const FFileChangeData& ChangedFile : ChangedFiles)
|
|
{
|
|
FString NormalizedFileName = ChangedFile.Filename;
|
|
FPaths::NormalizeFilename(NormalizedFileName);
|
|
|
|
// Skip ProjectGlue files
|
|
if (NormalizedFileName.Contains("Glue"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Skip generated files in bin and obj folders
|
|
if (NormalizedFileName.Contains(TEXT("/obj/")))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (Settings->AutomaticHotReloading == OnModuleChange && NormalizedFileName.EndsWith(".dll") &&
|
|
NormalizedFileName.Contains(TEXT("/bin/")))
|
|
{
|
|
// A module changed, initiate the reload and return
|
|
StartHotReload(false);
|
|
return;
|
|
}
|
|
|
|
// Check if the file is a .cs file and not in the bin directory
|
|
FString Extension = FPaths::GetExtension(NormalizedFileName);
|
|
if (Extension != "cs" || NormalizedFileName.Contains(TEXT("/bin/")))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Return on the first .cs file we encounter so we can reload.
|
|
if (Settings->AutomaticHotReloading != OnScriptSave)
|
|
{
|
|
HotReloadStatus = PendingReload;
|
|
}
|
|
else
|
|
{
|
|
StartHotReload(true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::StartHotReload(bool bRebuild, bool bPromptPlayerWithNewProject)
|
|
{
|
|
if (HotReloadStatus == FailedToUnload)
|
|
{
|
|
// If we failed to unload an assembly, we can't hot reload until the editor is restarted.
|
|
bHotReloadFailed = true;
|
|
UE_LOGFMT(LogUnrealSharpEditor, Error, "Hot reload is disabled until the editor is restarted.");
|
|
return;
|
|
}
|
|
|
|
TArray<FString> AllProjects;
|
|
FCSProcHelper::GetAllProjectPaths(AllProjects);
|
|
|
|
if (AllProjects.IsEmpty())
|
|
{
|
|
if (bPromptPlayerWithNewProject)
|
|
{
|
|
SuggestProjectSetup();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
HotReloadStatus = Active;
|
|
double StartTime = FPlatformTime::Seconds();
|
|
|
|
FScopedSlowTask Progress(3, LOCTEXT("HotReload", "Reloading C#..."));
|
|
Progress.MakeDialog();
|
|
|
|
FString SolutionPath = FCSProcHelper::GetPathToSolution();
|
|
FString OutputPath = FCSProcHelper::GetUserAssemblyDirectory();
|
|
|
|
const UCSUnrealSharpEditorSettings* Settings = GetDefault<UCSUnrealSharpEditorSettings>();
|
|
FString BuildConfiguration = Settings->GetBuildConfigurationString();
|
|
ECSLoggerVerbosity LogVerbosity = Settings->LogVerbosity;
|
|
|
|
FString ExceptionMessage;
|
|
if (!ManagedUnrealSharpEditorCallbacks.Build(*SolutionPath, *OutputPath, *BuildConfiguration, LogVerbosity, &ExceptionMessage, bRebuild))
|
|
{
|
|
HotReloadStatus = Inactive;
|
|
bHotReloadFailed = true;
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(ExceptionMessage), FText::FromString(TEXT("Building C# Project Failed")));
|
|
return;
|
|
}
|
|
|
|
UCSManager& CSharpManager = UCSManager::Get();
|
|
bool bUnloadFailed = false;
|
|
|
|
TArray<FString> ProjectsByLoadOrder;
|
|
FCSProcHelper::GetProjectNamesByLoadOrder(ProjectsByLoadOrder, true);
|
|
|
|
// Unload all assemblies in reverse order to prevent unloading an assembly that is still being referenced.
|
|
// For instance, most assemblies depend on ProjectGlue, so it must be unloaded last.
|
|
// Good info: https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability
|
|
// Note: An assembly is only referenced if any of its types are referenced in code.
|
|
// Otherwise optimized out, so ProjectGlue can be unloaded first if it's not used.
|
|
for (int32 i = ProjectsByLoadOrder.Num() - 1; i >= 0; --i)
|
|
{
|
|
const FString& ProjectName = ProjectsByLoadOrder[i];
|
|
UCSAssembly* Assembly = CSharpManager.FindAssembly(*ProjectName);
|
|
|
|
if (IsValid(Assembly) && !Assembly->UnloadAssembly())
|
|
{
|
|
UE_LOGFMT(LogUnrealSharpEditor, Error, "Failed to unload assembly: {0}", *ProjectName);
|
|
bUnloadFailed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bUnloadFailed)
|
|
{
|
|
HotReloadStatus = FailedToUnload;
|
|
bHotReloadFailed = true;
|
|
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("HotReloadFailure",
|
|
"One or more assemblies failed to unload. Hot reload will be disabled until the editor restarts.\n\n"
|
|
"Possible causes: Strong GC handles, running threads, etc."),
|
|
FText::FromString(TEXT("Hot Reload Failed")));
|
|
|
|
return;
|
|
}
|
|
|
|
// Load all assemblies again in the correct order.
|
|
for (const FString& ProjectName : ProjectsByLoadOrder)
|
|
{
|
|
UCSAssembly* Assembly = CSharpManager.FindAssembly(*ProjectName);
|
|
|
|
if (IsValid(Assembly))
|
|
{
|
|
Assembly->LoadAssembly();
|
|
}
|
|
else
|
|
{
|
|
// If the assembly is not loaded. It's a new project, and we need to load it.
|
|
CSharpManager.LoadUserAssemblyByName(*ProjectName);
|
|
}
|
|
}
|
|
|
|
Progress.EnterProgressFrame(1, LOCTEXT("HotReload", "Refreshing Affected Blueprints..."));
|
|
RefreshAffectedBlueprints();
|
|
|
|
HotReloadStatus = Inactive;
|
|
bHotReloadFailed = false;
|
|
|
|
UE_LOG(LogUnrealSharpEditor, Log, TEXT("Hot reload took %.2f seconds to execute"), FPlatformTime::Seconds() - StartTime);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::InitializeUnrealSharpEditorCallbacks(FCSManagedUnrealSharpEditorCallbacks Callbacks)
|
|
{
|
|
ManagedUnrealSharpEditorCallbacks = Callbacks;
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnCreateNewProject()
|
|
{
|
|
OpenNewProjectDialog();
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnCompileManagedCode()
|
|
{
|
|
Get().StartHotReload();
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnReloadManagedCode()
|
|
{
|
|
Get().StartHotReload(false);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnRegenerateSolution()
|
|
{
|
|
if (!FCSProcHelper::InvokeUnrealSharpBuildTool(BUILD_ACTION_GENERATE_SOLUTION))
|
|
{
|
|
return;
|
|
}
|
|
|
|
OpenSolution();
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnOpenSolution()
|
|
{
|
|
OpenSolution();
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnPackageProject()
|
|
{
|
|
PackageProject();
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnMergeManagedSlnAndNativeSln()
|
|
{
|
|
static FString NativeSolutionPath = FPaths::ProjectDir() / FApp::GetProjectName() + ".sln";
|
|
static FString ManagedSolutionPath = FPaths::ConvertRelativePathToFull(FCSProcHelper::GetPathToSolution());
|
|
|
|
if (!FPaths::FileExists(NativeSolutionPath))
|
|
{
|
|
FString DialogText = FString::Printf(TEXT("Failed to load native solution %s"), *NativeSolutionPath);
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(DialogText));
|
|
return;
|
|
}
|
|
|
|
if (!FPaths::FileExists(ManagedSolutionPath))
|
|
{
|
|
FString DialogText = FString::Printf(TEXT("Failed to load managed solution %s"), *ManagedSolutionPath);
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(DialogText));
|
|
return;
|
|
}
|
|
|
|
TArray<FString> NativeSlnFileLines;
|
|
FFileHelper::LoadFileToStringArray(NativeSlnFileLines, *NativeSolutionPath);
|
|
|
|
int32 LastEndProjectIdx = 0;
|
|
|
|
for (int32 idx = 0; idx < NativeSlnFileLines.Num(); ++idx)
|
|
{
|
|
FString Line = NativeSlnFileLines[idx];
|
|
Line.ReplaceInline(TEXT("\n"), TEXT(""));
|
|
if (Line == TEXT("EndProject"))
|
|
{
|
|
LastEndProjectIdx = idx;
|
|
}
|
|
}
|
|
|
|
TArray<FString> ManagedSlnFileLines;
|
|
FFileHelper::LoadFileToStringArray(ManagedSlnFileLines, *ManagedSolutionPath);
|
|
|
|
TArray<FString> ManagedProjectLines;
|
|
|
|
for (int32 idx = 0; idx < ManagedSlnFileLines.Num(); ++idx)
|
|
{
|
|
FString Line = ManagedSlnFileLines[idx];
|
|
Line.ReplaceInline(TEXT("\n"), TEXT(""));
|
|
if (Line.StartsWith(TEXT("Project(\"{")) || Line.StartsWith(TEXT("EndProject")))
|
|
{
|
|
ManagedProjectLines.Add(Line);
|
|
}
|
|
}
|
|
|
|
for (int32 idx = 0; idx < ManagedProjectLines.Num(); ++idx)
|
|
{
|
|
FString Line = ManagedProjectLines[idx];
|
|
if (Line.StartsWith(TEXT("Project(\"{")) && Line.Contains(TEXT(".csproj")))
|
|
{
|
|
TArray<FString> ProjectStrParts;
|
|
Line.ParseIntoArray(ProjectStrParts, TEXT(", "));
|
|
if(ProjectStrParts.Num() == 3 && ProjectStrParts[1].Contains(TEXT(".csproj")))
|
|
{
|
|
ProjectStrParts[1] = FString("\"Script\\") + ProjectStrParts[1].Mid(1);
|
|
Line = FString::Join(ProjectStrParts, TEXT(", "));
|
|
}
|
|
}
|
|
NativeSlnFileLines.Insert(Line, LastEndProjectIdx + 1 + idx);
|
|
}
|
|
|
|
FString MixedSlnPath = NativeSolutionPath.LeftChop(4) + FString(".Mixed.sln");
|
|
FFileHelper::SaveStringArrayToFile(NativeSlnFileLines, *MixedSlnPath);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnOpenSettings()
|
|
{
|
|
FModuleManager::LoadModuleChecked<ISettingsModule>("Settings").ShowViewer(
|
|
"Editor", "General", "CSUnrealSharpEditorSettings");
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnOpenDocumentation()
|
|
{
|
|
FPlatformProcess::LaunchURL(TEXT("https://www.unrealsharp.com"), nullptr, nullptr);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnReportBug()
|
|
{
|
|
FPlatformProcess::LaunchURL(TEXT("https://github.com/UnrealSharp/UnrealSharp/issues"), nullptr, nullptr);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnRefreshRuntimeGlue()
|
|
{
|
|
FUnrealSharpRuntimeGlueModule& RuntimeGlueModule = FModuleManager::LoadModuleChecked<FUnrealSharpRuntimeGlueModule>(
|
|
"UnrealSharpRuntimeGlue");
|
|
RuntimeGlueModule.ForceRefreshRuntimeGlue();
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::RepairComponents()
|
|
{
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(
|
|
AssetRegistryConstants::ModuleName);
|
|
AssetRegistryModule.Get().SearchAllAssets(/*bSynchronousSearch =*/true);
|
|
|
|
TArray<FAssetData> OutAssetData;
|
|
AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), OutAssetData, true);
|
|
|
|
FScopedSlowTask Progress(OutAssetData.Num());
|
|
Progress.MakeDialog();
|
|
|
|
USubobjectDataSubsystem* SubobjectDataSubsystem = GEngine->GetEngineSubsystem<USubobjectDataSubsystem>();
|
|
|
|
for (FAssetData const& Asset : OutAssetData)
|
|
{
|
|
const FString AssetPath = Asset.GetObjectPathString();
|
|
|
|
if (!AssetPath.Contains(TEXT("/Game/")))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UBlueprint* LoadedBlueprint = Cast<
|
|
UBlueprint>(StaticLoadObject(Asset.GetClass(), nullptr, *AssetPath, nullptr));
|
|
UClass* GeneratedClass = LoadedBlueprint->GeneratedClass;
|
|
UCSClass* ManagedClass = FCSClassUtilities::GetFirstManagedClass(GeneratedClass);
|
|
|
|
if (!ManagedClass)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Progress.EnterProgressFrame(1, FText::FromString(FString::Printf(TEXT("Fixing up Blueprint: %s"), *AssetPath)));
|
|
|
|
AActor* ActorCDO = Cast<AActor>(GeneratedClass->GetDefaultObject(false));
|
|
if (!ActorCDO)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TArray<FSubobjectDataHandle> SubobjectData;
|
|
SubobjectDataSubsystem->K2_GatherSubobjectDataForBlueprint(LoadedBlueprint, SubobjectData);
|
|
|
|
UInheritableComponentHandler* InheritableComponentHandler = LoadedBlueprint->
|
|
GetInheritableComponentHandler(false);
|
|
|
|
if (!InheritableComponentHandler)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TArray<UObject*> Subobjects;
|
|
ActorCDO->GetDefaultSubobjects(Subobjects);
|
|
|
|
TArray<UObject*> MatchingInstances;
|
|
GetObjectsOfClass(LoadedBlueprint->GeneratedClass, MatchingInstances, true, RF_ClassDefaultObject,
|
|
EInternalObjectFlags::Garbage);
|
|
|
|
for (TFieldIterator<FObjectProperty> PropertyIt(ManagedClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++
|
|
PropertyIt)
|
|
{
|
|
FObjectProperty* Property = *PropertyIt;
|
|
|
|
if (!FCSClassUtilities::IsManagedClass(Property->GetOwnerClass()))
|
|
{
|
|
break;
|
|
}
|
|
|
|
UActorComponent* OldComponentArchetype = Cast<UActorComponent>(
|
|
Property->GetObjectPropertyValue_InContainer(ActorCDO));
|
|
|
|
if (!OldComponentArchetype || !Subobjects.Contains(OldComponentArchetype))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Property->SetObjectPropertyValue_InContainer(ActorCDO, nullptr);
|
|
|
|
FComponentKey ComponentKey = InheritableComponentHandler->FindKey(OldComponentArchetype->GetFName());
|
|
|
|
if (!ComponentKey.IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UActorComponent* NewArchetype = InheritableComponentHandler->GetOverridenComponentTemplate(ComponentKey);
|
|
CopyProperties(OldComponentArchetype, NewArchetype);
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(LoadedBlueprint, Property);
|
|
|
|
for (UObject* Instance : MatchingInstances)
|
|
{
|
|
AActor* ActorInstance = static_cast<AActor*>(Instance);
|
|
TArray<TObjectPtr<UActorComponent>>& Components = ActorInstance->BlueprintCreatedComponents;
|
|
|
|
for (TObjectPtr<UActorComponent>& Component : Components)
|
|
{
|
|
if (Component->GetName() == OldComponentArchetype->GetName())
|
|
{
|
|
CopyProperties(OldComponentArchetype, Component);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UBlueprintEditorLibrary::CompileBlueprint(LoadedBlueprint);
|
|
}
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::CopyProperties(UActorComponent* Source, UActorComponent* Target)
|
|
{
|
|
UClass* SourceClass = Source->GetClass();
|
|
UClass* TargetClass = Target->GetClass();
|
|
|
|
if (SourceClass != TargetClass)
|
|
{
|
|
UE_LOG(LogUnrealSharpEditor, Error, TEXT("Source and Target classes are not the same."));
|
|
return;
|
|
}
|
|
|
|
for (TFieldIterator<FProperty> PropertyIt(SourceClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
|
|
{
|
|
FProperty* Property = *PropertyIt;
|
|
|
|
if (!Property->HasAnyPropertyFlags(CPF_BlueprintVisible | CPF_Edit))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FString Data;
|
|
Property->ExportTextItem_InContainer(Data, Source, nullptr, nullptr, PPF_None);
|
|
Property->ImportText_InContainer(*Data, Target, Target, 0);
|
|
}
|
|
|
|
Target->PostLoad();
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnRepairComponents()
|
|
{
|
|
RepairComponents();
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnExploreArchiveDirectory(FString ArchiveDirectory)
|
|
{
|
|
FPlatformProcess::ExploreFolder(*ArchiveDirectory);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::PackageProject()
|
|
{
|
|
FString ArchiveDirectory = SelectArchiveDirectory();
|
|
|
|
if (ArchiveDirectory.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FString ExecutablePath = ArchiveDirectory / FApp::GetProjectName() + ".exe";
|
|
if (!FPaths::FileExists(ExecutablePath))
|
|
{
|
|
FString DialogText = FString::Printf(
|
|
TEXT(
|
|
"The executable for project '%s' could not be found in the directory: %s. Please select the root directory where you packaged your game."),
|
|
FApp::GetProjectName(), *ArchiveDirectory);
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(DialogText));
|
|
return;
|
|
}
|
|
|
|
FScopedSlowTask Progress(1, LOCTEXT("USharpPackaging", "Packaging Project..."));
|
|
Progress.MakeDialog();
|
|
|
|
TMap<FString, FString> Arguments;
|
|
Arguments.Add("ArchiveDirectory", FCSUnrealSharpUtils::MakeQuotedPath(ArchiveDirectory));
|
|
Arguments.Add("BuildConfig", "Release");
|
|
FCSProcHelper::InvokeUnrealSharpBuildTool(BUILD_ACTION_PACKAGE_PROJECT, Arguments);
|
|
|
|
FNotificationInfo Info(
|
|
FText::FromString(
|
|
FString::Printf(TEXT("Project '%s' has been packaged successfully."), FApp::GetProjectName())));
|
|
Info.ExpireDuration = 15.0f;
|
|
Info.bFireAndForget = true;
|
|
Info.ButtonDetails.Add(FNotificationButtonInfo(
|
|
LOCTEXT("USharpRunPackagedGame", "Run Packaged Game"),
|
|
LOCTEXT("", ""),
|
|
FSimpleDelegate::CreateStatic(&FUnrealSharpEditorModule::RunGame, ExecutablePath),
|
|
SNotificationItem::CS_None));
|
|
|
|
Info.ButtonDetails.Add(FNotificationButtonInfo(
|
|
LOCTEXT("USharpOpenPackagedGame", "Open Folder"),
|
|
LOCTEXT("", ""),
|
|
FSimpleDelegate::CreateStatic(&FUnrealSharpEditorModule::OnExploreArchiveDirectory, ArchiveDirectory),
|
|
SNotificationItem::CS_None));
|
|
|
|
TSharedPtr<SNotificationItem> NotificationItem = FSlateNotificationManager::Get().AddNotification(Info);
|
|
NotificationItem->SetCompletionState(SNotificationItem::CS_None);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::RunGame(FString ExecutablePath)
|
|
{
|
|
FString OpenSolutionArgs = FString::Printf(TEXT("/c \"%s\""), *ExecutablePath);
|
|
FPlatformProcess::ExecProcess(TEXT("cmd.exe"), *OpenSolutionArgs, nullptr, nullptr, nullptr);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OpenSolution()
|
|
{
|
|
FString SolutionPath = FPaths::ConvertRelativePathToFull(FCSProcHelper::GetPathToSolution());
|
|
|
|
if (!FPaths::FileExists(SolutionPath))
|
|
{
|
|
OnRegenerateSolution();
|
|
}
|
|
|
|
FString ExceptionMessage;
|
|
if (!ManagedUnrealSharpEditorCallbacks.OpenSolution(*SolutionPath, &ExceptionMessage))
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(ExceptionMessage), FText::FromString(TEXT("Opening C# Project Failed")));
|
|
return;
|
|
}
|
|
};
|
|
|
|
FString FUnrealSharpEditorModule::SelectArchiveDirectory()
|
|
{
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
if (!DesktopPlatform)
|
|
{
|
|
return FString();
|
|
}
|
|
|
|
FString DestinationFolder;
|
|
const void* ParentWindowHandle = FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr);
|
|
const FString Title = LOCTEXT("USharpChooseArchiveRoot", "Find Archive Root").ToString();
|
|
|
|
if (DesktopPlatform->OpenDirectoryDialog(ParentWindowHandle, Title, FString(), DestinationFolder))
|
|
{
|
|
return FPaths::ConvertRelativePathToFull(DestinationFolder);
|
|
}
|
|
|
|
return FString();
|
|
}
|
|
|
|
TSharedRef<SWidget> FUnrealSharpEditorModule::GenerateUnrealSharpMenu()
|
|
{
|
|
const FCSUnrealSharpEditorCommands& CSCommands = FCSUnrealSharpEditorCommands::Get();
|
|
FMenuBuilder MenuBuilder(true, UnrealSharpCommands);
|
|
|
|
// Build
|
|
MenuBuilder.BeginSection("Build", LOCTEXT("Build", "Build"));
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.CompileManagedCode, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "LevelEditor.Recompile"));
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.ReloadManagedCode, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "LevelEditor.Recompile"));
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
// Project
|
|
MenuBuilder.BeginSection("Project", LOCTEXT("Project", "Project"));
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.CreateNewProject, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSourceCodeNavigation::GetOpenSourceCodeIDEIcon());
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.OpenSolution, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSourceCodeNavigation::GetOpenSourceCodeIDEIcon());
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.RegenerateSolution, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSourceCodeNavigation::GetOpenSourceCodeIDEIcon());
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.MergeManagedSlnAndNativeSln, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSourceCodeNavigation::GetOpenSourceCodeIDEIcon());
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
// Package
|
|
MenuBuilder.BeginSection("Package", LOCTEXT("Package", "Package"));
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.PackageProject, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "LevelEditor.Recompile"));
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
// Plugin
|
|
MenuBuilder.BeginSection("Plugin", LOCTEXT("Plugin", "Plugin"));
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.OpenSettings, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "EditorPreferences.TabIcon"));
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.OpenDocumentation, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "MainFrame.DocumentationHome"));
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.ReportBug, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "MainFrame.ReportABug"));
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.BeginSection("Glue", LOCTEXT("Glue", "Glue"));
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.RefreshRuntimeGlue, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "SourceControl.Actions.Refresh"));
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.BeginSection("Tools", LOCTEXT("Tools", "Tools"));
|
|
|
|
MenuBuilder.AddMenuEntry(CSCommands.RepairComponents, NAME_None, TAttribute<FText>(), TAttribute<FText>(),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "SourceControl.Actions.Refresh"));
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OpenNewProjectDialog()
|
|
{
|
|
TSharedRef<SWindow> AddCodeWindow = SNew(SWindow)
|
|
.Title(LOCTEXT("CreateNewProject", "New C# Project"))
|
|
.SizingRule(ESizingRule::Autosized)
|
|
.SupportsMinimize(false);
|
|
|
|
TSharedRef<SCSNewProjectDialog> NewProjectDialog = SNew(SCSNewProjectDialog);
|
|
AddCodeWindow->SetContent(NewProjectDialog);
|
|
|
|
FSlateApplication::Get().AddWindow(AddCodeWindow);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::SuggestProjectSetup()
|
|
{
|
|
FString DialogText = TEXT("No C# projects were found. Would you like to create a new C# project?");
|
|
EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(DialogText));
|
|
|
|
if (Result == EAppReturnType::No)
|
|
{
|
|
return;
|
|
}
|
|
|
|
OpenNewProjectDialog();
|
|
}
|
|
|
|
bool FUnrealSharpEditorModule::Tick(float DeltaTime)
|
|
{
|
|
const UCSUnrealSharpEditorSettings* Settings = GetDefault<UCSUnrealSharpEditorSettings>();
|
|
if (Settings->AutomaticHotReloading == OnEditorFocus && !IsHotReloading() && HasPendingHotReloadChanges() &&
|
|
FApp::HasFocus())
|
|
{
|
|
StartHotReload();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::RegisterCommands()
|
|
{
|
|
FCSUnrealSharpEditorCommands::Register();
|
|
UnrealSharpCommands = MakeShareable(new FUICommandList);
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().CreateNewProject,
|
|
FExecuteAction::CreateStatic(&FUnrealSharpEditorModule::OnCreateNewProject));
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().CompileManagedCode,
|
|
FExecuteAction::CreateStatic(&FUnrealSharpEditorModule::OnCompileManagedCode));
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().ReloadManagedCode,
|
|
FExecuteAction::CreateStatic(&FUnrealSharpEditorModule::OnReloadManagedCode));
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().RegenerateSolution,
|
|
FExecuteAction::CreateRaw(this, &FUnrealSharpEditorModule::OnRegenerateSolution));
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().OpenSolution,
|
|
FExecuteAction::CreateRaw(this, &FUnrealSharpEditorModule::OnOpenSolution));
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().MergeManagedSlnAndNativeSln,
|
|
FExecuteAction::CreateStatic(&FUnrealSharpEditorModule::OnMergeManagedSlnAndNativeSln));
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().PackageProject,
|
|
FExecuteAction::CreateStatic(&FUnrealSharpEditorModule::OnPackageProject));
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().OpenSettings,
|
|
FExecuteAction::CreateStatic(&FUnrealSharpEditorModule::OnOpenSettings));
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().OpenDocumentation,
|
|
FExecuteAction::CreateStatic(&FUnrealSharpEditorModule::OnOpenDocumentation));
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().ReportBug,
|
|
FExecuteAction::CreateStatic(&FUnrealSharpEditorModule::OnReportBug));
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().RefreshRuntimeGlue,
|
|
FExecuteAction::CreateStatic(&FUnrealSharpEditorModule::OnRefreshRuntimeGlue));
|
|
UnrealSharpCommands->MapAction(FCSUnrealSharpEditorCommands::Get().RepairComponents,
|
|
FExecuteAction::CreateStatic(&FUnrealSharpEditorModule::OnRepairComponents));
|
|
|
|
const FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
const TSharedRef<FUICommandList> Commands = LevelEditorModule.GetGlobalLevelEditorActions();
|
|
Commands->Append(UnrealSharpCommands.ToSharedRef());
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::RegisterMenu()
|
|
{
|
|
UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar");
|
|
FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("PluginTools");
|
|
|
|
FToolMenuEntry Entry = FToolMenuEntry::InitComboButton(
|
|
"UnrealSharp",
|
|
FUIAction(),
|
|
FOnGetContent::CreateLambda([this]() { return GenerateUnrealSharpMenu(); }),
|
|
LOCTEXT("UnrealSharp_Label", "UnrealSharp"),
|
|
LOCTEXT("UnrealSharp_Tooltip", "List of all UnrealSharp actions"),
|
|
TAttribute<FSlateIcon>::CreateLambda([this]()
|
|
{
|
|
return GetMenuIcon();
|
|
}));
|
|
|
|
Section.AddEntry(Entry);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::RegisterPluginTemplates()
|
|
{
|
|
IPluginBrowser& PluginBrowser = IPluginBrowser::Get();
|
|
const FString PluginBaseDir = FPaths::ConvertRelativePathToFull(IPluginManager::Get().FindPlugin(UE_PLUGIN_NAME)->GetBaseDir());
|
|
|
|
const FText BlankTemplateName = LOCTEXT("UnrealSharp_BlankLabel", "C++/C# Joint");
|
|
const FText CSharpOnlyTemplateName = LOCTEXT("UnrealSharp_CSharpOnlyLabel", "C# Only");
|
|
|
|
const FText BlankDescription = LOCTEXT("UnrealSharp_BlankTemplateDesc", "Create a blank plugin with a minimal amount of C++ and C# code.");
|
|
const FText CSharpOnlyDescription = LOCTEXT("UnrealSharp_CSharpOnlyTemplateDesc", "Create a blank plugin that can only contain content and C# scripts.");
|
|
|
|
const TSharedRef<FPluginTemplateDescription> BlankTemplate = MakeShared<FCSPluginTemplateDescription>(BlankTemplateName, BlankDescription,
|
|
PluginBaseDir / TEXT("Templates") / TEXT("Blank"), true, EHostType::Runtime, ELoadingPhase::Default, true);
|
|
|
|
const TSharedRef<FPluginTemplateDescription> CSharpOnlyTemplate = MakeShared<FCSPluginTemplateDescription>(CSharpOnlyTemplateName, CSharpOnlyDescription,
|
|
PluginBaseDir / TEXT("Templates") / TEXT("CSharpOnly"), true, EHostType::Runtime, ELoadingPhase::Default, false);
|
|
|
|
PluginBrowser.RegisterPluginTemplate(BlankTemplate);
|
|
PluginBrowser.RegisterPluginTemplate(CSharpOnlyTemplate);
|
|
|
|
PluginTemplates.Add(BlankTemplate);
|
|
PluginTemplates.Add(CSharpOnlyTemplate);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::UnregisterPluginTemplates()
|
|
{
|
|
IPluginBrowser& PluginBrowser = IPluginBrowser::Get();
|
|
for (const TSharedRef<FPluginTemplateDescription>& Template : PluginTemplates)
|
|
{
|
|
PluginBrowser.UnregisterPluginTemplate(Template);
|
|
}
|
|
}
|
|
|
|
|
|
void FUnrealSharpEditorModule::OnPIEShutdown(bool IsSimulating)
|
|
{
|
|
// Replicate UE behavior, which forces a garbage collection when exiting PIE.
|
|
ManagedUnrealSharpEditorCallbacks.ForceManagedGC();
|
|
|
|
if (bHasQueuedHotReload)
|
|
{
|
|
bHasQueuedHotReload = false;
|
|
StartHotReload();
|
|
}
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::AddNewProject(const FString& ModuleName, const FString& ProjectParentFolder, const FString& ProjectRoot, const TMap<FString, FString>& ExtraArguments)
|
|
{
|
|
TMap<FString, FString> Arguments = ExtraArguments;
|
|
|
|
TMap<FString, FString> SolutionArguments;
|
|
SolutionArguments.Add(TEXT("MODULENAME"), ModuleName);
|
|
|
|
FString ProjectFolder = FPaths::Combine(ProjectParentFolder, ModuleName);
|
|
FString ModuleFilePath = FPaths::Combine(ProjectFolder, ModuleName + ".cs");
|
|
|
|
FillTemplateFile(TEXT("Module"), SolutionArguments, ModuleFilePath);
|
|
|
|
Arguments.Add(TEXT("NewProjectName"), ModuleName);
|
|
Arguments.Add(TEXT("NewProjectFolder"), FCSUnrealSharpUtils::MakeQuotedPath(FPaths::ConvertRelativePathToFull(ProjectParentFolder)));
|
|
|
|
FString FullProjectRoot = FPaths::ConvertRelativePathToFull(ProjectRoot);
|
|
Arguments.Add(TEXT("ProjectRoot"), FCSUnrealSharpUtils::MakeQuotedPath(FullProjectRoot));
|
|
|
|
if (!FCSProcHelper::InvokeUnrealSharpBuildTool(BUILD_ACTION_GENERATE_PROJECT, Arguments))
|
|
{
|
|
UE_LOGFMT(LogUnrealSharpEditor, Error, "Failed to generate project %s in %s", *ModuleName, *ProjectParentFolder);
|
|
return;
|
|
}
|
|
|
|
OpenSolution();
|
|
AddDirectoryToWatch(FPaths::Combine(FullProjectRoot, TEXT("Script")));
|
|
|
|
FString CsProjPath = FPaths::Combine(ProjectFolder, ModuleName + ".csproj");
|
|
|
|
if (!FPaths::FileExists(CsProjPath))
|
|
{
|
|
UE_LOGFMT(LogUnrealSharpEditor, Error, "Failed to find .csproj %s in %s", *ModuleName, *ProjectParentFolder);
|
|
return;
|
|
}
|
|
|
|
GetManagedUnrealSharpEditorCallbacks().AddProjectToCollection(*CsProjPath);
|
|
}
|
|
|
|
bool FUnrealSharpEditorModule::FillTemplateFile(const FString& TemplateName, TMap<FString, FString>& Replacements, const FString& Path)
|
|
{
|
|
const FString FullFileName = FCSProcHelper::GetPluginDirectory() / TEXT("Templates") / TemplateName + TEXT(".cs.template");
|
|
|
|
FString OutTemplate;
|
|
if (!FFileHelper::LoadFileToString(OutTemplate, *FullFileName))
|
|
{
|
|
UE_LOG(LogUnrealSharpEditor, Error, TEXT("Failed to load template file %s"), *FullFileName);
|
|
return false;
|
|
}
|
|
|
|
for (const TPair<FString, FString>& Replacement : Replacements)
|
|
{
|
|
FString ReplacementKey = TEXT("%") + Replacement.Key + TEXT("%");
|
|
OutTemplate = OutTemplate.Replace(*ReplacementKey, *Replacement.Value);
|
|
}
|
|
|
|
if (!FFileHelper::SaveStringToFile(OutTemplate, *Path))
|
|
{
|
|
UE_LOG(LogUnrealSharpEditor, Error, TEXT("Failed to save %s when trying to create a template"), *Path);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnStructRebuilt(UCSScriptStruct* NewStruct)
|
|
{
|
|
RebuiltStructs.Add(NewStruct);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnClassRebuilt(UCSClass* NewClass)
|
|
{
|
|
RebuiltClasses.Add(NewClass);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::OnEnumRebuilt(UCSEnum* NewEnum)
|
|
{
|
|
RebuiltEnums.Add(NewEnum);
|
|
}
|
|
|
|
bool FUnrealSharpEditorModule::IsPinAffectedByReload(const FEdGraphPinType& PinType) const
|
|
{
|
|
UObject* PinSubCategoryObject = PinType.PinSubCategoryObject.Get();
|
|
if (!IsValid(PinSubCategoryObject) || !Manager->IsManagedType(PinSubCategoryObject))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto IsPinTypeRebuilt = [this](UObject* PinSubCategoryObject) -> bool
|
|
{
|
|
if (UCSClass* Class = Cast<UCSClass>(PinSubCategoryObject))
|
|
{
|
|
return RebuiltClasses.Contains(Class);
|
|
}
|
|
|
|
if (UCSEnum* Enum = Cast<UCSEnum>(PinSubCategoryObject))
|
|
{
|
|
return RebuiltEnums.Contains(Enum);
|
|
}
|
|
|
|
if (UCSScriptStruct* Struct = Cast<UCSScriptStruct>(PinSubCategoryObject))
|
|
{
|
|
return RebuiltStructs.Contains(Struct);
|
|
}
|
|
|
|
if (UCSEnum* Enum = Cast<UCSEnum>(PinSubCategoryObject))
|
|
{
|
|
return RebuiltEnums.Contains(Enum);
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
if (!IsPinTypeRebuilt(PinSubCategoryObject))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (PinType.IsMap() && PinType.PinValueType.TerminalSubCategoryObject.IsValid())
|
|
{
|
|
UObject* MapValueType = PinType.PinValueType.TerminalSubCategoryObject.Get();
|
|
if (IsValid(MapValueType) && Manager->IsManagedType(MapValueType))
|
|
{
|
|
return IsPinTypeRebuilt(MapValueType);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FUnrealSharpEditorModule::IsNodeAffectedByReload(UEdGraphNode* Node) const
|
|
{
|
|
if (UK2Node_EditablePinBase* EditableNode = Cast<UK2Node_EditablePinBase>(Node))
|
|
{
|
|
for (const TSharedPtr<FUserPinInfo>& Pin : EditableNode->UserDefinedPins)
|
|
{
|
|
if (IsPinAffectedByReload(Pin->PinType))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
for (UEdGraphPin* Pin : Node->Pins)
|
|
{
|
|
if (IsPinAffectedByReload(Pin->PinType))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::AddDirectoryToWatch(const FString& Directory)
|
|
{
|
|
if (WatchingDirectories.Contains(Directory))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!FPaths::DirectoryExists(Directory))
|
|
{
|
|
FPlatformFileManager::Get().GetPlatformFile().CreateDirectory(*Directory);
|
|
}
|
|
|
|
FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked<FDirectoryWatcherModule>("DirectoryWatcher");
|
|
|
|
FDelegateHandle Handle;
|
|
DirectoryWatcherModule.Get()->RegisterDirectoryChangedCallback_Handle(
|
|
Directory,
|
|
IDirectoryWatcher::FDirectoryChanged::CreateRaw(this, &FUnrealSharpEditorModule::OnCSharpCodeModified),
|
|
Handle);
|
|
|
|
WatchingDirectories.Add(Directory);
|
|
}
|
|
|
|
void FUnrealSharpEditorModule::RefreshAffectedBlueprints()
|
|
{
|
|
if (RebuiltStructs.IsEmpty() && RebuiltClasses.IsEmpty() && RebuiltEnums.IsEmpty())
|
|
{
|
|
// Early out if nothing has changed its structure.
|
|
return;
|
|
}
|
|
|
|
TArray<UBlueprint*> AffectedBlueprints;
|
|
for (TObjectIterator<UBlueprint> BlueprintIt; BlueprintIt; ++BlueprintIt)
|
|
{
|
|
UBlueprint* Blueprint = *BlueprintIt;
|
|
if (!IsValid(Blueprint->GeneratedClass) || FCSClassUtilities::IsManagedClass(Blueprint->GeneratedClass))
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<UK2Node*> AllNodes;
|
|
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node>(Blueprint, AllNodes);
|
|
|
|
for (UK2Node* Node : AllNodes)
|
|
{
|
|
if (IsNodeAffectedByReload(Node))
|
|
{
|
|
Node->ReconstructNode();
|
|
}
|
|
}
|
|
|
|
AffectedBlueprints.Add(Blueprint);
|
|
}
|
|
|
|
for (UBlueprint* Blueprint : AffectedBlueprints)
|
|
{
|
|
FKismetEditorUtilities::CompileBlueprint(Blueprint, EBlueprintCompileOptions::SkipGarbageCollection);
|
|
}
|
|
|
|
RebuiltStructs.Reset();
|
|
RebuiltClasses.Reset();
|
|
RebuiltEnums.Reset();
|
|
|
|
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
|
|
}
|
|
|
|
FSlateIcon FUnrealSharpEditorModule::GetMenuIcon() const
|
|
{
|
|
if (HasHotReloadFailed())
|
|
{
|
|
return FSlateIcon(FCSStyle::GetStyleSetName(), "UnrealSharp.Toolbar.Fail");
|
|
}
|
|
if (HasPendingHotReloadChanges())
|
|
{
|
|
return FSlateIcon(FCSStyle::GetStyleSetName(), "UnrealSharp.Toolbar.Modified");
|
|
}
|
|
|
|
return FSlateIcon(FCSStyle::GetStyleSetName(), "UnrealSharp.Toolbar");
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|
|
IMPLEMENT_MODULE(FUnrealSharpEditorModule, UnrealSharpEditor)
|