diff --git a/.idea/.idea.MingSimulator/.idea/.gitignore b/.idea/.idea.MingSimulator/.idea/.gitignore new file mode 100644 index 0000000..0dce447 --- /dev/null +++ b/.idea/.idea.MingSimulator/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/projectSettingsUpdater.xml +/contentModel.xml +/.idea.MingSimulator.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index dbec0c9..ece480d 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -2,6 +2,7 @@ GameDefaultMap=/Game/TopDown/Maps/TopDownMap.TopDownMap EditorStartupMap=/Game/TopDown/Maps/TopDownMap.TopDownMap GlobalDefaultGameMode="/Script/MingSimulator.MingSimulatorGameMode" +GameInstanceClass=/Script/MingSimulator.MingGameInstance [/Script/Engine.RendererSettings] r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True diff --git a/Plugins/ReactUMG/LICENSE b/Plugins/ReactUMG/LICENSE new file mode 100644 index 0000000..994d302 --- /dev/null +++ b/Plugins/ReactUMG/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, puerts + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Plugins/ReactUMG/README.md b/Plugins/ReactUMG/README.md new file mode 100644 index 0000000..45f5c5f --- /dev/null +++ b/Plugins/ReactUMG/README.md @@ -0,0 +1 @@ +# ReactUMG \ No newline at end of file diff --git a/Plugins/ReactUMG/ReactUMG.uplugin b/Plugins/ReactUMG/ReactUMG.uplugin new file mode 100644 index 0000000..8b1e3d1 --- /dev/null +++ b/Plugins/ReactUMG/ReactUMG.uplugin @@ -0,0 +1,34 @@ +{ + "FileVersion": 3, + "FriendlyName": "react umg", + "Version": 1, + "VersionName": "1.0", + "FriendlyVersion": "1.0", + "Description": "react umg", + "Category": "Programming", + "CreatedBy": "Tencent Corporation", + "CreatedByURL": "https://github.com/Tencent", + "EnabledByDefault": true, + "MarketplaceURL" : "", + "CanContainContent": true, + "CanBeUsedWithUnrealHeaderTool" : false, + "Modules": [ + { + "Name": "ReactDeclarationGenerator", + "Type": "Editor", + "LoadingPhase": "Default" + }, + { + "Name": "ReactUMG", + "Type": "Runtime", + "LoadingPhase": "PreDefault", + "WhitelistPlatforms": [ "Win64", "Android", "Mac", "IOS", "Linux" ] + } + ], + "Plugins": [ + { + "Name": "Puerts", + "Enabled": true + } + ] +} diff --git a/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Private/ReactDeclarationGenerator.cpp b/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Private/ReactDeclarationGenerator.cpp new file mode 100644 index 0000000..ff62c31 --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Private/ReactDeclarationGenerator.cpp @@ -0,0 +1,281 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "ReactDeclarationGenerator.h" +#include "TypeScriptDeclarationGenerator.h" +#include "Misc/Paths.h" +#include "CoreUObject.h" +#include "TypeScriptDeclarationGenerator.h" +#include "Components/PanelSlot.h" +#include "Components/Widget.h" +#include "Interfaces/IPluginManager.h" + +static FString SafeName(const FString& Name) +{ + auto Ret = Name.Replace(TEXT(" "), TEXT("")) + .Replace(TEXT("-"), TEXT("_")) + .Replace(TEXT("/"), TEXT("_")) + .Replace(TEXT("("), TEXT("_")) + .Replace(TEXT(")"), TEXT("_")) + .Replace(TEXT("?"), TEXT("$")) + .Replace(TEXT(","), TEXT("_")); + if (Ret.Len() > 0) + { + auto FirstChar = Ret[0]; + if ((TCHAR) '0' <= FirstChar && FirstChar <= (TCHAR) '9') + { + return TEXT("_") + Ret; + } + } + return Ret; +} + +static bool IsDelegate(PropertyMacro* InProperty) +{ + return InProperty->IsA() || InProperty->IsA() +#if ENGINE_MINOR_VERSION >= 23 + || InProperty->IsA() || InProperty->IsA() +#endif + ; +} + +static bool HasObjectParam(UFunction* InFunction) +{ + for (TFieldIterator ParamIt(InFunction); ParamIt; ++ParamIt) + { + auto Property = *ParamIt; + if (Property->IsA()) + { + return true; + } + } + return false; +} + +struct FReactDeclarationGenerator : public FTypeScriptDeclarationGenerator +{ + void Begin(FString Namespace) override; + + void GenReactDeclaration(); + + void GenClass(UClass* Class) override; + + void GenStruct(UStruct* Struct) override; + + void GenEnum(UEnum* Enum) override; + + void End() override; + + virtual ~FReactDeclarationGenerator() + { + } +}; + +//--- FSlotDeclarationGenerator begin --- +void FReactDeclarationGenerator::Begin(FString Namespace) +{ +} // do nothing + +void FReactDeclarationGenerator::End() +{ +} // do nothing + +void FReactDeclarationGenerator::GenReactDeclaration() +{ + FString Components = TEXT("exports.lazyloadComponents = {};\n"); + Output << "declare module \"react-umg\" {\n"; + Output << " " + << "import * as React from 'react';\n import * as UE from 'ue';\n import * as cpp from 'cpp';\n type TArray " + "= UE.TArray;\n type " + "TSet = UE.TSet;\n type TMap = UE.TMap;\n\n"; + + Output << " type RecursivePartial = {\n" + << " [P in keyof T]?:\n" + << " T[P] extends (infer U)[] ? RecursivePartial[] :\n" + << " T[P] extends object ? RecursivePartial :\n" + << " T[P];\n" + << " };\n\n"; + + for (TObjectIterator It; It; ++It) + { + UClass* Class = *It; + checkfSlow(Class != nullptr, TEXT("Class name corruption!")); + if (Class->GetName().StartsWith("SKEL_") || Class->GetName().StartsWith("REINST_") || + Class->GetName().StartsWith("TRASHCLASS_") || Class->GetName().StartsWith("PLACEHOLDER_")) + { + continue; + } + if (Class->IsChildOf()) + Gen(Class); + } + + Output << " " + << "export interface Props {\n"; + Output << " " + << " Slot ? : PanelSlot;\n"; + Output << " " + << "}\n\n"; + + for (TObjectIterator It; It; ++It) + { + UClass* Class = *It; + checkfSlow(Class != nullptr, TEXT("Class name corruption!")); + if (Class->GetName().StartsWith("SKEL_") || Class->GetName().StartsWith("REINST_") || + Class->GetName().StartsWith("TRASHCLASS_") || Class->GetName().StartsWith("PLACEHOLDER_")) + { + continue; + } + if (Class->IsChildOf()) + { + Gen(Class); + Components += "exports." + SafeName(Class->GetName()) + " = '" + SafeName(Class->GetName()) + "';\n"; + if (!(Class->ClassFlags & CLASS_Native)) + { + Components += "exports.lazyloadComponents." + SafeName(Class->GetName()) + " = '" + Class->GetPathName() + "';\n"; + } + } + } + + Output << R"( + interface Root { + removeFromViewport() : void; + getWidget(): any; + } + + interface TReactUMG { + render(element: React.ReactElement) : Root; + init(world: any) : void; + } + + var ReactUMG : TReactUMG; +} + )"; + + FFileHelper::SaveStringToFile(ToString(), *(FPaths::ProjectDir() / TEXT("Typing/react-umg/index.d.ts")), + FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); + FFileHelper::SaveStringToFile(Components, *(FPaths::ProjectContentDir() / TEXT("JavaScript/react-umg/components.js")), + FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); +} + +static bool IsReactSupportProperty(PropertyMacro* Property) +{ + if (CastFieldMacro(Property) || CastFieldMacro(Property) || + CastFieldMacro(Property) || CastFieldMacro(Property) || + CastFieldMacro(Property)) + return false; + if (auto ArrayProperty = CastFieldMacro(Property)) + { + return IsReactSupportProperty(ArrayProperty->Inner); + } + else if (auto DelegateProperty = CastFieldMacro(Property)) + { + return !HasObjectParam(DelegateProperty->SignatureFunction); + } + else if (auto MulticastDelegateProperty = CastFieldMacro(Property)) + { + return !HasObjectParam(MulticastDelegateProperty->SignatureFunction); + } + return true; +} + +void FReactDeclarationGenerator::GenClass(UClass* Class) +{ + if (!Class->IsChildOf() && !Class->IsChildOf()) + return; + bool IsWidget = Class->IsChildOf(); + FStringBuffer StringBuffer{"", ""}; + StringBuffer << " " + << "interface " << SafeName(Class->GetName()); + if (IsWidget) + StringBuffer << "Props"; + + auto Super = Class->GetSuperStruct(); + + if (Super && (Super->IsChildOf() || Super->IsChildOf())) + { + Gen(Super); + StringBuffer << " extends " << SafeName(Super->GetName()); + if (Super->IsChildOf()) + StringBuffer << "Props"; + } + else if (IsWidget) + { + StringBuffer << " extends Props"; + } + + StringBuffer << " {\n"; + + for (TFieldIterator PropertyIt(Class, EFieldIteratorFlags::ExcludeSuper); PropertyIt; ++PropertyIt) + { + auto Property = *PropertyIt; + if (!IsReactSupportProperty(Property)) + continue; + + //UE.27上遇到一个问题, + //引擎内置蓝图DefaultBurnIn有个FLinearColor类型的变量Foreground Color, + //并且父类UUserWidget有个FSlateColor类型的变量ForegroundColor + //此处SafeName去掉空格导致interface写入了父类变量名字,类型对不上,有报错,仅此一例 + //PuerTS插件DeclarationGenerator.cpp中的SafeName存在同样隐患 + FString PropertyNameSafe = SafeName(Property->GetName()); + UClass* SuperClass = Class->GetSuperClass(); + if (PropertyNameSafe != Property->GetName() && + SuperClass != nullptr && SuperClass->FindPropertyByName(*PropertyNameSafe) != nullptr) + { + UE_LOG(LogTemp, Warning, TEXT("ReactUMG, invalid property name, origin name: %s, safe name %s in super class!"), *Property->GetName(), *PropertyNameSafe); + continue; + } + + FStringBuffer TmpBuff; + TmpBuff << " " << SafeName(Property->GetName()) << "?: "; + TArray RefTypesTmp; + if (!IsWidget && IsDelegate(Property)) // UPanelSlot skip delegate + { + continue; + } + if (CastFieldMacro(Property)) + { + TmpBuff << "RecursivePartial<"; + } + if (!GenTypeDecl(TmpBuff, Property, RefTypesTmp, false, true)) + { + continue; + } + if (CastFieldMacro(Property)) + { + TmpBuff << ">"; + } + for (auto Type : RefTypesTmp) + { + Gen(Type); + } + StringBuffer << " " << TmpBuff.Buffer << ";\n"; + } + + StringBuffer << " " + << "}\n\n"; + + if (IsWidget) + { + StringBuffer << " " + << "class " << SafeName(Class->GetName()) << " extends React.Component<" << SafeName(Class->GetName()) + << "Props> {\n" + << " nativePtr: " << GetNameWithNamespace(Class) << ";\n }\n\n"; + } + + Output << StringBuffer; +} + +void FReactDeclarationGenerator::GenStruct(UStruct* Struct) +{ +} + +void FReactDeclarationGenerator::GenEnum(UEnum* Enum) +{ +} + +//--- FSlotDeclarationGenerator end --- + +void UReactDeclarationGenerator::Gen_Implementation() const +{ + FReactDeclarationGenerator ReactDeclarationGenerator; + ReactDeclarationGenerator.GenReactDeclaration(); +} diff --git a/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Private/ReactDeclarationGeneratorModule.cpp b/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Private/ReactDeclarationGeneratorModule.cpp new file mode 100644 index 0000000..030ac96 --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Private/ReactDeclarationGeneratorModule.cpp @@ -0,0 +1,17 @@ +#include "IReactDeclarationGenerator.h" +#include "Features/IModularFeatures.h" + +class FReactDeclarationGeneratorModule : public IReactDeclarationGenerator +{ +private: +public: + void StartupModule() override + { + } + + void ShutdownModule() override + { + } +}; + +IMPLEMENT_MODULE(FReactDeclarationGeneratorModule, ReactDeclarationGenerator) \ No newline at end of file diff --git a/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Public/IReactDeclarationGenerator.h b/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Public/IReactDeclarationGenerator.h new file mode 100644 index 0000000..a3469cd --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Public/IReactDeclarationGenerator.h @@ -0,0 +1,31 @@ +/* + * Tencent is pleased to support the open source community by making Puerts available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may + * be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE', + * which is part of this source code package. + */ + +#pragma once + +#include + +#if ENGINE_MAJOR_VERSION < 5 || ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 2 +#include "IScriptGeneratorPluginInterface.h" +#endif +#include "Modules/ModuleManager.h" +#include "CoreMinimal.h" + +class IReactDeclarationGenerator : public IModuleInterface +{ +public: + static inline IReactDeclarationGenerator& Get() + { + return FModuleManager::LoadModuleChecked("ReactDeclarationGenerator"); + } + + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("ReactDeclarationGenerator"); + } +}; diff --git a/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Public/ReactDeclarationGenerator.h b/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Public/ReactDeclarationGenerator.h new file mode 100644 index 0000000..c6f6c3e --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactDeclarationGenerator/Public/ReactDeclarationGenerator.h @@ -0,0 +1,24 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CodeGenerator.h" + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "ReactDeclarationGenerator.generated.h" + +/** + * + */ +UCLASS() +class REACTDECLARATIONGENERATOR_API UReactDeclarationGenerator : public UObject, public ICodeGenerator +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintNativeEvent) + void Gen() const; + + virtual void Gen_Implementation() const override; +}; diff --git a/Plugins/ReactUMG/Source/ReactDeclarationGenerator/ReactDeclarationGenerator.Build.cs b/Plugins/ReactUMG/Source/ReactDeclarationGenerator/ReactDeclarationGenerator.Build.cs new file mode 100644 index 0000000..3ef701c --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactDeclarationGenerator/ReactDeclarationGenerator.Build.cs @@ -0,0 +1,40 @@ +/* +* Tencent is pleased to support the open source community by making Puerts available. +* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may be subject to their corresponding license terms. +* This file is subject to the terms and conditions defined in file 'LICENSE', which is part of this source code package. +*/ + +using System; +using System.IO; +using UnrealBuildTool; + +public class ReactDeclarationGenerator : ModuleRules +{ + public ReactDeclarationGenerator(ReadOnlyTargetRules Target) : base(Target) + { + PublicIncludePaths.AddRange( + new string[] { + "Programs/UnrealHeaderTool/Public", + } + ); + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "UMG", + "UnrealEd", + "LevelEditor", + "Engine", + "Slate", + "SlateCore", + "EditorStyle", + "InputCore", + "Projects", + "DeclarationGenerator", + "JsEnv", + } + ); + } +} diff --git a/Plugins/ReactUMG/Source/ReactUMG/IReactUMG.h b/Plugins/ReactUMG/Source/ReactUMG/IReactUMG.h new file mode 100644 index 0000000..847c482 --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactUMG/IReactUMG.h @@ -0,0 +1,27 @@ +/* + * Tencent is pleased to support the open source community by making Puerts available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may + * be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE', + * which is part of this source code package. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +class IReactUMG : public IModuleInterface //: public IScriptGeneratorPluginInterface +{ +public: + static inline IReactUMG& Get() + { + return FModuleManager::LoadModuleChecked("ReactUMG"); + } + + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("ReactUMG"); + } +}; diff --git a/Plugins/ReactUMG/Source/ReactUMG/ReactUMG.Build.cs b/Plugins/ReactUMG/Source/ReactUMG/ReactUMG.Build.cs new file mode 100644 index 0000000..6c3f760 --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactUMG/ReactUMG.Build.cs @@ -0,0 +1,65 @@ +/* +* Tencent is pleased to support the open source community by making Puerts available. +* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may be subject to their corresponding license terms. +* This file is subject to the terms and conditions defined in file 'LICENSE', which is part of this source code package. +*/ + +using UnrealBuildTool; +using System.IO; + +public class ReactUMG : ModuleRules +{ + public ReactUMG(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] + { + "Core", "CoreUObject", "Engine", "InputCore", "Serialization", "UMG" + }); + + bEnableExceptions = true; + + //string coreJSPath = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "Content", "Scripts")); + //string destDirName = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "..", "..", "Content", "Scripts")); + //DirectoryCopy(coreJSPath, destDirName, true); + + } + + private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs) + { + DirectoryInfo dir = new DirectoryInfo(sourceDirName); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException( + "Source directory does not exist or could not be found: " + + sourceDirName); + } + + if (!Directory.Exists(destDirName)) + { + Directory.CreateDirectory(destDirName); + } + + // Get the files in the directory and copy them to the new location. + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + string temppath = Path.Combine(destDirName, file.Name); + file.CopyTo(temppath, true); + } + + if (copySubDirs) + { + DirectoryInfo[] dirs = dir.GetDirectories(); + foreach (DirectoryInfo subdir in dirs) + { + string temppath = Path.Combine(destDirName, subdir.Name); + DirectoryCopy(subdir.FullName, temppath, copySubDirs); + } + } + } + +} diff --git a/Plugins/ReactUMG/Source/ReactUMG/ReactUMGModule.cpp b/Plugins/ReactUMG/Source/ReactUMG/ReactUMGModule.cpp new file mode 100644 index 0000000..e24d2e2 --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactUMG/ReactUMGModule.cpp @@ -0,0 +1,25 @@ +/* + * Tencent is pleased to support the open source community by making Puerts available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may + * be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE', + * which is part of this source code package. + */ + +#include "IReactUMG.h" +#include "Features/IModularFeatures.h" + +class FReactUMGModule : public IReactUMG +{ +private: +public: + void StartupModule() override + { + } + + void ShutdownModule() override + { + } +}; + +IMPLEMENT_MODULE(FReactUMGModule, ReactUMG) diff --git a/Plugins/ReactUMG/Source/ReactUMG/ReactWidget.cpp b/Plugins/ReactUMG/Source/ReactUMG/ReactWidget.cpp new file mode 100644 index 0000000..bf62ba3 --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactUMG/ReactWidget.cpp @@ -0,0 +1,76 @@ +/* + * Tencent is pleased to support the open source community by making Puerts available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may + * be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE', + * which is part of this source code package. + */ + +#include "ReactWidget.h" +#include "Blueprint/WidgetTree.h" + +UReactWidget::UReactWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) +{ + WidgetTree = CreateDefaultSubobject(TEXT("WidgetTree")); + WidgetTree->SetFlags(RF_Transactional); +} + +UPanelSlot* UReactWidget::AddChild(UWidget* Content) +{ + if (Content == nullptr) + { + return nullptr; + } + + if (RootSlot) + { + return nullptr; + } + + Content->RemoveFromParent(); + + EObjectFlags NewObjectFlags = RF_Transactional; + if (HasAnyFlags(RF_Transient)) + { + NewObjectFlags |= RF_Transient; + } + + UPanelSlot* PanelSlot = NewObject(this, UPanelSlot::StaticClass(), NAME_None, NewObjectFlags); + PanelSlot->Content = Content; + + Content->Slot = PanelSlot; + + RootSlot = PanelSlot; + + WidgetTree->RootWidget = Content; + + InvalidateLayoutAndVolatility(); + + return PanelSlot; +} + +bool UReactWidget::RemoveChild(UWidget* Content) +{ + if (Content == nullptr || RootSlot == nullptr || Content != RootSlot->Content) + { + return false; + } + UPanelSlot* PanelSlot = RootSlot; + RootSlot = nullptr; + + if (PanelSlot->Content) + { + PanelSlot->Content->Slot = nullptr; + } + + const bool bReleaseChildren = true; + PanelSlot->ReleaseSlateResources(bReleaseChildren); + PanelSlot->Parent = nullptr; + PanelSlot->Content = nullptr; + + WidgetTree->RootWidget = nullptr; + + InvalidateLayoutAndVolatility(); + + return true; +} diff --git a/Plugins/ReactUMG/Source/ReactUMG/ReactWidget.h b/Plugins/ReactUMG/Source/ReactUMG/ReactWidget.h new file mode 100644 index 0000000..ca6e14c --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactUMG/ReactWidget.h @@ -0,0 +1,34 @@ +/* + * Tencent is pleased to support the open source community by making Puerts available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may + * be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE', + * which is part of this source code package. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "ReactWidget.generated.h" + +/** + * + */ +UCLASS() +class REACTUMG_API UReactWidget : public UUserWidget +{ + GENERATED_BODY() + +protected: + UPanelSlot* RootSlot; + +public: + explicit UReactWidget(const FObjectInitializer& ObjectInitializer); + + UFUNCTION(BlueprintCallable, Category = "Scripting | Javascript") + UPanelSlot* AddChild(UWidget* Content); + + UFUNCTION(BlueprintCallable, Category = "Scripting | Javascript") + bool RemoveChild(UWidget* Content); +}; diff --git a/Plugins/ReactUMG/Source/ReactUMG/UMGManager.cpp b/Plugins/ReactUMG/Source/ReactUMG/UMGManager.cpp new file mode 100644 index 0000000..eceac9f --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactUMG/UMGManager.cpp @@ -0,0 +1,31 @@ +/* + * Tencent is pleased to support the open source community by making Puerts available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may + * be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE', + * which is part of this source code package. + */ + +#include "UMGManager.h" +#include "Engine/Engine.h" +#include "Async/Async.h" + +UReactWidget* UUMGManager::CreateReactWidget(UWorld* World) +{ + return ::CreateWidget(World); +} + +UUserWidget* UUMGManager::CreateWidget(UWorld* World, UClass* Class) +{ + return ::CreateWidget(World, Class); +} + +void UUMGManager::SynchronizeWidgetProperties(UWidget* Widget) +{ + Widget->SynchronizeProperties(); +} + +void UUMGManager::SynchronizeSlotProperties(UPanelSlot* Slot) +{ + Slot->SynchronizeProperties(); +} diff --git a/Plugins/ReactUMG/Source/ReactUMG/UMGManager.h b/Plugins/ReactUMG/Source/ReactUMG/UMGManager.h new file mode 100644 index 0000000..9906f06 --- /dev/null +++ b/Plugins/ReactUMG/Source/ReactUMG/UMGManager.h @@ -0,0 +1,40 @@ +/* + * Tencent is pleased to support the open source community by making Puerts available. + * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may + * be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE', + * which is part of this source code package. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "ReactWidget.h" +#include "Components/PanelSlot.h" +#include "Blueprint/UserWidget.h" +#include "Blueprint/WidgetBlueprintLibrary.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "GameFramework/PlayerController.h" +#include "UMGManager.generated.h" + +/** + * + */ +UCLASS() +class REACTUMG_API UUMGManager : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Widget") + static UReactWidget* CreateReactWidget(UWorld* World); + + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Widget") + static UUserWidget* CreateWidget(UWorld* World, UClass* Class); + + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Widget") + static void SynchronizeWidgetProperties(UWidget* Widget); + + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Widget") + static void SynchronizeSlotProperties(UPanelSlot* Slot); +}; diff --git a/Plugins/ReactUMG/TypeScript/react-umg/react-umg.ts b/Plugins/ReactUMG/TypeScript/react-umg/react-umg.ts new file mode 100644 index 0000000..74ae29b --- /dev/null +++ b/Plugins/ReactUMG/TypeScript/react-umg/react-umg.ts @@ -0,0 +1,300 @@ +/* +* Tencent is pleased to support the open source community by making Puerts available. +* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may be subject to their corresponding license terms. +* This file is subject to the terms and conditions defined in file 'LICENSE', which is part of this source code package. +*/ + +import * as Reconciler from 'react-reconciler' +import * as puerts from 'puerts' +import * as UE from 'ue' + +let world: UE.World; + +function deepEquals(x: any, y: any) { + if ( x === y ) return true; + + if ( typeof x !== 'object' || x === null || typeof y !== 'object' || y === null ) return false; + + for (var p in x) { // all x[p] in y + if (p == 'children' || p == 'Slot') continue; + if (!deepEquals(x[p], y[p])) return false; + } + + for (var p in y) { + if (p == 'children' || p == 'Slot') continue; + if (!x.hasOwnProperty(p)) return false; + } + + return true; +} + +declare const exports: {lazyloadComponents:{}} + +class UEWidget { + type: string; + callbackRemovers: {[key: string] : () => void}; + nativePtr: UE.Widget; + slot: any; + nativeSlotPtr: UE.PanelSlot; + + constructor (type: string, props: any) { + this.type = type; + this.callbackRemovers = {}; + + try { + this.init(type, props); + } catch(e) { + console.error("create " + type + " throw " + e); + } + } + + init(type: string, props: any) { + let classPath = exports.lazyloadComponents[type]; + if (classPath) { + //this.nativePtr = asyncUIManager.CreateComponentByClassPathName(classPath); + this.nativePtr = UE.NewObject(UE.Class.Load(classPath)) as UE.Widget; + } else { + this.nativePtr = new UE[type](); + } + + let myProps = {}; + for (const key in props) { + let val = props[key]; + if (key == 'Slot') { + this.slot = val; + } else if (typeof val === 'function') { + this.bind(key, val); + } else if(key !== 'children') { + myProps[key] = val; + } + } + //console.log("UEWidget", type, JSON.stringify(myProps)) + puerts.merge(this.nativePtr, myProps); + //console.log(type + ' inited') + } + + bind(name: string, callback: Function) { + let nativePtr = this.nativePtr + let prop = nativePtr[name]; + if (typeof prop.Add === 'function') { + prop.Add(callback); + this.callbackRemovers[name] = () => { + prop.Remove(callback); + } + } else if (typeof prop.Bind == 'function') { + prop.Bind(callback); + this.callbackRemovers[name] = () => { + prop.Unbind(); + } + } else { + console.warn("unsupport callback " + name); + } + } + + update(oldProps: any, newProps: any) { + let myProps = {}; + let propChange = false; + for(var key in newProps) { + let oldProp = oldProps[key]; + let newProp = newProps[key]; + if (key != 'children' && oldProp != newProp) { + if (key == 'Slot') { + this.slot = newProp; + //console.log("update slot..", this.toJSON()); + puerts.merge(this.nativeSlotPtr, newProp); + UE.UMGManager.SynchronizeSlotProperties(this.nativeSlotPtr) + } else if (typeof newProp === 'function') { + this.unbind(key); + this.bind(key, newProp); + } else { + myProps[key] = newProp; + propChange = true; + } + } + } + if (propChange) { + //console.log("update props", this.toJSON(), JSON.stringify(myProps)); + puerts.merge(this.nativePtr, myProps); + UE.UMGManager.SynchronizeWidgetProperties(this.nativePtr) + } + } + + unbind(name: string) { + let remover = this.callbackRemovers[name]; + this.callbackRemovers[name] = undefined; + if (remover) { + remover(); + } + } + + unbindAll() { + for(var key in this.callbackRemovers) { + this.callbackRemovers[key](); + } + this.callbackRemovers = {}; + } + + appendChild(child: UEWidget) { + let nativeSlot = (this.nativePtr as UE.PanelWidget).AddChild(child.nativePtr); + //console.log("appendChild", (await this.nativePtr).toJSON(), (await child.nativePtr).toJSON()); + child.nativeSlot = nativeSlot; + } + + removeChild(child: UEWidget) { + child.unbindAll(); + (this.nativePtr as UE.PanelWidget).RemoveChild(child.nativePtr); + //console.log("removeChild", (await this.nativePtr).toJSON(), (await child.nativePtr).toJSON()) + } + + set nativeSlot(value: UE.PanelSlot) { + this.nativeSlotPtr = value; + //console.log('setting nativeSlot', value.toJSON()); + if (this.slot) { + puerts.merge(this.nativeSlotPtr, this.slot); + UE.UMGManager.SynchronizeSlotProperties(this.nativeSlotPtr); + } + } +} + +class UEWidgetRoot { + nativePtr: UE.ReactWidget; + Added: boolean; + + constructor(nativePtr: UE.ReactWidget) { + this.nativePtr = nativePtr; + } + + appendChild(child: UEWidget) { + let nativeSlot = this.nativePtr.AddChild(child.nativePtr); + child.nativeSlot = nativeSlot; + } + + removeChild(child: UEWidget) { + child.unbindAll(); + this.nativePtr.RemoveChild(child.nativePtr); + } + + addToViewport(z : number) { + if (!this.Added) { + this.nativePtr.AddToViewport(z); + this.Added = true; + } + } + + removeFromViewport() { + this.nativePtr.RemoveFromViewport(); + } + + getWidget() { + return this.nativePtr; + } +} + +const hostConfig : Reconciler.HostConfig = { + getRootHostContext () { + return {}; + }, + //CanvasPanel()的parentHostContext是getRootHostContext返回的值 + getChildHostContext (parentHostContext: {}) { + return parentHostContext;//no use, share one + }, + appendInitialChild (parent: UEWidget, child: UEWidget) { + parent.appendChild(child); + }, + appendChildToContainer (container: UEWidgetRoot, child: UEWidget) { + container.appendChild(child); + }, + appendChild (parent: UEWidget, child: UEWidget) { + parent.appendChild(child); + }, + createInstance (type: string, props: any) { + return new UEWidget(type, props); + }, + createTextInstance (text: string) { + return new UEWidget("TextBlock", {Text: text}); + }, + finalizeInitialChildren () { + return false + }, + getPublicInstance (instance: UEWidget) { + console.warn('getPublicInstance'); + return instance + }, + now: Date.now, + prepareForCommit () { + //log('prepareForCommit'); + }, + resetAfterCommit (container: UEWidgetRoot) { + container.addToViewport(0); + }, + resetTextContent () { + console.error('resetTextContent not implemented!'); + }, + shouldSetTextContent (type, props) { + return false + }, + + commitTextUpdate (textInstance: UEWidget, oldText: string, newText: string) { + if (oldText != newText) { + textInstance.update({}, {Text: newText}) + } + }, + + //return false表示不更新,真值将会出现到commitUpdate的updatePayload里头 + prepareUpdate (instance: UEWidget, type: string, oldProps: any, newProps: any) { + try{ + return !deepEquals(oldProps, newProps); + } catch(e) { + console.error(e.message); + return true; + } + }, + commitUpdate (instance: UEWidget, updatePayload: any, type : string, oldProps : any, newProps: any) { + try{ + instance.update(oldProps, newProps); + } catch(e) { + console.error("commitUpdate fail!, " + e); + } + }, + removeChildFromContainer (container: UEWidgetRoot, child: UEWidget) { + console.error('removeChildFromContainer'); + //container.removeChild(child).catch(e => { + // console.error('removeChildFromContainer , e:' + e.message); + //}); + }, + removeChild(parent: UEWidget, child: UEWidget) { + parent.removeChild(child); + }, + + //useSyncScheduling: true, + supportsMutation: true, + isPrimaryRenderer: true, + supportsPersistence: false, + supportsHydration: false, + + shouldDeprioritizeSubtree: undefined, + setTimeout: undefined, + clearTimeout: undefined, + cancelDeferredCallback: undefined, + noTimeout: undefined, + scheduleDeferredCallback: undefined, +} + +const reconciler = Reconciler(hostConfig) + +export const ReactUMG = { + render: function(reactElement: React.ReactNode) { + if (world == undefined) { + throw new Error("init with World first!"); + } + let root = new UEWidgetRoot(UE.UMGManager.CreateReactWidget(world)); + const container = reconciler.createContainer(root, false, false); + reconciler.updateContainer(reactElement, container, null, null); + return root; + }, + init: function(inWorld: UE.World) { + world = inWorld; + } +} + diff --git a/Source/MingSimulator/FMCJsEnvManager.cpp b/Source/MingSimulator/FMCJsEnvManager.cpp new file mode 100644 index 0000000..834e545 --- /dev/null +++ b/Source/MingSimulator/FMCJsEnvManager.cpp @@ -0,0 +1,20 @@ +#include "FMCJsEnvManager.h" +#include "JsEnv.h" + +void FMCJsEnvManager::Init(TObjectPtr GameInstance) +{ + M_GameInstance = GameInstance; + //TODO:从PuertsSettings获取是否开启Debug,是否阻塞,端口号等信息 + //8080是调试端口 + M_JsEnv = MakeShared(std::make_unique(TEXT("JavaScript")), std::make_shared(), 8080); + //M_JsEnv->WaitDebugger(); + TArray> Arguments; + Arguments.Add(TPair(TEXT("GameInstance"), GameInstance)); // 可选步骤 + M_JsEnv->Start("entry.js", Arguments); + //GameScript = MakeShared(std::make_unique(TEXT("JavaScript")), std::make_shared(), 8080); +} + +void FMCJsEnvManager::Release() +{ + M_JsEnv.Reset(); +} diff --git a/Source/MingSimulator/FMCJsEnvManager.h b/Source/MingSimulator/FMCJsEnvManager.h new file mode 100644 index 0000000..aa0c137 --- /dev/null +++ b/Source/MingSimulator/FMCJsEnvManager.h @@ -0,0 +1,16 @@ +#pragma once + +namespace puerts +{ + class FJsEnv; +} + +class FMCJsEnvManager +{ +public: + void Init(TObjectPtr GameInstance); + void Release(); +private: + TObjectPtr M_GameInstance; + TSharedPtr M_JsEnv; +}; diff --git a/Source/MingSimulator/MingGameInstance.cpp b/Source/MingSimulator/MingGameInstance.cpp new file mode 100644 index 0000000..034f524 --- /dev/null +++ b/Source/MingSimulator/MingGameInstance.cpp @@ -0,0 +1,18 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "MingGameInstance.h" + + +void UMingGameInstance::Init() +{ + Super::Init(); + M_JsEnvManager = MakeShared(FMCJsEnvManager()); + M_JsEnvManager->Init(this); +} + +void UMingGameInstance::Shutdown() +{ + M_JsEnvManager->Release(); + Super::Shutdown(); +} diff --git a/Source/MingSimulator/MingGameInstance.h b/Source/MingSimulator/MingGameInstance.h new file mode 100644 index 0000000..6aab663 --- /dev/null +++ b/Source/MingSimulator/MingGameInstance.h @@ -0,0 +1,22 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "FMCJsEnvManager.h" +#include "Kismet/BlueprintPlatformLibrary.h" +#include "MingGameInstance.generated.h" + +/** + * + */ +UCLASS() +class MINGSIMULATOR_API UMingGameInstance : public UPlatformGameInstance +{ + GENERATED_BODY() +public: + virtual void Init() override; + virtual void Shutdown() override; +private: + TSharedPtr M_JsEnvManager; +}; diff --git a/Source/MingSimulator/MingSimulator.Build.cs b/Source/MingSimulator/MingSimulator.Build.cs index 6f0e43c..e0fa209 100644 --- a/Source/MingSimulator/MingSimulator.Build.cs +++ b/Source/MingSimulator/MingSimulator.Build.cs @@ -8,6 +8,6 @@ public class MingSimulator : ModuleRules { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "NavigationSystem", "AIModule", "Niagara", "EnhancedInput" }); + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "NavigationSystem", "AIModule", "Niagara", "EnhancedInput","JsEnv" }); } } diff --git a/TypeScript/entry.ts b/TypeScript/entry.ts new file mode 100644 index 0000000..e43f253 --- /dev/null +++ b/TypeScript/entry.ts @@ -0,0 +1,2 @@ +import * as UE from 'ue' +import {argv} from 'puerts'; diff --git a/package.json b/package.json new file mode 100644 index 0000000..429f904 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "mingsimulator", + "version": "1.0.0", + "description": "大明模拟器", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "http://micecola.top:8418/starmen1265/MingSimulator.git" + }, + "author": "MiceCola", + "license": "ISC" +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..22b6fff --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "commonjs", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "jsx": "react", + "sourceMap": true, + "typeRoots": [ + "Typing", + "./node_modules/@types" + ], + "outDir": "Content/JavaScript" + }, + "include": [ + "TypeScript/**/*" + ] +} \ No newline at end of file