diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5b782ea --- /dev/null +++ b/.gitmodules @@ -0,0 +1,11 @@ +[submodule "unreal-cpp-net-fps/Plugins/Ecsact"] + path = unreal-cpp-net-fps/Plugins/Ecsact + url = https://github.com/ecsact-dev/ecsact_unreal + branch = main +[submodule "unreal-cpp-net-fps/Plugins/EcsactNet"] + path = unreal-cpp-net-fps/Plugins/EcsactNet + url = https://github.com/seaube/ecsact-net-unreal + branch = main +[submodule "unreal-cpp-benchmark/Plugins/Ecsact"] + path = unreal-cpp-benchmark/Plugins/Ecsact + url = git@github.com:ecsact-dev/ecsact_unreal.git diff --git a/unreal-cpp-benchmark/.clang-format b/unreal-cpp-benchmark/.clang-format new file mode 100644 index 0000000..b090ef9 --- /dev/null +++ b/unreal-cpp-benchmark/.clang-format @@ -0,0 +1,99 @@ +AccessModifierOffset: -2 +AlignAfterOpenBracket: BlockIndent +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Left +AlignOperands: DontAlign +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakAfterJavaFieldAnnotations: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakConstructorInitializersBeforeComma: false +BreakStringLiterals: true +ColumnLimit: 80 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +IncludeBlocks: Preserve +IncludeIsMainRegex: "(Test)?$" +IndentCaseLabels: true +IndentPPDirectives: AfterHash +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: true +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PackConstructorInitializers: NextLine +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 400 +PenaltyReturnTypeOnItsOwnLine: 350 +PointerAlignment: Left +ReflowComments: true +RequiresClausePosition: OwnLine +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: Never +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SeparateDefinitionBlocks: Always +Standard: Latest +TabWidth: 2 +UseTab: ForContinuationAndIndentation diff --git a/unreal-cpp-benchmark/.gitignore b/unreal-cpp-benchmark/.gitignore new file mode 100644 index 0000000..8320393 --- /dev/null +++ b/unreal-cpp-benchmark/.gitignore @@ -0,0 +1,78 @@ +# Visual Studio 2015 user specific files +.vs/ + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +*.ipa + +# These project files can be generated by the engine +*.xcodeproj +*.xcworkspace +*.sln +*.suo +*.opensdf +*.sdf +*.VC.db +*.VC.opendb + +# Precompiled Assets +SourceArt/**/*.png +SourceArt/**/*.tga + +# Binary Files +Binaries/* +Plugins/**/Binaries/* + +# Builds +Build/* + +# Whitelist PakBlacklist-.txt files +!Build/*/ +Build/*/** +!Build/*/PakBlacklist*.txt + +# Don't ignore icon files in Build +!Build/**/*.ico + +# Built data for maps +*_BuiltData.uasset + +# Configuration files generated by the Editor +Saved/* + +# Compiled source files for the engine to use +Intermediate/* +Plugins/**/Intermediate/* + +# Cache files for the editor to use +DerivedDataCache/* + +# Clangd +.cache +compile_commands.json diff --git a/unreal-cpp-benchmark/BuildSystemImpls.ps1 b/unreal-cpp-benchmark/BuildSystemImpls.ps1 new file mode 100644 index 0000000..d39c192 --- /dev/null +++ b/unreal-cpp-benchmark/BuildSystemImpls.ps1 @@ -0,0 +1,84 @@ +#!/usr/bin/env pwsh + +# This is an example batch file for building your Ecsact system implementations +# with Emscripten. In a more sophisticated setup you will likely use a build +# system such as bazel, cmake, zig, etc. + +param ( + [Parameter(Mandatory)] $ProjectDir +) + +if (${env:UE-ZenSubprocessDataPath}) +{ + Write-Host "Detected live coding enabled" + Write-Host "Skipping system impl re-build" + exit 0 +} + +$ErrorActionPreference = 'Stop' + +if (-not $env:EMSDK) +{ + if (Test-Path -Path "C:\emsdk\emsdk_env.ps1" -PathType Leaf) + { + (. C:\emsdk\emsdk_env.ps1) 2> $null + } + + if (-not $env:EMSDK) + { + throw "Unable to find the Emscripten SDK installed on your system" + } +} + +$EcsactFiles = @( + "$ProjectDir/Source/EcsactBenchmark/EcsactBenchmark.ecsact" +) + +$Sources = @( + "$ProjectDir/SystemImpls/EcsactSystemImpls.cpp" +) + +$GeneratedOutDir = "$ProjectDir/SystemImpls/generated" + +# TODO: don't hard set generated sources +$GeneratedSources = @( + "$ProjectDir/SystemImpls/generated/EcsactBenchmark.ecsact.systems.cc" +) + +$EcsactInc = (ecsact config include_dir) + +ecsact codegen $EcsactFiles ` + --plugin=cpp_header ` + --plugin=systems_header ` + --plugin=cpp_systems_header ` + --plugin=cpp_systems_source ` + --outdir=$GeneratedOutDir + +emcc -v + +mkdir -Force "$ProjectDir/Binaries" | Out-Null + +$WasmOutputFilePath = "$ProjectDir/Binaries/SystemImpls.wasm" + +Write-Host "Building $WasmOutputFilePath ..." + +# NOTE: PURE_WASI=1 removes emscripten_* functions that are not compatible with the Ecsact SI Wasm host +emcc -std=c++20 --no-entry -I"$EcsactInc" -I"SystemImpls/generated" ` + -s ERROR_ON_UNDEFINED_SYMBOLS=0 ` + -s WASM=1 ` + -s STANDALONE_WASM=1 ` + -s PURE_WASI=1 ` + -O3 ` + -Wno-js-compiler ` + -o $WasmOutputFilePath ` + $Sources ` + $GeneratedSources + +if (-not $?) +{ + throw "emcc exited with code ${LastExitCode}" +} + + +Write-Host "Uploading $WasmOutputFilePath to Ecsact Net ..." + diff --git a/unreal-cpp-benchmark/Config/DefaultEcsact.ini b/unreal-cpp-benchmark/Config/DefaultEcsact.ini new file mode 100644 index 0000000..98ad987 --- /dev/null +++ b/unreal-cpp-benchmark/Config/DefaultEcsact.ini @@ -0,0 +1,11 @@ +[/Script/Ecsact.EcsactSettings] +bEnableBuild=True +CustomEcsactRuntimeLibraryPath= +BuildReportFilter=None ++Recipes=rt_entt ++Recipes=si_wasmer +Runner=Custom +CustomRunnerClass=None +bAutoCollectBlueprintRunnerSubsystems=True ++RunnerSubsystems=/Game/Blueprints/BP_EcsactBenchmarkMassSpawner.BP_EcsactBenchmarkMassSpawner_C + diff --git a/unreal-cpp-benchmark/Config/DefaultEditor.ini b/unreal-cpp-benchmark/Config/DefaultEditor.ini new file mode 100644 index 0000000..e69de29 diff --git a/unreal-cpp-benchmark/Config/DefaultEngine.ini b/unreal-cpp-benchmark/Config/DefaultEngine.ini new file mode 100644 index 0000000..ffd1b77 --- /dev/null +++ b/unreal-cpp-benchmark/Config/DefaultEngine.ini @@ -0,0 +1,93 @@ + + +[/Script/EngineSettings.GameMapsSettings] +GameDefaultMap=/Game/Maps/Benchmark.Benchmark +EditorStartupMap=/Game/Maps/Benchmark.Benchmark + +[/Script/Engine.RendererSettings] +r.AllowStaticLighting=False + +r.GenerateMeshDistanceFields=True + +r.DynamicGlobalIlluminationMethod=1 + +r.ReflectionMethod=1 + +r.SkinCache.CompileShaders=True + +r.RayTracing=True + +r.Shadow.Virtual.Enable=1 + +r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True + +r.DefaultFeature.LocalExposure.HighlightContrastScale=0.8 + +r.DefaultFeature.LocalExposure.ShadowContrastScale=0.8 + +[/Script/WindowsTargetPlatform.WindowsTargetSettings] +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +-D3D12TargetedShaderFormats=PCD3D_SM5 ++D3D12TargetedShaderFormats=PCD3D_SM6 +-D3D11TargetedShaderFormats=PCD3D_SM5 ++D3D11TargetedShaderFormats=PCD3D_SM5 +Compiler=Default +AudioSampleRate=48000 +AudioCallbackBufferFrameSize=1024 +AudioNumBuffersToEnqueue=1 +AudioMaxChannels=0 +AudioNumSourceWorkers=4 +SpatializationPlugin= +SourceDataOverridePlugin= +ReverbPlugin= +OcclusionPlugin= +CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0) +CacheSizeKB=65536 +MaxChunkSizeOverrideKB=0 +bResampleForDevice=False +MaxSampleRate=48000.000000 +HighSampleRate=32000.000000 +MedSampleRate=24000.000000 +LowSampleRate=12000.000000 +MinSampleRate=8000.000000 +CompressionQualityModifier=1.000000 +AutoStreamingThreshold=0.000000 +SoundCueCookQualityIndex=-1 + +[/Script/LinuxTargetPlatform.LinuxTargetSettings] +-TargetedRHIs=SF_VULKAN_SM5 ++TargetedRHIs=SF_VULKAN_SM6 + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/WorldPartitionEditor.WorldPartitionEditorSettings] +CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet' + +[/Script/Engine.UserInterfaceSettings] +bAuthorizeAutomaticWidgetVariableCreation=False +FontDPIPreset=Standard +FontDPI=72 + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/EcsactBenchmark") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/EcsactBenchmark") + +[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] +bEnablePlugin=True +bAllowNetworkConnection=True +SecurityToken=636BE87843D087D62604C5917E6F5CA0 +bIncludeInShipping=False +bAllowExternalStartInShipping=False +bCompileAFSProject=False +bUseCompression=False +bLogFiles=False +bReportStats=False +ConnectionType=USBOnly +bUseManualIPAddress=False +ManualIPAddress= + diff --git a/unreal-cpp-benchmark/Config/DefaultGame.ini b/unreal-cpp-benchmark/Config/DefaultGame.ini new file mode 100644 index 0000000..9ad2e90 --- /dev/null +++ b/unreal-cpp-benchmark/Config/DefaultGame.ini @@ -0,0 +1,3 @@ + +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=96F09A7E4BCF19A5F9C692A8E6975C7C diff --git a/unreal-cpp-benchmark/Config/DefaultInput.ini b/unreal-cpp-benchmark/Config/DefaultInput.ini new file mode 100644 index 0000000..a919105 --- /dev/null +++ b/unreal-cpp-benchmark/Config/DefaultInput.ini @@ -0,0 +1,84 @@ +[/Script/Engine.InputSettings] +-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) ++AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) +bAltEnterTogglesFullscreen=True +bF11TogglesFullscreen=True +bUseMouseForTouch=False +bEnableMouseSmoothing=True +bEnableFOVScaling=True +bCaptureMouseOnLaunch=True +bEnableLegacyInputScales=True +bEnableMotionControls=True +bFilterInputByPlatformUser=False +bShouldFlushPressedKeysOnViewportFocusLost=True +bAlwaysShowTouchInterface=False +bShowConsoleOnFourFingerTap=True +bEnableGestureRecognizer=False +bUseAutocorrect=False +DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown +DefaultViewportMouseLockMode=LockOnCapture +FOVScale=0.011110 +DoubleClickTime=0.200000 +DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput +DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent +DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde + diff --git a/unreal-cpp-benchmark/Content/Blueprints/BP_EcsactBenchmarkMassSpawner.uasset b/unreal-cpp-benchmark/Content/Blueprints/BP_EcsactBenchmarkMassSpawner.uasset new file mode 100644 index 0000000..67269be Binary files /dev/null and b/unreal-cpp-benchmark/Content/Blueprints/BP_EcsactBenchmarkMassSpawner.uasset differ diff --git a/unreal-cpp-benchmark/Content/Blueprints/BP_StartButton.uasset b/unreal-cpp-benchmark/Content/Blueprints/BP_StartButton.uasset new file mode 100644 index 0000000..ab7d4e0 Binary files /dev/null and b/unreal-cpp-benchmark/Content/Blueprints/BP_StartButton.uasset differ diff --git a/unreal-cpp-benchmark/Content/Blueprints/StartButton.uasset b/unreal-cpp-benchmark/Content/Blueprints/StartButton.uasset new file mode 100644 index 0000000..0ceeb1e Binary files /dev/null and b/unreal-cpp-benchmark/Content/Blueprints/StartButton.uasset differ diff --git a/unreal-cpp-benchmark/Content/Maps/Benchmark.umap b/unreal-cpp-benchmark/Content/Maps/Benchmark.umap new file mode 100644 index 0000000..3304a80 Binary files /dev/null and b/unreal-cpp-benchmark/Content/Maps/Benchmark.umap differ diff --git a/unreal-cpp-benchmark/Content/Mass/BenchmarkConfig.uasset b/unreal-cpp-benchmark/Content/Mass/BenchmarkConfig.uasset new file mode 100644 index 0000000..22008b8 Binary files /dev/null and b/unreal-cpp-benchmark/Content/Mass/BenchmarkConfig.uasset differ diff --git a/unreal-cpp-benchmark/EcsactBenchmark.uproject b/unreal-cpp-benchmark/EcsactBenchmark.uproject new file mode 100644 index 0000000..106e5be --- /dev/null +++ b/unreal-cpp-benchmark/EcsactBenchmark.uproject @@ -0,0 +1,35 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.5", + "Category": "", + "Description": "", + "PreBuildSteps": { + "Win64": [ + "EcsactUnrealCodegen $(ProjectDir) --engine-dir $(EngineDir) || exit /b 1" + ] + }, + "Modules": [ + { + "Name": "EcsactBenchmark", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "ModelingToolsEditorMode", + "Enabled": true, + "TargetAllowList": [ + "Editor" + ] + }, + { + "Name": "MassGameplay", + "Enabled": true + }, + { + "Name": "Ecsact", + "Enabled": true + } + ] +} diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark.Target.cs b/unreal-cpp-benchmark/Source/EcsactBenchmark.Target.cs new file mode 100644 index 0000000..43abcdc --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark.Target.cs @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class EcsactBenchmarkTarget : TargetRules +{ + public EcsactBenchmarkTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Game; + DefaultBuildSettings = BuildSettingsVersion.V5; + IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_5; + ExtraModuleNames.Add("EcsactBenchmark"); + } +} diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.Build.cs b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.Build.cs new file mode 100644 index 0000000..5657b46 --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.Build.cs @@ -0,0 +1,23 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class EcsactBenchmark : ModuleRules +{ + public EcsactBenchmark(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "MassEntity", "Ecsact", "MassSpawner" }); + + PrivateDependencyModuleNames.AddRange(new string[] { }); + + // Uncomment if you are using Slate UI + // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); + + // Uncomment if you are using online features + // PrivateDependencyModuleNames.Add("OnlineSubsystem"); + + // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true + } +} diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.cpp b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.cpp new file mode 100644 index 0000000..75d4321 --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.cpp @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "EcsactBenchmark.h" +#include "EcsactUnreal/EcsactGameModuleImpl.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_PRIMARY_GAME_MODULE(FEcsactGameModuleImpl, EcsactBenchmark, + "EcsactBenchmark"); diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.ecsact b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.ecsact new file mode 100644 index 0000000..9f17bc0 --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.ecsact @@ -0,0 +1,12 @@ +main package benchmark; + +component Counter { + i32 value; +} +component MassOnly; + +system IterateCounter { + exclude MassOnly; + readwrite Counter; +} + diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.ecsact.hh b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.ecsact.hh new file mode 100644 index 0000000..b89e28d --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.ecsact.hh @@ -0,0 +1,29 @@ +// GENERATED FILE - DO NOT EDIT +#pragma once + +#include +#include +#include "ecsact/runtime/common.h" + +namespace benchmark { + +struct Counter { + static constexpr bool transient = false; + static constexpr bool has_assoc_fields = false; + static constexpr auto id = static_cast(1); + int32_t value; + auto operator<=>(const benchmark::Counter&) const = default; +}; +struct MassOnly { + static constexpr bool transient = false; + static constexpr bool has_assoc_fields = false; + static constexpr auto id = static_cast(2); + auto operator<=>(const benchmark::MassOnly&) const = default; +}; +struct IterateCounter { + static constexpr auto id = static_cast(3); + struct context; + static void impl(context&); +}; + +}// namespace benchmark diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.h b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.h new file mode 100644 index 0000000..677c8e2 --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark.h @@ -0,0 +1,6 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkMassSpawner.cpp b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkMassSpawner.cpp new file mode 100644 index 0000000..60a55c1 --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkMassSpawner.cpp @@ -0,0 +1,170 @@ + +#include "EcsactBenchmarkMassSpawner.h" +#include "Ecsact/Private/EcsactGameInstanceSubsystem.h" +#include "EcsactBenchmark/EcsactBenchmark.ecsact.hh" +#include "EcsactBenchmark/EcsactBenchmark__ecsact__ue.h" +#include "EcsactBenchmarkRunner.h" +#include "EcsactUnreal/EcsactExecution.h" +#include "EcsactUnreal/EcsactRunner.h" +#include "EcsactUnreal/EcsactUnrealExecutionOptions.h" +#include "Logging/LogVerbosity.h" +#include "MassEntitySubsystem.h" +#include "MassSpawnerSubsystem.h" +#include "Fragments/MassCounterFragments.h" +#include "ecsact/si/wasm.h" +#include + +UEcsactBenchmarkMassSpawner::UEcsactBenchmarkMassSpawner() { +} + +auto UEcsactBenchmarkMassSpawner::CreateEcsactEntities(int count) -> void { + auto runner = GetRunner(); + check(runner); + + GetWorld()->GetTimerManager().SetTimer( + RunTimer, + [this]() { + UE_LOG(LogTemp, Log, TEXT("Stop dat runner")); + auto runner = GetRunner(); + runner->Stop(); + }, + 5.0f, + false + ); + + for(int i = 0; i < count; ++i) { + runner->CreateEntity().AddComponent({.value = 0}); + } + UE_LOG(LogTemp, Log, TEXT("ECSACT: Created %i entities"), count); +} + +auto UEcsactBenchmarkMassSpawner::CreateMassEntities(int count) -> void { + auto* world = GetWorld(); + check(world); + + const auto& entity_template = config->GetOrCreateEntityTemplate(*world); + + auto mass_spawner = world->GetSubsystem(); + + // GetWorld()->GetTimerManager().SetTimer( + // RunTimer, + // [this]() { + // UE_LOG(LogTemp, Log, TEXT("Remove mass tag")); + // auto& entity_manager = // + // GetWorld() + // ->GetSubsystem() + // ->GetMutableEntityManager(); + // for(auto entity_handle : mass_entities) { + // entity_manager.Defer().RemoveTag(entity_handle); + // } + // }, + // 5.0f, + // false + // ); + + for(int i = 0; i < count; ++i) { + auto new_entity_handles = TArray{}; + mass_spawner->SpawnEntities(entity_template, 1, new_entity_handles); + mass_entities.Add(new_entity_handles[0]); + } + UE_LOG(LogTemp, Log, TEXT("MASS: Created %i entities"), count); +} + +auto UEcsactBenchmarkMassSpawner::LoadWASMFiles() -> void { + auto cwd = FString{std::filesystem::current_path().string().c_str()}; + + UE_LOG(LogTemp, Log, TEXT("CWD %s"), *cwd); + auto file_path = FString{ + "C:/Users/Austin/Documents/programming/ecsact/ecsact-examples/" + "unreal-cpp-benchmark/Binaries/SystemImpls.wasm" + }; + + auto err = ecsact_si_wasm_load_file( + const_cast(TCHAR_TO_UTF8(*file_path)), + EcsactUnreal::CodegenMeta::BenchmarkSystemLikeIds.size(), + const_cast( + EcsactUnreal::CodegenMeta::BenchmarkSystemLikeIds.data() + ), + const_cast( + EcsactUnreal::CodegenMeta::BenchmarkExportNames.data() + ) + ); + + if(err != ECSACT_SI_WASM_OK) { +#define HANDLE_ECSACT_SI_ERROR_CASE(err) \ + case err: \ + UE_LOG(LogTemp, Error, TEXT("ecsact_si_wasm_load_file error: " #err)); \ + break + switch(err) { + HANDLE_ECSACT_SI_ERROR_CASE(ECSACT_SI_WASM_ERR_FILE_OPEN_FAIL); + HANDLE_ECSACT_SI_ERROR_CASE(ECSACT_SI_WASM_ERR_FILE_READ_FAIL); + HANDLE_ECSACT_SI_ERROR_CASE(ECSACT_SI_WASM_ERR_COMPILE_FAIL); + HANDLE_ECSACT_SI_ERROR_CASE(ECSACT_SI_WASM_ERR_INSTANTIATE_FAIL); + HANDLE_ECSACT_SI_ERROR_CASE(ECSACT_SI_WASM_ERR_EXPORT_NOT_FOUND); + HANDLE_ECSACT_SI_ERROR_CASE(ECSACT_SI_WASM_ERR_EXPORT_INVALID); + HANDLE_ECSACT_SI_ERROR_CASE(ECSACT_SI_WASM_ERR_GUEST_IMPORT_UNKNOWN); + HANDLE_ECSACT_SI_ERROR_CASE(ECSACT_SI_WASM_ERR_GUEST_IMPORT_INVALID); + HANDLE_ECSACT_SI_ERROR_CASE(ECSACT_SI_WASM_ERR_INITIALIZE_FAIL); + HANDLE_ECSACT_SI_ERROR_CASE(ECSACT_SI_WASM_ERR_NO_SET_SYSTEM_EXECUTION); + case ECSACT_SI_WASM_OK: + break; + } +#undef HANDLE_ECSACT_SI_ERROR_CASE + } +} + +void UEcsactBenchmarkMassSpawner::InitCounter_Implementation( + int32 Entity, + FBenchmarkCounter +) { + // UE_LOG(LogTemp, Log, TEXT("Counter init on entity %i"), Entity); +} + +void UEcsactBenchmarkMassSpawner::UpdateCounter_Implementation( + int32 Entity, + FBenchmarkCounter Counter +) { + // UE_LOG( + // LogTemp, + // Log, + // TEXT("Counter updated on entity %i to %i"), + // Entity, + // Counter.Value + // ); +} + +void UEcsactBenchmarkMassSpawner::StartMassSpawner( // + UObject* WorldContext, + EcsactBenchmarkType BenchmarkType +) { + UE_LOG( + LogTemp, + Warning, + TEXT("BENCHMARKTYPE: %s"), + *UEnum::GetValueAsString(BenchmarkType) + ); + + auto* world = WorldContext->GetWorld(); + check(world); + + auto ecsact_game_subsystem = + world->GetGameInstance()->GetSubsystem(); + check(ecsact_game_subsystem); + + ecsact_game_subsystem->StartCustomRunner(); + auto runner = EcsactUnrealExecution::Runner(world); + + if(!runner.IsValid()) { + UE_LOG(LogTemp, Error, TEXT("Provided runner is invalid")); + return; + } + + auto mass_spawner_subsystem = + runner->GetSubsystem(); + + if(mass_spawner_subsystem) { + mass_spawner_subsystem->OnStartButtonPressed(BenchmarkType); + } else { + UE_LOG(LogTemp, Error, TEXT("BenchmarkMassSpawner can't be found")); + } +} diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkMassSpawner.h b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkMassSpawner.h new file mode 100644 index 0000000..6312d2c --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkMassSpawner.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include "CoreMinimal.h" +#include "MassEntityConfigAsset.h" +#include "ecsact/runtime/core.hh" + +#include "EcsactBenchmark__ecsact__mass__ue.h" +#include "EcsactBenchmark__ecsact__ue.h" + +#include "EcsactBenchmarkMassSpawner.generated.h" + +UENUM(BlueprintType) +enum class EcsactBenchmarkType : uint8 { + EcsactCounter UMETA(DisplayName = "Ecsact Counter"), + MassCounter UMETA(DisplayName = "Mass Counter") +}; + +UCLASS(Abstract) + +class UEcsactBenchmarkMassSpawner : public UBenchmarkMassSpawner { + GENERATED_BODY() + UEcsactBenchmarkMassSpawner(); + + TArray entities; + TArray mass_entities; + + FTimerHandle RunTimer; + + UPROPERTY(EditAnywhere) + UMassEntityConfigAsset* config; + +public: + UFUNCTION(BlueprintCallable, Meta = (WorldContext = "WorldContext")) + static void StartMassSpawner( + UObject* WorldContext, + EcsactBenchmarkType BenchmarkType + ); + + UFUNCTION(BlueprintCallable) + void CreateMassEntities(int count); + + UFUNCTION(BlueprintCallable) + void CreateEcsactEntities(int count); + + UFUNCTION(BlueprintCallable) + void LoadWASMFiles(); + + UFUNCTION(BlueprintImplementableEvent) + void OnStartButtonPressed(EcsactBenchmarkType BenchmarkType); + + void InitCounter_Implementation(int32 Entity, FBenchmarkCounter) override; + void UpdateCounter_Implementation(int32 Entity, FBenchmarkCounter) override; +}; diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkRunner.cpp b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkRunner.cpp new file mode 100644 index 0000000..bdd851e --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkRunner.cpp @@ -0,0 +1,33 @@ +#include "EcsactBenchmarkRunner.h" +#include "ecsact/runtime/core.h" +#include "ecsact/runtime/core.hh" + +auto UEcsactBenchmarkRunner::Start() -> void { + Super::Start(); + should_run = true; + registry.emplace("Benchmark"); +} + +auto UEcsactBenchmarkRunner::Stop() -> void { + Super::Stop(); + should_run = false; +} + +auto UEcsactBenchmarkRunner::Tick(float deltaTime) -> void { + Super::Tick(deltaTime); + if(ecsact_execute_systems && should_run) { + ecsact_execution_options* exec_opts = nullptr; + if(ExecutionOptions != nullptr && ExecutionOptions->IsNotEmpty()) { + exec_opts = ExecutionOptions->GetCPtr(); + } + auto err = ecsact_execute_systems( // + registry->id(), + 1, + exec_opts, + GetEventsCollector() + ); + if(ExecutionOptions != nullptr) { + ExecutionOptions->Clear(); + } + } +} diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkRunner.h b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkRunner.h new file mode 100644 index 0000000..f394f39 --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmarkRunner.h @@ -0,0 +1,21 @@ +#pragma once + +#include "CoreMinimal.h" +#include "EcsactUnreal/EcsactRunner.h" +#include "ecsact/runtime/core.hh" + +#include "EcsactBenchmarkRunner.generated.h" + +UCLASS() + +class ECSACTBENCHMARK_API UEcsactBenchmarkRunner : public UEcsactRunner { + GENERATED_BODY() + + std::optional registry; + + auto Start() -> void override; + auto Stop() -> void override; + auto Tick(float deltaTime) -> void override; + + bool should_run; +}; diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__mass__ue.cpp b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__mass__ue.cpp new file mode 100644 index 0000000..6d99a25 --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__mass__ue.cpp @@ -0,0 +1,128 @@ +#include "EcsactBenchmark__ecsact__mass__ue.h" +#include "Engine/World.h" +#include "MassEntitySubsystem.h" +#include "MassSpawnerSubsystem.h" +#include "MassCommandBuffer.h" + +void UBenchmarkMassSpawner::InitCounter_Implementation(int32 Entity, FBenchmarkCounter Counter) + { + auto* world = GetWorld(); + check(world); + + auto& entity_manager = world->GetSubsystem()->GetMutableEntityManager(); + auto entity_handles = GetEcsactMassEntityHandles(Entity); + for(auto entity_handle : entity_handles) { + entity_manager.Defer().AddFragment(entity_handle); + entity_manager.Defer().PushCommand(entity_handle, FBenchmarkCounterFragment{Counter}); + + } +} +void UBenchmarkMassSpawner::UpdateCounter_Implementation(int32 Entity, FBenchmarkCounter Counter) + { + auto* world = GetWorld(); + check(world); + + auto& entity_manager = world->GetSubsystem()->GetMutableEntityManager(); + auto entity_handles = GetEcsactMassEntityHandles(Entity); + for(auto entity_handle : entity_handles) { + entity_manager.Defer().PushCommand([this, Entity, entity_handle, Counter](FMassEntityManager& entity_manager) + { + auto fragment = entity_manager.GetFragmentDataPtr(entity_handle); + if(!fragment) { + UE_LOG(Ecsact, Warning, TEXT("%s fragment FBenchmarkCounterFragment does not exist for entity %i"), *GetClass()->GetName(), Entity); + return; + } + fragment->component = Counter; + + }); + + } +} +void UBenchmarkMassSpawner::RemoveCounter_Implementation(int32 Entity, FBenchmarkCounter Counter) + { + auto* world = GetWorld(); + check(world); + + auto& entity_manager = world->GetSubsystem()->GetMutableEntityManager(); + auto entity_handles = GetEcsactMassEntityHandles(Entity); + for(auto entity_handle : entity_handles) { + entity_manager.Defer().RemoveFragment(entity_handle); + + } +} +void UBenchmarkMassSpawner::InitMassonly_Implementation(int32 Entity, FBenchmarkMassonly Massonly) + { + auto* world = GetWorld(); + check(world); + + auto& entity_manager = world->GetSubsystem()->GetMutableEntityManager(); + auto entity_handles = GetEcsactMassEntityHandles(Entity); + for(auto entity_handle : entity_handles) { + entity_manager.Defer().AddTag(entity_handle); + + } +} +void UBenchmarkMassSpawner::RemoveMassonly_Implementation(int32 Entity, FBenchmarkMassonly Massonly) + { + auto* world = GetWorld(); + check(world); + + auto& entity_manager = world->GetSubsystem()->GetMutableEntityManager(); + auto entity_handles = GetEcsactMassEntityHandles(Entity); + for(auto entity_handle : entity_handles) { + entity_manager.Defer().RemoveTag(entity_handle); + + } +} +auto UBenchmarkMassSpawner::GetEcsactMassEntityHandles(int32 Entity) -> TArray { + UE_LOG(LogTemp, Error, TEXT("GetEcsactMassEntityHandless must be implemented for EcsactMassEntitySpawner")); + return TArray{}; + +} +auto UOneToOneBenchmarkMassSpawner::EntityCreated_Implementation(int32 Entity) -> void { + auto* world = GetWorld(); + check(world); + + auto* config = GetEntityMassConfig(); + if(!config) { + UE_LOG(Ecsact, Warning, TEXT("%s GetEntityMassConfig() returned null"), *GetClass()->GetName()); + return; + } + const auto& entity_template = config->GetOrCreateEntityTemplate(*world); + auto new_entity_handles = TArray{}; + + auto mass_spawner = world->GetSubsystem(); + auto& entity_manager = world->GetSubsystem()->GetMutableEntityManager(); + + mass_spawner->SpawnEntities(entity_template, 1, new_entity_handles); + MassEntities.Add(static_cast(Entity), new_entity_handles); + for(auto entity_handle : new_entity_handles) { + entity_manager.Defer().AddFragment(entity_handle); + entity_manager.Defer().PushCommand(entity_handle, FEcsactEntityFragment{static_cast(Entity)}); + ; + + } +}auto UOneToOneBenchmarkMassSpawner::EntityDestroyed_Implementation(int32 Entity) -> void { + auto* world = GetWorld(); + check(world); + + auto& entity_manager = world->GetSubsystem()->GetMutableEntityManager(); + + auto old_entity_handles = TArray{}; + MassEntities.RemoveAndCopyValue(static_cast(Entity),old_entity_handles); + for(auto entity_handle : old_entity_handles) { + entity_manager.Defer().DestroyEntity(entity_handle); + + } +} +auto UOneToOneBenchmarkMassSpawner::GetEcsactMassEntityHandles(int32 Entity) -> TArray { + auto handles = MassEntities.Find(static_cast(Entity)); + if(!handles) return {}; + return *handles; + +} +auto UOneToOneBenchmarkMassSpawner::GetEntityMassConfig() const -> UMassEntityConfigAsset* + { + return MassEntityConfigAsset; + +} \ No newline at end of file diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__mass__ue.h b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__mass__ue.h new file mode 100644 index 0000000..ba40f9e --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__mass__ue.h @@ -0,0 +1,66 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MassEntityTypes.h" +#include "MassEntityConfigAsset.h" +#include "ecsact/runtime/common.h" +#include "EcsactBenchmark__ecsact__ue.h" +#include "EcsactBenchmark.ecsact.hh" +#include "EcsactBenchmark__ecsact__mass__ue.generated.h" + +USTRUCT() +struct FEcsactEntityFragment : public FMassFragment { + GENERATED_BODY() + FEcsactEntityFragment() = default; + FEcsactEntityFragment(const ecsact_entity_id EntityId) : id(EntityId) {} + + ecsact_entity_id GetId() const { + return id; + + } + private: + ecsact_entity_id id; +}; +USTRUCT() +struct FBenchmarkCounterFragment : public FMassFragment { + GENERATED_BODY() + + FBenchmarkCounterFragment() = default; + FBenchmarkCounterFragment(FBenchmarkCounter in_component) : component(in_component){} + + FBenchmarkCounter component; +}; +USTRUCT() +struct FBenchmarkMassonlyFragment : public FMassTag { + GENERATED_BODY() + +}; + +UCLASS(Abstract) +class UBenchmarkMassSpawner : public UBenchmarkEcsactRunnerSubsystem { + GENERATED_BODY() + + public: + virtual auto GetEcsactMassEntityHandles(int32 Entity) -> TArray; + void InitCounter_Implementation(int32 Entity, FBenchmarkCounter Counter) override; + void UpdateCounter_Implementation(int32 Entity, FBenchmarkCounter Counter) override; + void RemoveCounter_Implementation(int32 Entity, FBenchmarkCounter Counter) override; + void InitMassonly_Implementation(int32 Entity, FBenchmarkMassonly Massonly) override; + void RemoveMassonly_Implementation(int32 Entity, FBenchmarkMassonly Massonly) override; + +}; + +UCLASS(Abstract) +class UOneToOneBenchmarkMassSpawner : public UBenchmarkMassSpawner { + GENERATED_BODY() + + TMap> MassEntities; + protected: + UPROPERTY(EditAnywhere) + UMassEntityConfigAsset* MassEntityConfigAsset; + auto EntityCreated_Implementation(int32 Entity) -> void override; + auto EntityDestroyed_Implementation(int32 Entity) -> void override; + auto GetEcsactMassEntityHandles(int32 Entity) -> TArray override; + virtual auto GetEntityMassConfig() const -> UMassEntityConfigAsset*; + +}; diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__ue.cpp b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__ue.cpp new file mode 100644 index 0000000..de874ef --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__ue.cpp @@ -0,0 +1,77 @@ +#include "EcsactBenchmark__ecsact__ue.h" +FBenchmarkCounter FBenchmarkCounter::FromEcsactComponentData(const void* component_data) { + auto result = FBenchmarkCounter{}; + result.Value = static_cast(component_data)->value; + return result; +} +FBenchmarkMassonly FBenchmarkMassonly::FromEcsactComponentData(const void* component_data) { + auto result = FBenchmarkMassonly{}; + return result; +} +UBenchmarkEcsactRunnerSubsystem::UBenchmarkEcsactRunnerSubsystem() { + InitComponentFns.Init(nullptr, 3); + UpdateComponentFns.Init(nullptr, 3); + RemoveComponentFns.Init(nullptr, 3); + InitComponentFns[1] = &ThisClass::RawInitCounter; + UpdateComponentFns[1] = &ThisClass::RawUpdateCounter; + RemoveComponentFns[1] = &ThisClass::RawRemoveCounter; + InitComponentFns[2] = &ThisClass::RawInitMassonly; + UpdateComponentFns[2] = &ThisClass::RawUpdateMassonly; + RemoveComponentFns[2] = &ThisClass::RawRemoveMassonly; + +} + +void UBenchmarkEcsactRunnerSubsystem::InitComponentRaw( ecsact_entity_id entity, ecsact_component_id component_id, const void* component_data) { + (this->*InitComponentFns[static_cast(component_id)])(static_cast(entity), component_data); +} + +void UBenchmarkEcsactRunnerSubsystem::UpdateComponentRaw( ecsact_entity_id entity, ecsact_component_id component_id, const void* component_data) { + (this->*UpdateComponentFns[static_cast(component_id)])(static_cast(entity), component_data); +} + +void UBenchmarkEcsactRunnerSubsystem::RemoveComponentRaw( ecsact_entity_id entity, ecsact_component_id component_id, const void* component_data) { + (this->*RemoveComponentFns[static_cast(component_id)])(static_cast(entity), component_data); +} + +void UBenchmarkEcsactRunnerSubsystem::RawInitCounter(int32 entity, const void* component) { + InitCounter(entity, FBenchmarkCounter::FromEcsactComponentData(component)); +} +void UBenchmarkEcsactRunnerSubsystem::RawUpdateCounter(int32 entity, const void* component) { + UpdateCounter(entity, FBenchmarkCounter::FromEcsactComponentData(component)); +} +void UBenchmarkEcsactRunnerSubsystem::RawRemoveCounter(int32 entity, const void* component) { + RemoveCounter(entity, FBenchmarkCounter::FromEcsactComponentData(component)); +} +void UBenchmarkEcsactRunnerSubsystem::RawInitMassonly(int32 entity, const void* component) { + InitMassonly(entity, FBenchmarkMassonly::FromEcsactComponentData(component)); +} +void UBenchmarkEcsactRunnerSubsystem::RawUpdateMassonly(int32 entity, const void* component) { + UpdateMassonly(entity, FBenchmarkMassonly::FromEcsactComponentData(component)); +} +void UBenchmarkEcsactRunnerSubsystem::RawRemoveMassonly(int32 entity, const void* component) { + RemoveMassonly(entity, FBenchmarkMassonly::FromEcsactComponentData(component)); +} +void UBenchmarkEcsactRunnerSubsystem::InitCounter_Implementation(int32 Entity, FBenchmarkCounter Counter) { + +} + +void UBenchmarkEcsactRunnerSubsystem::UpdateCounter_Implementation(int32 Entity, FBenchmarkCounter Counter) { + +} + +void UBenchmarkEcsactRunnerSubsystem::RemoveCounter_Implementation(int32 Entity, FBenchmarkCounter Counter) { + +} + +void UBenchmarkEcsactRunnerSubsystem::InitMassonly_Implementation(int32 Entity, FBenchmarkMassonly Massonly) { + +} + +void UBenchmarkEcsactRunnerSubsystem::UpdateMassonly_Implementation(int32 Entity, FBenchmarkMassonly Massonly) { + +} + +void UBenchmarkEcsactRunnerSubsystem::RemoveMassonly_Implementation(int32 Entity, FBenchmarkMassonly Massonly) { + +} + diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__ue.h b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__ue.h new file mode 100644 index 0000000..1f9563f --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/EcsactBenchmark__ecsact__ue.h @@ -0,0 +1,83 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include +#include "ecsact/runtime/common.h" +#include "EcsactUnreal/Ecsact.h" +#include "EcsactUnreal/EcsactRunnerSubsystem.h" +#include "EcsactBenchmark.ecsact.hh" +#include "EcsactBenchmark__ecsact__ue.generated.h" + + +namespace EcsactUnreal::CodegenMeta { + constexpr auto BenchmarkSystemLikeIds = std::array{ + static_cast(3 /* benchmark.IterateCounter */), + }; + + constexpr auto BenchmarkExportNames = std::array{ + "benchmark__IterateCounter", + }; + +} + +USTRUCT(BlueprintType) +struct FBenchmarkCounter { + GENERATED_BODY() + + static FBenchmarkCounter FromEcsactComponentData(const void*); + UPROPERTY(EditAnywhere, BlueprintReadWrite) + int32 Value; + +}; + +USTRUCT(BlueprintType) +struct FBenchmarkMassonly { + GENERATED_BODY() + + static FBenchmarkMassonly FromEcsactComponentData(const void*); + +}; + +UCLASS(Abstract, Blueprintable, meta = (DisplayName = "Ecsact Runner Package Subsystem (benchmark)")) +class UBenchmarkEcsactRunnerSubsystem : public UEcsactRunnerSubsystem { + GENERATED_BODY() // NOLINT + + TArray InitComponentFns; + TArray UpdateComponentFns; + TArray RemoveComponentFns; + void RawInitCounter(int32 Entity, const void* Component); + void RawUpdateCounter(int32 Entity, const void* Component); + void RawRemoveCounter(int32 Entity, const void* Component); + void RawInitMassonly(int32 Entity, const void* Component); + void RawUpdateMassonly(int32 Entity, const void* Component); + void RawRemoveMassonly(int32 Entity, const void* Component); + +protected: + void InitComponentRaw(ecsact_entity_id, ecsact_component_id, const void*) override; + void UpdateComponentRaw(ecsact_entity_id, ecsact_component_id, const void*) override; + void RemoveComponentRaw(ecsact_entity_id, ecsact_component_id, const void*) override; + + +public: + UBenchmarkEcsactRunnerSubsystem(); + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Init benchmark.Counter")) + void InitCounter(int32 Entity, FBenchmarkCounter Counter); + virtual void InitCounter_Implementation(int32 Entity, FBenchmarkCounter Counter); + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Update benchmark.Counter")) + void UpdateCounter(int32 Entity, FBenchmarkCounter Counter); + virtual void UpdateCounter_Implementation(int32 Entity, FBenchmarkCounter Counter); + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Remove benchmark.Counter")) + void RemoveCounter(int32 Entity, FBenchmarkCounter Counter); + virtual void RemoveCounter_Implementation(int32 Entity, FBenchmarkCounter Counter); + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Init benchmark.MassOnly")) + void InitMassonly(int32 Entity, FBenchmarkMassonly Massonly); + virtual void InitMassonly_Implementation(int32 Entity, FBenchmarkMassonly Massonly); + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Update benchmark.MassOnly")) + void UpdateMassonly(int32 Entity, FBenchmarkMassonly Massonly); + virtual void UpdateMassonly_Implementation(int32 Entity, FBenchmarkMassonly Massonly); + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Remove benchmark.MassOnly")) + void RemoveMassonly(int32 Entity, FBenchmarkMassonly Massonly); + virtual void RemoveMassonly_Implementation(int32 Entity, FBenchmarkMassonly Massonly); + +}; diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/Fragments/MassCounterFragments.h b/unreal-cpp-benchmark/Source/EcsactBenchmark/Fragments/MassCounterFragments.h new file mode 100644 index 0000000..1b37433 --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/Fragments/MassCounterFragments.h @@ -0,0 +1,11 @@ +#pragma once + +#include "MassEntityTypes.h" + +#include "MassCounterFragments.generated.h" + +USTRUCT() + +struct ECSACTBENCHMARK_API FRunMassCounter : public FMassTag { + GENERATED_BODY() +}; diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/Processors/MassBenchmarkProcessor.cpp b/unreal-cpp-benchmark/Source/EcsactBenchmark/Processors/MassBenchmarkProcessor.cpp new file mode 100644 index 0000000..2cc968c --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/Processors/MassBenchmarkProcessor.cpp @@ -0,0 +1,47 @@ +#include "MassBenchmarkProcessor.h" +#include "../EcsactBenchmark__ecsact__mass__ue.h" +#include "EcsactBenchmark/EcsactBenchmark__ecsact__ue.h" +#include "MassArchetypeTypes.h" + +#include "../Fragments/MassCounterFragments.h" +#include "MassEntityView.h" +#include "MassExecutionContext.h" +#include "MassRequirements.h" + +UMassBenchmarkProcessor::UMassBenchmarkProcessor() : EntityQuery(*this) { +} + +void UMassBenchmarkProcessor::ConfigureQueries() { + EntityQuery.AddRequirement( + EMassFragmentAccess::ReadWrite + ); + EntityQuery.AddTagRequirement(EMassFragmentPresence::All); +} + +void UMassBenchmarkProcessor::Execute( + FMassEntityManager& EntityManager, + FMassExecutionContext& Context +) { + EntityQuery.ForEachEntityChunk( // + EntityManager, + Context, + [](FMassExecutionContext& context) { + const auto entity_num = context.GetNumEntities(); + auto counter_fragments = + context.GetMutableFragmentView(); + + for(int i = 0; i < entity_num; ++i) { + auto& counter_fragment = counter_fragments[i]; + + counter_fragment.component.Value += 1; + + // UE_LOG( + // LogTemp, + // Warning, + // TEXT("MASS: Updated entity counter to %i"), + // counter_fragment.component.Value + // ); + } + } + ); +} diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmark/Processors/MassBenchmarkProcessor.h b/unreal-cpp-benchmark/Source/EcsactBenchmark/Processors/MassBenchmarkProcessor.h new file mode 100644 index 0000000..575699a --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmark/Processors/MassBenchmarkProcessor.h @@ -0,0 +1,22 @@ + +#pragma once +#include "MassEntityQuery.h" +#include "MassProcessor.h" + +#include "MassBenchmarkProcessor.generated.h" + +UCLASS() +class ECSACTBENCHMARK_API UMassBenchmarkProcessor : public UMassProcessor { + GENERATED_BODY() + +public: + UMassBenchmarkProcessor(); + +protected: + virtual void ConfigureQueries() override; + virtual void Execute(FMassEntityManager &EntityManager, + FMassExecutionContext &Context) override; + +private: + FMassEntityQuery EntityQuery; +}; diff --git a/unreal-cpp-benchmark/Source/EcsactBenchmarkEditor.Target.cs b/unreal-cpp-benchmark/Source/EcsactBenchmarkEditor.Target.cs new file mode 100644 index 0000000..a5793e9 --- /dev/null +++ b/unreal-cpp-benchmark/Source/EcsactBenchmarkEditor.Target.cs @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class EcsactBenchmarkEditorTarget : TargetRules +{ + public EcsactBenchmarkEditorTarget( TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + DefaultBuildSettings = BuildSettingsVersion.V5; + IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_5; + ExtraModuleNames.Add("EcsactBenchmark"); + } +} diff --git a/unreal-cpp-benchmark/SystemImpls/EcsactSystemImpls.cpp b/unreal-cpp-benchmark/SystemImpls/EcsactSystemImpls.cpp new file mode 100644 index 0000000..139094d --- /dev/null +++ b/unreal-cpp-benchmark/SystemImpls/EcsactSystemImpls.cpp @@ -0,0 +1,10 @@ + +#include "generated/EcsactBenchmark.ecsact.hh" +#include "generated/EcsactBenchmark.ecsact.systems.hh" + +auto benchmark::IterateCounter::impl(context &ctx) -> void { + auto counter = ctx.get(); + + counter.value += 1; + ctx.update(counter); +} diff --git a/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.hh b/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.hh new file mode 100644 index 0000000..05e9804 --- /dev/null +++ b/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.hh @@ -0,0 +1,23 @@ +// GENERATED FILE - DO NOT EDIT +#pragma once + +#include +#include +#include "ecsact/runtime/common.h" + +namespace benchmark { + +struct Counter { + static constexpr bool transient = false; + static constexpr bool has_assoc_fields = false; + static constexpr auto id = static_cast(1); + int32_t value; + auto operator<=>(const benchmark::Counter&) const = default; +}; +struct IterateCounter { + static constexpr auto id = static_cast(2); + struct context; + static void impl(context&); +}; + +}// namespace benchmark diff --git a/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.systems.cc b/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.systems.cc new file mode 100644 index 0000000..07b5ac2 --- /dev/null +++ b/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.systems.cc @@ -0,0 +1,6 @@ +// GENERATED FILE - DO NOT EDIT +#include "EcsactBenchmark.ecsact.systems.hh" +void benchmark__IterateCounter (struct ecsact_system_execution_context* cctx) { + benchmark::IterateCounter::context ctx{cctx}; + benchmark::IterateCounter::impl(ctx); +} diff --git a/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.systems.h b/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.systems.h new file mode 100644 index 0000000..bffafbb --- /dev/null +++ b/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.systems.h @@ -0,0 +1,12 @@ +// GENERATED FILE - DO NOT EDIT +#ifndef BENCHMARK_H +#define BENCHMARK_H + +#include "ecsact/runtime/common.h" + + +ECSACT_EXTERN +ECSACT_EXPORT("benchmark__IterateCounter") +void benchmark__IterateCounter(struct ecsact_system_execution_context*); + +#endif // BENCHMARK_H diff --git a/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.systems.hh b/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.systems.hh new file mode 100644 index 0000000..ad64458 --- /dev/null +++ b/unreal-cpp-benchmark/SystemImpls/generated/EcsactBenchmark.ecsact.systems.hh @@ -0,0 +1,53 @@ +// GENERATED FILE - DO NOT EDIT +#pragma once + +#include +#include "ecsact/cpp/execution_context.hh" +#include "EcsactBenchmark.ecsact.hh" +#include "EcsactBenchmark.ecsact.systems.h" + + +struct ecsact_system_execution_context; + +struct benchmark::IterateCounter::context { + [[no_unique_address]] ::ecsact::execution_context _ctx; + + template + auto get(AssocFields&&... assoc_fields) -> T { + // local type to make static assert always fail + struct codegen_error {}; + static_assert(std::is_same_v, + "| [Ecsact C++ Error]: System Execution Context Misuse\n" + "| benchmark.IterateCounter context.get may only be called with a component\n" + "| readable by the system. Did you forget to add readonly or readwrite\n" + "| capabilities? The following components are allowed:\n" + "| - benchmark.Counter\n" + "| \n"); + } + + template + auto update(const T& updated_component, AssocFields&&... assoc_fields) -> void { + // local type to make static assert always fail + struct codegen_error {}; + static_assert(std::is_same_v, + "| [Ecsact C++ Error]: System Execution Context Misuse\n" + "| benchmark.IterateCounter context.update may only be called with a\n" + "| component writable by the system. Did you forget to add readwrite capabilities? The\n" + "| following components are allowed:\n" + "| - benchmark.Counter\n" + "| \n"); + } + + template<> auto get() -> benchmark::Counter { + return _ctx.get(); + } + template<> auto update(const benchmark::Counter& updated_component) -> void { + _ctx.update(updated_component); + } + auto entity() const -> ecsact_entity_id { + return _ctx.entity(); + } + + + +}; diff --git a/unreal-cpp-floppybots/Content/Blueprints/ConnectMenu.uasset b/unreal-cpp-floppybots/Content/Blueprints/ConnectMenu.uasset index 3e669a5..98ce114 100644 Binary files a/unreal-cpp-floppybots/Content/Blueprints/ConnectMenu.uasset and b/unreal-cpp-floppybots/Content/Blueprints/ConnectMenu.uasset differ