├── .gitignore ├── Doc ├── Pic │ └── easyeditor.gif └── extension.md ├── Resources └── Icon128.png ├── EasyEditorPlugin.uplugin ├── LICENSE ├── Source └── EasyEditorPlugin │ ├── Public │ └── EasyEditorPlugin.h │ ├── EasyEditorPlugin.Build.cs │ └── Private │ └── EasyEditorPlugin.cpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Binaries/ 2 | Intermediate/ 3 | -------------------------------------------------------------------------------- /Doc/Pic/easyeditor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puerts/EasyEditorPlugin/HEAD/Doc/Pic/easyeditor.gif -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puerts/EasyEditorPlugin/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /EasyEditorPlugin.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "EasyEditorPlugin", 6 | "Description": "", 7 | "Category": "Other", 8 | "CreatedBy": "johnche", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "EasyEditorPlugin", 20 | "Type": "Editor", 21 | "LoadingPhase": "Default" 22 | } 23 | ], 24 | "Plugins": [ 25 | { 26 | "Name": "Puerts", 27 | "Enabled": true 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 puerts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/EasyEditorPlugin/Public/EasyEditorPlugin.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Modules/ModuleManager.h" 5 | #include "LevelEditor.h" 6 | #include "JsEnv.h" 7 | #include "SourceFileWatcher.h" 8 | #include 9 | 10 | class FEasyEditorPluginModule : public IModuleInterface 11 | { 12 | public: 13 | 14 | /** IModuleInterface implementation */ 15 | virtual void StartupModule() override; 16 | virtual void ShutdownModule() override; 17 | 18 | std::function OnJsEnvPreReload; 19 | 20 | std::function Eval; 21 | 22 | TSparseArray> TsConsoleCommands; 23 | 24 | FSimpleMulticastDelegate OnJsEnvCleanup; 25 | 26 | private: 27 | TSharedPtr JsEnv; 28 | TSharedPtr SourceFileWatcher; 29 | 30 | TUniquePtr ConsoleCommand; 31 | 32 | void OnPostEngineInit(); 33 | 34 | void InitJsEnv(); 35 | 36 | void UnInitJsEnv(); 37 | 38 | bool Tick(float); 39 | 40 | bool StartupScriptCalled = false; 41 | 42 | void HandleMapChanged(UWorld* InWorld, EMapChangeType InMapChangeType); 43 | }; 44 | -------------------------------------------------------------------------------- /Doc/extension.md: -------------------------------------------------------------------------------- 1 | # 第三方库调用 2 | 3 | 主要是两步,以[UEImgui](https://github.com/ZhuRong-HomoStation/UEImgui)为例说明。 4 | 5 | 这里的做法是新建了另外一个plugin对UEImgui进行封装。该新建的plugin是[EasyEditor_ImGui](https://github.com/puerts/EasyEditor_ImGui)。 6 | 7 | ## C++ api绑定声明 8 | 9 | puerts对于UE能反射调用的api可以直接使用(标注了UCLASS,UPPROPERTY,UFUNCTION,USTRUCT,UENUM的C++类,以及所有的蓝图)。这已经能满足不少的编辑器扩展需求。 10 | 11 | 但UEImgui还有大量的普通C/C++ api,这时可以用puerts模板绑定功能,对需要在ts中调用的api用puerts的声明语法简单声明一下即可。 12 | 13 | 对UEImgui的绑定声明在这两个文件:[DearImGuiBinding.cpp](https://github.com/puerts/EasyEditor_ImGui/blob/master/Source/EasyEditor_ImGui/Private/DearImGuiBinding.cpp)、[UEImGuiBinding.cpp](https://github.com/puerts/EasyEditor_ImGui/blob/master/Source/EasyEditor_ImGui/Private/UEImGuiBinding.cpp) 14 | 15 | ## 虚拟机关闭清理 16 | 17 | 如果你有些工作需要在虚拟机关闭前清理一下,可以注册到FEasyEditorPluginModule::OnJsEnvCleanup 18 | 19 | 比如EasyEditor_ImGui就注册了清理回调,见[EasyEditor_ImGui.cpp](https://github.com/puerts/EasyEditor_ImGui/blob/master/Source/EasyEditor_ImGui/Private/EasyEditor_ImGui.cpp) 20 | 21 | ~~~c++ 22 | FModuleManager::LoadModuleChecked("EasyEditorPlugin").OnJsEnvCleanup.AddLambda([]() 23 | { 24 | UEasyEditorDetailCustomization* CDO = Cast(UEasyEditorDetailCustomization::StaticClass()->GetDefaultObject(false)); 25 | if (CDO) 26 | { 27 | CDO->ClearOnDetailDraw(); 28 | } 29 | }); 30 | ~~~ 31 | 32 | -------------------------------------------------------------------------------- /Source/EasyEditorPlugin/EasyEditorPlugin.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class EasyEditorPlugin : ModuleRules 6 | { 7 | public EasyEditorPlugin(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | #if UE_5_3_OR_LATER 10 | PCHUsage = PCHUsageMode.NoPCHs; 11 | #else 12 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 13 | #endif 14 | 15 | PublicIncludePaths.AddRange( 16 | new string[] { 17 | // ... add public include paths required here ... 18 | } 19 | ); 20 | 21 | 22 | PrivateIncludePaths.AddRange( 23 | new string[] { 24 | // ... add other private include paths required here ... 25 | } 26 | ); 27 | 28 | 29 | PublicDependencyModuleNames.AddRange( 30 | new string[] 31 | { 32 | "Core", 33 | // ... add other public dependencies that you statically link with here ... 34 | } 35 | ); 36 | 37 | 38 | PrivateDependencyModuleNames.AddRange( 39 | new string[] 40 | { 41 | "CoreUObject", 42 | "Engine", 43 | "Slate", 44 | "SlateCore", 45 | "ToolMenus", 46 | "UnrealEd", 47 | "ContentBrowserData", 48 | "JsEnv" 49 | } 50 | ); 51 | 52 | 53 | DynamicallyLoadedModuleNames.AddRange( 54 | new string[] 55 | { 56 | // ... add any modules that your module loads dynamically here ... 57 | } 58 | ); 59 | 60 | bEnableUndefinedIdentifierWarnings = false; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyEditorPlugin 2 | 3 | ## EasyEditorPlugin是什么 4 | 5 | EasyEditorPlugin是一个UE下快速开发编辑器扩展的插件,其特点: 6 | 7 | * 1、几行代码即可添加主菜单、工具栏按钮、上下文菜单、Detail扩展等等; 8 | 9 | * 2、运行时脚本热刷新; 10 | 11 | * 3、代码即是UI,结合2可实现UI热刷新;[可选] 12 | 13 | * 4、nodejs + npm海量扩展可用 14 | 15 | * 5、脚本可调用任意UE API,可调用几乎所有c++库 16 | 17 | ## 效果图 18 | 19 | ![easyeditor](Doc/Pic/easyeditor.gif) 20 | 21 | ## demo工程 22 | 23 | [EasyEditorPluginDemo](https://github.com/puerts/EasyEditorPluginDemo) 24 | 25 | ## 代码示例 26 | 27 | ### 添加主菜单 28 | 29 | ~~~typescript 30 | const main_menu = menus.FindMenu("LevelEditor.MainMenu"); 31 | const script_menu = main_menu.AddSubMenuScript(main_menu.GetName(), "Puerts", "Puerts", "Puerts", "Puerts Demo..."); 32 | 33 | const entry = UE.ToolMenuEntry.InitMenuEntry("ShowDemoWindow", "ShowDemoWindow", "just a test...", () => { 34 | let demo = new DemoWindow(); 35 | cpp.UEImGui.AddGlobalWindow(demo.Render.bind(demo)); 36 | }) 37 | script_menu.AddMenuEntry("Scripts", entry); 38 | ~~~ 39 | 40 | ### 添加工具栏按钮 41 | 42 | ~~~typescript 43 | const toolbar = menus.FindMenu('LevelEditor.LevelEditorToolBar') 44 | const entry = UE.ToolMenuEntry.InitToolBarButton("Widget", "ImGUIWidget", () => { 45 | cpp.UEImGui.AddGlobalWindow(() => { 46 | //console.log('toolbar button click') 47 | }); 48 | }); 49 | toolbar.AddMenuEntry("ImGUIWidget", entry); 50 | ~~~ 51 | 52 | ### 添加工具栏下拉按钮(ComboButton) 53 | 54 | ~~~typescript 55 | const icon = new cpp.FSlateIcon("EditorStyle", "LevelEditor.WorldProperties", "LevelEditor.WorldProperties.Small"); 56 | const entry = UE.ToolMenuEntry.InitComboButton("Puerts", undefined, (tool_menu: UE.ToolMenu) => { 57 | const sub_entry = UE.ToolMenuEntry.InitMenuEntry("ShowDemoWindow", "ShowDemoWindow", "just a test...", () => { 58 | //console.log('toolbar combo button click') 59 | }); 60 | tool_menu.AddMenuEntry("ShowDemo", sub_entry); 61 | }, "Puerts", "Puerts", icon); 62 | toolbar.AddMenuEntry("ComboButton", entry); 63 | ~~~ 64 | 65 | ### 添加文件夹右键菜单 66 | 67 | ~~~typescript 68 | const folder_context_menu = menus.FindMenu('ContentBrowser.FolderContextMenu'); 69 | const entry = UE.ToolMenuEntry.InitMenuEntry("PuertsOpen", "PuertsOpen", "just a test...", (context: UE.ToolMenuContext) => { 70 | if (context) { 71 | const context_menu_context = UE.ToolMenus.FindContext(context, UE.ContentBrowserDataMenuContext_FolderMenu.StaticClass()) as UE.ContentBrowserDataMenuContext_FolderMenu; 72 | if (context_menu_context) { 73 | const selected_items = context_menu_context.SelectedItems; 74 | console.log(`selected_items num:${selected_items.Num()}`); 75 | for(var i = 0; i < selected_items.Num(); i++) { 76 | const selecte_item = selected_items.Get(i); 77 | let path = $ref(); 78 | selecte_item.GetItemPhysicalPath(path); 79 | console.log(`name:${selecte_item.GetItemName()}, path:${path}, isfolder:${selecte_item.IsFolder()}`); 80 | } 81 | } 82 | } 83 | }); 84 | folder_context_menu.AddMenuEntry("TS", entry); 85 | ~~~ 86 | 87 | ### 添加asset右键菜单 88 | 89 | ~~~typescript 90 | const asset_context_menu = menus.FindMenu('ContentBrowser.AssetContextMenu'); 91 | const entry = UE.ToolMenuEntry.InitMenuEntry("PuertsAssetOpen", "PuertsAssetOpen", "just a test...", (context: UE.ToolMenuContext) => { 92 | if (context) { 93 | const context_menu_context = UE.ToolMenus.FindContext(context, UE.ContentBrowserAssetContextMenuContext.StaticClass()) as UE.ContentBrowserAssetContextMenuContext; 94 | console.log(typeof context_menu_context); 95 | if (context_menu_context) { 96 | const selected_objects = context_menu_context.SelectedObjects; 97 | console.log(`selected_objects num:${selected_objects.Num()}`); 98 | for(var i = 0; i < selected_objects.Num(); i++) { 99 | const selecte_objects = selected_objects.Get(i); 100 | console.log(typeof(selecte_objects), selecte_objects.GetClass().GetName()); 101 | } 102 | } 103 | } 104 | }); 105 | asset_context_menu.AddMenuEntry("TS", entry); 106 | ~~~ 107 | 108 | ### 添加命令行 109 | 110 | ~~~typescript 111 | cpp.EasyEditorPlugin.AddConsoleCommand("Puerts.TestCmd", "just a test...", (...args:string[]) => { 112 | for(var i = 0; i < args.length; i++) { 113 | console.log(`arg${i}: ${args[i]}`); 114 | } 115 | }) 116 | 117 | ~~~ 118 | 119 | ## ImGui相关 120 | 121 | 本部分内容需要添加两个插件:[UEImgui](https://github.com/ZhuRong-HomoStation/UEImgui)、[EasyEditor_ImGui](https://github.com/puerts/EasyEditor_ImGui) 122 | 123 | 如果需要接入其它UI能力,或者其它c++库提供的能力,可参考[该文档](Doc/extension.md) 124 | 125 | ### 添加Detail扩展 126 | ~~~typescript 127 | cpp.UEImGui.AddDetailCustomization(UE.Actor.StaticClass(), (obj:UE.Object) => { 128 | cpp.UEImGui.BeginDetail(); 129 | cpp.ImGui.Text(`Actor Name:${obj.GetName()}`); 130 | cpp.UEImGui.EndDetail(); 131 | }); 132 | ~~~ 133 | 134 | ### ImGUI窗口 135 | 136 | ~~~typescript 137 | ImGui.SetNextWindowSize(new ImVec2(200, 100), 1 << 2) 138 | 139 | ImGui.Begin(this.simple_window_titil, isOpened); 140 | ImGui.Text("Hello, world!"); 141 | ImGui.Text(`Application average ${(1000 / ImGui.GetIO().Framerate).toFixed(3)} ms/frame (${ImGui.GetIO().Framerate.toFixed(1)} FPS)`); 142 | 143 | ImGui.End(); 144 | ~~~ 145 | 146 | ## 依赖 147 | 148 | * [Puerts](https://github.com/Tencent/puerts) 149 | * [UEImgui](https://github.com/ZhuRong-HomoStation/UEImgui) 150 | * [EasyEditor_ImGui](https://github.com/puerts/EasyEditor_ImGui) -------------------------------------------------------------------------------- /Source/EasyEditorPlugin/Private/EasyEditorPlugin.cpp: -------------------------------------------------------------------------------- 1 | #include "EasyEditorPlugin.h" 2 | 3 | #include "ToolMenuDelegates.h" 4 | #include "Misc/FileHelper.h" 5 | #include "Containers/Ticker.h" 6 | #include "ContentBrowserItem.h" 7 | #include "ToolMenus.h" 8 | #include "Binding.hpp" 9 | #include "UEDataBinding.hpp" 10 | #include "Object.hpp" 11 | #include "UECompatible.h" 12 | 13 | namespace puerts 14 | { 15 | template 16 | struct ScriptTypeName> 17 | { 18 | static constexpr auto value() 19 | { 20 | return ScriptTypeName::value(); 21 | } 22 | }; 23 | 24 | namespace v8_impl 25 | { 26 | template 27 | struct Converter> 28 | { 29 | static API::ValueType toScript(API::ContextType context, TAttribute value) 30 | { 31 | if (value.IsSet()) 32 | { 33 | return Converter::toScript(context, value.Get()); 34 | } 35 | return API::GetUndefined(context); 36 | } 37 | 38 | static TAttribute toCpp(API::ContextType context, const API::ValueType value) 39 | { 40 | if (API::IsNullOrUndefined(context, value)) 41 | return TAttribute(); 42 | return TAttribute(Converter::toCpp(context, value)); 43 | } 44 | 45 | static bool accept(API::ContextType context, const API::ValueType value) 46 | { 47 | return API::IsNullOrUndefined(context, value) || Converter::accept(context, value); 48 | } 49 | }; 50 | } // namespace v8_impl 51 | } // namespace puerts 52 | 53 | 54 | struct EasyEditorPlugin 55 | { 56 | static void SetOnJsEnvPreReload(std::function Func) 57 | { 58 | FModuleManager::LoadModuleChecked("EasyEditorPlugin").OnJsEnvPreReload = Func; 59 | } 60 | 61 | static void SetEval(std::function Func) 62 | { 63 | FModuleManager::LoadModuleChecked("EasyEditorPlugin").Eval = Func; 64 | } 65 | 66 | 67 | static int32 AddConsoleCommand(const TCHAR* Name, const TCHAR* Help, puerts::Function Command) 68 | { 69 | return FModuleManager::LoadModuleChecked("EasyEditorPlugin").TsConsoleCommands.Add( 70 | MakeUnique(Name, Help, 71 | FConsoleCommandWithArgsDelegate::CreateLambda( 72 | [Command](const TArray& Args) 73 | { 74 | auto Isolate = Command.Isolate; 75 | v8::Isolate::Scope IsolateScope(Isolate); 76 | v8::HandleScope HandleScope(Isolate); 77 | auto Context = Command.GContext.Get(Isolate); 78 | v8::Context::Scope ContextScope(Context); 79 | 80 | auto JsFunction = Command.GObject.Get(Isolate).As(); 81 | 82 | v8::TryCatch TryCatch(Isolate); 83 | 84 | v8::Local* JsArgs = 85 | static_cast*>(FMemory_Alloca(sizeof(v8::Local) * Args.Num())); 86 | 87 | for(int i = 0; i < Args.Num(); i++) 88 | { 89 | JsArgs[i] = puerts::FV8Utils::ToV8String(Isolate, Args[i]); 90 | } 91 | 92 | auto Result = JsFunction->Call(Context, v8::Undefined(Isolate), Args.Num(), JsArgs); 93 | 94 | if (TryCatch.HasCaught()) 95 | { 96 | UE_LOG(Puerts, Error, TEXT("call function throw: %s"), *puerts::FV8Utils::TryCatchToString(Isolate, &TryCatch)); 97 | } 98 | }))); 99 | } 100 | 101 | static void RemoveConsoleCommand(int32 Index) 102 | { 103 | FModuleManager::LoadModuleChecked("EasyEditorPlugin").TsConsoleCommands.RemoveAt(Index); 104 | } 105 | }; 106 | 107 | UsingUStruct(FToolMenuEntry); 108 | UsingUClass(UToolMenu); 109 | UsingUStruct(FToolMenuContext); 110 | UsingUStruct(FContentBrowserItem); 111 | UsingCppType(FSlateIcon); 112 | UsingCppType(EasyEditorPlugin); 113 | 114 | static FToolMenuEntry FToolMenuEntry_InitMenuEntry(const FName InName, const FText& InLabel, const FText& InToolTip, std::function InExecuteAction) 115 | { 116 | return FToolMenuEntry::InitMenuEntry(InName, InLabel, InToolTip, TAttribute(), FToolUIActionChoice(FToolMenuExecuteAction::CreateLambda([InExecuteAction](const FToolMenuContext& InContext) 117 | { 118 | if(InExecuteAction) 119 | { 120 | InExecuteAction(InContext); 121 | } 122 | }))); 123 | } 124 | 125 | static FToolMenuEntry FToolMenuEntry_InitToolBarButton(const FName InName, const FText& InLabel, std::function InExecuteAction) 126 | { 127 | return FToolMenuEntry::InitToolBarButton(InName, FToolUIActionChoice(FToolMenuExecuteAction::CreateLambda([InExecuteAction](const FToolMenuContext& InContext) 128 | { 129 | if(InExecuteAction) 130 | { 131 | InExecuteAction(InContext); 132 | } 133 | })), InLabel); 134 | } 135 | 136 | 137 | static FToolMenuEntry FToolMenuEntry_InitComboButton(const FName InName, std::function InExecuteAction, std::function InMenuContentGenerator, 138 | const TAttribute& InLabel = TAttribute(), const TAttribute& InToolTip = TAttribute(), const FSlateIcon& InIcon = FSlateIcon()) 139 | { 140 | return FToolMenuEntry::InitComboButton( 141 | InName, 142 | FToolUIActionChoice(FToolMenuExecuteAction::CreateLambda([InExecuteAction](const FToolMenuContext& InContext) 143 | { 144 | if(InExecuteAction) 145 | { 146 | InExecuteAction(InContext); 147 | } 148 | })), 149 | FNewToolMenuDelegate::CreateLambda([InMenuContentGenerator](UToolMenu* InSubMenu) 150 | { 151 | if (InMenuContentGenerator) 152 | { 153 | InMenuContentGenerator(InSubMenu); 154 | } 155 | }), InLabel, InToolTip, InIcon); 156 | } 157 | 158 | struct AutoRegisterForEEP 159 | { 160 | AutoRegisterForEEP() 161 | { 162 | puerts::DefineClass() 163 | .Constructor(CombineConstructors( 164 | MakeConstructor(FSlateIcon), 165 | MakeConstructor(FSlateIcon, const FName&, const FName&), 166 | MakeConstructor(FSlateIcon, const FName&, const FName&, const FName&) 167 | )) 168 | .Register(); 169 | 170 | puerts::DefineClass() 171 | .Function("InitMenuEntry", MakeFunction(&FToolMenuEntry_InitMenuEntry)) 172 | .Function("InitToolBarButton", MakeFunction(&FToolMenuEntry_InitToolBarButton)) 173 | .Function("InitComboButton", MakeFunction(&FToolMenuEntry_InitComboButton, TAttribute(), TAttribute(), FSlateIcon())) 174 | .Register(); 175 | 176 | puerts::DefineClass() 177 | .Function("SetOnJsEnvPreReload", MakeFunction(&EasyEditorPlugin::SetOnJsEnvPreReload)) 178 | .Function("SetEval", MakeFunction(&EasyEditorPlugin::SetEval)) 179 | .Function("AddConsoleCommand", MakeFunction(&EasyEditorPlugin::AddConsoleCommand)) 180 | .Function("RemoveConsoleCommand", MakeFunction(&EasyEditorPlugin::RemoveConsoleCommand)) 181 | .Register(); 182 | 183 | puerts::DefineClass() 184 | .Method("IsFolder", MakeFunction(&FContentBrowserItem::IsFolder)) 185 | .Method("IsFile", MakeFunction(&FContentBrowserItem::IsFile)) 186 | .Method("GetItemName", MakeFunction(&FContentBrowserItem::GetItemName)) 187 | .Method("GetItemPhysicalPath", MakeFunction(&FContentBrowserItem::GetItemPhysicalPath)) 188 | .Register(); 189 | 190 | } 191 | }; 192 | 193 | AutoRegisterForEEP _AutoRegisterForEEP__; 194 | 195 | #define LOCTEXT_NAMESPACE "FEasyEditorPluginModule" 196 | 197 | void FEasyEditorPluginModule::HandleMapChanged(UWorld* InWorld, EMapChangeType InMapChangeType) 198 | { 199 | if (EMapChangeType::TearDownWorld == InMapChangeType && IsInGameThread()) 200 | { 201 | if (JsEnv.IsValid()) 202 | { 203 | JsEnv->RequestFullGarbageCollectionForTesting(); 204 | } 205 | } 206 | } 207 | 208 | void FEasyEditorPluginModule::StartupModule() 209 | { 210 | char GCFlags[] = "--expose-gc"; 211 | v8::V8::SetFlagsFromString(GCFlags, sizeof(GCFlags)); 212 | FCoreDelegates::OnPostEngineInit.AddRaw(this, &FEasyEditorPluginModule::OnPostEngineInit); 213 | if (FLevelEditorModule* LevelEditor = FModuleManager::GetModulePtr("LevelEditor")) 214 | { 215 | LevelEditor->OnMapChanged().AddRaw(this, &FEasyEditorPluginModule::HandleMapChanged); 216 | } 217 | } 218 | 219 | void FEasyEditorPluginModule::OnPostEngineInit() 220 | { 221 | InitJsEnv(); 222 | FUETicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FEasyEditorPluginModule::Tick)); 223 | 224 | ConsoleCommand = MakeUnique(TEXT("EasyEditor.Restart"), TEXT("Start Script"), 225 | FConsoleCommandWithArgsDelegate::CreateLambda( 226 | [this](const TArray& Args) 227 | { 228 | if (OnJsEnvPreReload) 229 | { 230 | OnJsEnvPreReload(); 231 | UnInitJsEnv(); 232 | InitJsEnv(); 233 | StartupScriptCalled = true; 234 | JsEnv->Start(TEXT("Main")); 235 | } 236 | else 237 | { 238 | UE_LOG(Puerts, Warning, TEXT("OnJsEnvPreReload no seted!")); 239 | } 240 | })); 241 | 242 | UToolMenus::Get()->RegisterStringCommandHandler("TypeScript", FToolMenuExecuteString::CreateLambda([this](const FString& InString, const FToolMenuContext& InContext) { 243 | if(Eval) 244 | { 245 | Eval(InString); 246 | } 247 | else 248 | { 249 | UE_LOG(Puerts, Warning, TEXT("call EasyEditorPlugin.SetEval set eval before execute a StringCommand")); 250 | } 251 | })); 252 | } 253 | 254 | void FEasyEditorPluginModule::InitJsEnv() 255 | { 256 | SourceFileWatcher = MakeShared( 257 | [this](const FString& InPath) 258 | { 259 | if (JsEnv.IsValid()) 260 | { 261 | TArray Source; 262 | if (FFileHelper::LoadFileToArray(Source, *InPath)) 263 | { 264 | JsEnv->ReloadSource(InPath, puerts::PString((const char*) Source.GetData(), Source.Num())); 265 | } 266 | else 267 | { 268 | UE_LOG(Puerts, Error, TEXT("read file fail for %s"), *InPath); 269 | } 270 | } 271 | }); 272 | JsEnv = MakeShared(std::make_shared(TEXT("EasyEditorScripts")), 273 | std::make_shared(), -1, 274 | [this](const FString& InPath) 275 | { 276 | if (SourceFileWatcher.IsValid()) 277 | { 278 | SourceFileWatcher->OnSourceLoaded(InPath); 279 | } 280 | }); 281 | } 282 | 283 | void FEasyEditorPluginModule::UnInitJsEnv() 284 | { 285 | OnJsEnvCleanup.Broadcast(); 286 | Eval = nullptr; 287 | OnJsEnvPreReload = nullptr; 288 | TsConsoleCommands.Empty(); 289 | if (JsEnv.IsValid()) 290 | { 291 | JsEnv.Reset(); 292 | } 293 | if (SourceFileWatcher.IsValid()) 294 | { 295 | SourceFileWatcher.Reset(); 296 | } 297 | } 298 | 299 | bool FEasyEditorPluginModule::Tick(float) 300 | { 301 | if (!StartupScriptCalled) 302 | { 303 | StartupScriptCalled = true; 304 | if (JsEnv.IsValid()) 305 | { 306 | JsEnv->Start(TEXT("Main")); 307 | } 308 | } 309 | return !StartupScriptCalled; 310 | } 311 | 312 | void FEasyEditorPluginModule::ShutdownModule() 313 | { 314 | UnInitJsEnv(); 315 | } 316 | 317 | #undef LOCTEXT_NAMESPACE 318 | 319 | IMPLEMENT_MODULE(FEasyEditorPluginModule, EasyEditorPlugin) --------------------------------------------------------------------------------