From 10c8454e8fadcbfd1e4875a8077d3f4b47bfe4ab Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 27 Jul 2023 10:30:27 -0500 Subject: [PATCH 01/76] Paste at same level of selected actor parent node instead of as child of selected actor for better behavior. --- Source/Editor/Modules/SceneEditingModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 58c2f21dc..2b8bf718e 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -503,7 +503,7 @@ namespace FlaxEditor.Modules // Set paste target if only one actor is selected and no target provided if (pasteTargetActor == null && SelectionCount == 1 && Selection[0] is ActorNode actorNode) { - pasteTargetActor = actorNode.Actor; + pasteTargetActor = actorNode.Actor.Scene == actorNode.Actor ? actorNode.Actor : actorNode.Actor.Parent; } // Create paste action From d6bfcbcc3dd757abed8b5311b79c794cb24b37e5 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 27 Jul 2023 10:49:29 -0500 Subject: [PATCH 02/76] Add for prefab --- Source/Editor/Windows/Assets/PrefabWindow.Actions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs index 3246e51b5..6de553a30 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets // Set paste target if only one actor is selected and no target provided if (pasteTargetActor == null && Selection.Count == 1 && Selection[0] is ActorNode actorNode) { - pasteTargetActor = actorNode.Actor; + pasteTargetActor = actorNode.Actor.IsPrefabRoot ? actorNode.Actor : actorNode.Actor.Parent; } // Create paste action From e598746980bdd9d1d6a71d2a92c25b69d35f05ab Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 30 Jul 2023 15:26:29 +0300 Subject: [PATCH 03/76] Fix build issues in Visual Studio 2022 Preview 5 --- Source/ThirdParty/fmt/format.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/ThirdParty/fmt/format.h b/Source/ThirdParty/fmt/format.h index 0550af1b5..f2151d7b0 100644 --- a/Source/ThirdParty/fmt/format.h +++ b/Source/ThirdParty/fmt/format.h @@ -36,6 +36,7 @@ #include #include #include +#include #include "core.h" From db56284ca4195840a207efcaa8ee44f6ccde7432 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Tue, 1 Aug 2023 19:32:40 +0300 Subject: [PATCH 04/76] Fix WindowsPlatform::LoadLibrary to not modify the string parameter --- Source/Engine/Platform/Windows/WindowsPlatform.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 4d43272d1..970ccfa10 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -1191,11 +1191,8 @@ void* WindowsPlatform::LoadLibrary(const Char* filename) folder = StringView::Empty; if (folder.HasChars()) { - Char& end = ((Char*)folder.Get())[folder.Length()]; - const Char c = end; - end = 0; - SetDllDirectoryW(*folder); - end = c; + String folderNullTerminated(folder); + SetDllDirectoryW(folderNullTerminated.Get()); } // Avoiding windows dialog boxes if missing From fe87eb96e619482dd254917866d536763788af14 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 21 May 2023 00:53:03 +0300 Subject: [PATCH 05/76] Skip post processing when tonemapping and camera artifacts are disabled --- Source/Engine/Renderer/PostProcessingPass.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index 760b12ea0..345147b61 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -190,8 +190,8 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, PostProcessSettings& settings = renderContext.List->Settings; bool useBloom = EnumHasAnyFlags(view.Flags, ViewFlags::Bloom) && settings.Bloom.Enabled && settings.Bloom.Intensity > 0.0f; - bool useToneMapping = EnumHasAnyFlags(view.Flags, ViewFlags::ToneMapping); - bool useCameraArtifacts = EnumHasAnyFlags(view.Flags, ViewFlags::CameraArtifacts); + bool useToneMapping = EnumHasAnyFlags(view.Flags, ViewFlags::ToneMapping) && settings.ToneMapping.Mode != ToneMappingMode::None; + bool useCameraArtifacts = EnumHasAnyFlags(view.Flags, ViewFlags::CameraArtifacts) && (settings.CameraArtifacts.VignetteIntensity > 0.0f || settings.CameraArtifacts.GrainAmount > 0.0f || settings.CameraArtifacts.ChromaticDistortion > 0.0f || settings.CameraArtifacts.ScreenFadeColor.A > 0.0f); bool useLensFlares = EnumHasAnyFlags(view.Flags, ViewFlags::LensFlares) && settings.LensFlares.Intensity > 0.0f && useBloom; // Ensure to have valid data and if at least one effect should be applied From ec3b921a4f494c9b2a11da9709d72bb7accce5eb Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Wed, 2 Aug 2023 05:17:49 +0300 Subject: [PATCH 06/76] Fix navigation rebuilding --- Source/Engine/Navigation/NavMeshBoundsVolume.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp index 85a022c82..89e76a9bc 100644 --- a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp +++ b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp @@ -34,10 +34,10 @@ void NavMeshBoundsVolume::Deserialize(DeserializeStream& stream, ISerializeModif void NavMeshBoundsVolume::OnEnable() { + GetScene()->Navigation.Volumes.Add(this); + // Base Actor::OnEnable(); - - GetScene()->Navigation.Volumes.Add(this); } void NavMeshBoundsVolume::OnDisable() From b9ffb950f37b4ede8e2497b71fad0bfc35562615 Mon Sep 17 00:00:00 2001 From: envision3d Date: Sun, 6 Aug 2023 13:11:45 -0500 Subject: [PATCH 07/76] Refactor and more appropriately organize logic - move play logic to simulation module - update play hotkey to call play game/scene delegate function - link hotkey tooltip text to inputOptions --- Source/Editor/Editor.cs | 2 +- Source/Editor/Modules/SimulationModule.cs | 76 ++++++++++++++++++- Source/Editor/Modules/UIModule.cs | 86 ++++------------------ Source/Editor/Windows/GameWindow.cs | 2 +- Source/Editor/Windows/SceneEditorWindow.cs | 2 +- 5 files changed, 88 insertions(+), 80 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 1a1fd3b2d..7f0359331 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -1579,7 +1579,7 @@ namespace FlaxEditor private static void RequestStartPlayOnEditMode() { if (Instance.StateMachine.IsEditMode) - Instance.Simulation.RequestStartPlay(); + Instance.Simulation.RequestStartPlayScenes(); if (Instance.StateMachine.IsPlayMode) Instance.StateMachine.StateChanged -= RequestStartPlayOnEditMode; } diff --git a/Source/Editor/Modules/SimulationModule.cs b/Source/Editor/Modules/SimulationModule.cs index cc081c23c..97676c2eb 100644 --- a/Source/Editor/Modules/SimulationModule.cs +++ b/Source/Editor/Modules/SimulationModule.cs @@ -20,6 +20,7 @@ namespace FlaxEditor.Modules private bool _updateOrFixedUpdateWasCalled; private long _breakpointHangFlag; private EditorWindow _enterPlayFocusedWindow; + private Scene[] _scenesToReload; internal SimulationModule(Editor editor) : base(editor) @@ -68,6 +69,22 @@ namespace FlaxEditor.Modules BreakpointHangEnd?.Invoke(); } + /// + /// Delegates between playing game and playing scenes in editor based on the user's editor preference. + /// + public void DelegatePlayOrStopPlayInEditor() + { + switch (Editor.Options.Options.Interface.PlayButtonAction) + { + case Options.InterfaceOptions.PlayAction.PlayGame: + Editor.Simulation.RequestPlayGameOrStopPlay(); + return; + case Options.InterfaceOptions.PlayAction.PlayScenes: + Editor.Simulation.RequestPlayScenesOrStopPlay(); + return; + } + } + /// /// Returns true if play mode has been requested. /// @@ -76,7 +93,7 @@ namespace FlaxEditor.Modules /// /// Requests start playing in editor. /// - public void RequestStartPlay() + public void RequestStartPlayScenes() { if (Editor.StateMachine.IsEditMode) { @@ -89,6 +106,57 @@ namespace FlaxEditor.Modules } } + /// + /// Requests playing game start or stop in editor from the project's configured FirstScene. + /// + public void RequestPlayGameOrStopPlay() + { + if (Editor.StateMachine.IsPlayMode) + { + RequestStopPlay(); + } + else + { + RequestStartPlayGame(); + } + } + + /// + /// Requests start playing in editor from the project's configured FirstScene. + /// + public void RequestStartPlayGame() + { + if (!Editor.StateMachine.IsEditMode) + { + return; + } + + var firstScene = Content.Settings.GameSettings.Load().FirstScene; + if (firstScene == Guid.Empty) + { + if (Level.IsAnySceneLoaded) + Editor.Simulation.RequestStartPlayScenes(); + return; + } + + _scenesToReload = Level.Scenes; + Level.UnloadAllScenes(); + Level.LoadScene(firstScene); + + Editor.PlayModeEnd += OnPlayGameEnd; + RequestPlayScenesOrStopPlay(); + } + + private void OnPlayGameEnd() + { + Editor.PlayModeEnd -= OnPlayGameEnd; + + Level.UnloadAllScenes(); + + foreach (var scene in _scenesToReload) + Level.LoadScene(scene.ID); + } + /// /// Requests stop playing in editor. /// @@ -106,14 +174,14 @@ namespace FlaxEditor.Modules } /// - /// Requests the playing start or stop in editor. + /// Requests the playing scenes start or stop in editor. /// - public void RequestPlayOrStopPlay() + public void RequestPlayScenesOrStopPlay() { if (Editor.StateMachine.IsPlayMode) RequestStopPlay(); else - RequestStartPlay(); + RequestStartPlayScenes(); } /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 38c61942a..08ec09de8 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -39,7 +39,6 @@ namespace FlaxEditor.Modules private bool _progressFailed; ContextMenuSingleSelectGroup _numberOfClientsGroup = new ContextMenuSingleSelectGroup(); - private Scene[] _scenesToReload; private ContextMenuButton _menuFileSaveScenes; private ContextMenuButton _menuFileCloseScenes; @@ -556,8 +555,8 @@ namespace FlaxEditor.Modules cm = MenuGame.ContextMenu; cm.VisibleChanged += OnMenuGameShowHide; - _menuGamePlayGame = cm.AddButton("Play Game", PlayGame); - _menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", inputOptions.Play.ToString(), PlayScenes); + _menuGamePlayGame = cm.AddButton("Play Game", Editor.Simulation.RequestPlayGameOrStopPlay); + _menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", Editor.Simulation.RequestPlayScenesOrStopPlay); _menuGameStop = cm.AddButton("Stop Game", Editor.Simulation.RequestStopPlay); _menuGamePause = cm.AddButton("Pause", inputOptions.Pause.ToString(), Editor.Simulation.RequestPausePlay); @@ -566,8 +565,8 @@ namespace FlaxEditor.Modules _numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu); cm.AddSeparator(); - cm.AddButton("Cook & Run", CookAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after."); - cm.AddButton("Run cooked game", RunCookedGame).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first."); + cm.AddButton("Cook & Run", Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after."); + cm.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first."); // Tools MenuTools = MainMenu.AddButton("Tools"); @@ -658,23 +657,23 @@ namespace FlaxEditor.Modules Parent = mainWindow, }; - _toolStripSaveAll = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Save64, Editor.SaveAll).LinkTooltip("Save all (Ctrl+S)"); + _toolStripSaveAll = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Save64, Editor.SaveAll).LinkTooltip($"Save all ({inputOptions.Save})"); ToolStrip.AddSeparator(); - _toolStripUndo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Undo64, Editor.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); - _toolStripRedo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Redo64, Editor.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); + _toolStripUndo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Undo64, Editor.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})"); + _toolStripRedo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Redo64, Editor.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})"); ToolStrip.AddSeparator(); - _toolStripTranslate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Translate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip("Change Gizmo tool mode to Translate (1)"); - _toolStripRotate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Rotate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)"); - _toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)"); + _toolStripTranslate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Translate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip($"Change Gizmo tool mode to Translate ({inputOptions.TranslateMode})"); + _toolStripRotate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Rotate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip($"Change Gizmo tool mode to Rotate ({inputOptions.RotateMode})"); + _toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})"); ToolStrip.AddSeparator(); // Cook scenes _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)"); // Cook and run - _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, CookAndRun).LinkTooltip("Cook & Run - build game for the current platform and run it locally"); + _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Cook & Run - build game for the current platform and run it locally"); _toolStripCook.ContextMenu = new ContextMenu(); - _toolStripCook.ContextMenu.AddButton("Run cooked game", RunCookedGame); + _toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked); _toolStripCook.ContextMenu.AddSeparator(); var numberOfClientsMenu = _toolStripCook.ContextMenu.AddChildMenu("Number of game clients"); _numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu); @@ -682,7 +681,7 @@ namespace FlaxEditor.Modules ToolStrip.AddSeparator(); // Play - _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, OnPlayPressed).LinkTooltip("Play Game"); + _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, Editor.Simulation.DelegatePlayOrStopPlayInEditor).LinkTooltip($"Play In Editor ({inputOptions.Play})"); _toolStripPlay.ContextMenu = new ContextMenu(); var playSubMenu = _toolStripPlay.ContextMenu.AddChildMenu("Play button action"); var playActionGroup = new ContextMenuSingleSelectGroup(); @@ -1039,65 +1038,6 @@ namespace FlaxEditor.Modules Editor.Options.Apply(options); } - private void OnPlayPressed() - { - switch (Editor.Options.Options.Interface.PlayButtonAction) - { - case InterfaceOptions.PlayAction.PlayGame: - if (Editor.IsPlayMode) - Editor.Simulation.RequestStopPlay(); - else - PlayGame(); - return; - case InterfaceOptions.PlayAction.PlayScenes: - PlayScenes(); - return; - } - } - - private void PlayGame() - { - var firstScene = GameSettings.Load().FirstScene; - if (firstScene == Guid.Empty) - { - if (Level.IsAnySceneLoaded) - Editor.Simulation.RequestStartPlay(); - return; - } - - _scenesToReload = Level.Scenes; - Level.UnloadAllScenes(); - Level.LoadScene(firstScene); - - Editor.PlayModeEnd += OnPlayGameSceneEnding; - Editor.Simulation.RequestPlayOrStopPlay(); - } - - private void OnPlayGameSceneEnding() - { - Editor.PlayModeEnd -= OnPlayGameSceneEnding; - - Level.UnloadAllScenes(); - - foreach (var scene in _scenesToReload) - Level.LoadScene(scene.ID); - } - - private void PlayScenes() - { - Editor.Simulation.RequestPlayOrStopPlay(); - } - - private void CookAndRun() - { - Editor.Windows.GameCookerWin.BuildAndRun(); - } - - private void RunCookedGame() - { - Editor.Windows.GameCookerWin.RunCooked(); - } - private void OnMainWindowClosing() { // Clear UI references (GUI cannot be used after window closing) diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 46c0f33f4..513542fdc 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -303,7 +303,7 @@ namespace FlaxEditor.Windows Editor.Options.OptionsChanged += OnOptionsChanged; OnOptionsChanged(Editor.Options.Options); - InputActions.Add(options => options.Play, Editor.Simulation.RequestPlayOrStopPlay); + InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor); InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause); InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); } diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs index f55ff4ee4..72ff3be33 100644 --- a/Source/Editor/Windows/SceneEditorWindow.cs +++ b/Source/Editor/Windows/SceneEditorWindow.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.SelectAll, Editor.SceneEditing.SelectAllScenes); InputActions.Add(options => options.Delete, Editor.SceneEditing.Delete); InputActions.Add(options => options.Search, () => Editor.Windows.SceneWin.Search()); - InputActions.Add(options => options.Play, Editor.Simulation.RequestPlayOrStopPlay); + InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor); InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause); InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); } From 47c6e544068831a511f141ed642489a5f1ac2a96 Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Tue, 8 Aug 2023 06:40:28 +0300 Subject: [PATCH 08/76] Fix exposed public CommonValue inclusion --- .../Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp | 1 + Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp | 1 + Source/Engine/Particles/Graph/ParticleEmitterGraph.h | 1 + Source/Engine/Particles/ParticleEffect.cpp | 1 + Source/Engine/Particles/ParticleEmitter.h | 1 + Source/Engine/Particles/ParticleSystem.cpp | 1 + Source/Engine/Particles/ParticlesSimulation.cpp | 1 + Source/Engine/Particles/ParticlesSimulation.h | 1 - 8 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index 5d8297452..6ea4b8b4e 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -3,6 +3,7 @@ #include "ParticleEmitterGraph.CPU.h" #include "Engine/Core/Random.h" #include "Engine/Utilities/Noise.h" +#include "Engine/Core/Types/CommonValue.h" // ReSharper disable CppCStyleCast // ReSharper disable CppClangTidyClangDiagnosticCastAlign diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp index 60df4a6d6..513252e80 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp @@ -6,6 +6,7 @@ #include "Engine/Serialization/FileReadStream.h" #include "Engine/Visject/ShaderGraphUtilities.h" #include "Engine/Engine/Globals.h" +#include "Engine/Core/Types/CommonValue.h" /// /// GPU particles shader source code template has special marks for generated code. diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h index 7b0b53eb7..e184b0660 100644 --- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h +++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h @@ -8,6 +8,7 @@ #include "Engine/Particles/Types.h" #include "Engine/Particles/ParticlesSimulation.h" #include "Engine/Particles/ParticlesData.h" +#include "Engine/Core/Types/CommonValue.h" class ParticleEffect; diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 1d7fa18a1..8a767ba22 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -2,6 +2,7 @@ #include "ParticleEffect.h" #include "Particles.h" +#include "Engine/Core/Types/CommonValue.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Scene/SceneRendering.h" diff --git a/Source/Engine/Particles/ParticleEmitter.h b/Source/Engine/Particles/ParticleEmitter.h index 93279290f..4d502bcf5 100644 --- a/Source/Engine/Particles/ParticleEmitter.h +++ b/Source/Engine/Particles/ParticleEmitter.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Content/BinaryAsset.h" +#include "Engine/Core/Math/BoundingBox.h" #include "Engine/Graphics/Shaders/Cache/ShaderAssetBase.h" #include "Graph/CPU/ParticleEmitterGraph.CPU.h" #if COMPILE_WITH_GPU_PARTICLES diff --git a/Source/Engine/Particles/ParticleSystem.cpp b/Source/Engine/Particles/ParticleSystem.cpp index 6f63101b2..0ef193cdc 100644 --- a/Source/Engine/Particles/ParticleSystem.cpp +++ b/Source/Engine/Particles/ParticleSystem.cpp @@ -2,6 +2,7 @@ #include "ParticleSystem.h" #include "ParticleEffect.h" +#include "Engine/Core/Types/CommonValue.h" #include "Engine/Level/Level.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Serialization/MemoryReadStream.h" diff --git a/Source/Engine/Particles/ParticlesSimulation.cpp b/Source/Engine/Particles/ParticlesSimulation.cpp index c68862925..cfdb51432 100644 --- a/Source/Engine/Particles/ParticlesSimulation.cpp +++ b/Source/Engine/Particles/ParticlesSimulation.cpp @@ -6,6 +6,7 @@ #include "Particles.h" #include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Core/Types/CommonValue.h" ParticleEmitterInstance::ParticleEmitterInstance() { diff --git a/Source/Engine/Particles/ParticlesSimulation.h b/Source/Engine/Particles/ParticlesSimulation.h index a7d16d3d1..d7b4cd1d1 100644 --- a/Source/Engine/Particles/ParticlesSimulation.h +++ b/Source/Engine/Particles/ParticlesSimulation.h @@ -2,7 +2,6 @@ #pragma once -#include "Engine/Core/Types/CommonValue.h" #include "Engine/Visject/GraphParameter.h" class ParticleSystemInstance; From 9d8105e3f3bbf61d8b5082c5c5d8ec48e9414e83 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Tue, 8 Aug 2023 17:35:13 +0300 Subject: [PATCH 09/76] Separate managed assembly unloading and scripting ALC reinitialization Fixes an issue with multiple managed assemblies unloading and releasing all cached data before native resources were fully released in other assemblies. --- .../Engine/Engine/NativeInterop.Unmanaged.cs | 31 +++++++++++-------- Source/Engine/Scripting/ManagedCLR/MCore.h | 2 +- Source/Engine/Scripting/Runtime/DotNet.cpp | 18 +++++------ Source/Engine/Scripting/Runtime/Mono.cpp | 2 +- Source/Engine/Scripting/Runtime/None.cpp | 2 +- Source/Engine/Scripting/Scripting.cpp | 4 ++- 6 files changed, 33 insertions(+), 26 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 57454ad1e..d2a8ca55e 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -903,11 +903,25 @@ namespace FlaxEngine.Interop AssemblyLocations.Remove(assembly.FullName); - // Clear all caches which might hold references to closing assembly + // Unload native library handles associated for this assembly + string nativeLibraryName = assemblyOwnedNativeLibraries.GetValueOrDefault(assembly); + if (nativeLibraryName != null && loadedNativeLibraries.TryGetValue(nativeLibraryName, out IntPtr nativeLibrary)) + { + NativeLibrary.Free(nativeLibrary); + loadedNativeLibraries.Remove(nativeLibraryName); + } + if (nativeLibraryName != null) + nativeLibraryPaths.Remove(nativeLibraryName); + } + + [UnmanagedCallersOnly] + internal static void ReloadScriptingAssemblyLoadContext() + { +#if FLAX_EDITOR + // Clear all caches which might hold references to assemblies in collectible ALC typeCache.Clear(); // Release all references in collectible ALC -#if FLAX_EDITOR cachedDelegatesCollectible.Clear(); foreach (var pair in typeHandleCacheCollectible) pair.Value.Free(); @@ -918,23 +932,13 @@ namespace FlaxEngine.Interop foreach (var handle in fieldHandleCacheCollectible) handle.Free(); fieldHandleCacheCollectible.Clear(); -#endif + _typeSizeCache.Clear(); foreach (var pair in classAttributesCacheCollectible) pair.Value.Free(); classAttributesCacheCollectible.Clear(); - // Unload native library handles associated for this assembly - string nativeLibraryName = assemblyOwnedNativeLibraries.GetValueOrDefault(assembly); - if (nativeLibraryName != null && loadedNativeLibraries.TryGetValue(nativeLibraryName, out IntPtr nativeLibrary)) - { - NativeLibrary.Free(nativeLibrary); - loadedNativeLibraries.Remove(nativeLibraryName); - } - if (nativeLibraryName != null) - nativeLibraryPaths.Remove(nativeLibraryName); - // Unload the ALC bool unloading = true; scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; }; @@ -945,6 +949,7 @@ namespace FlaxEngine.Interop InitScriptingAssemblyLoadContext(); DelegateHelpers.InitMethods(); +#endif } [UnmanagedCallersOnly] diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.h b/Source/Engine/Scripting/ManagedCLR/MCore.h index 38b58403d..e1de3c207 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.h +++ b/Source/Engine/Scripting/ManagedCLR/MCore.h @@ -47,7 +47,7 @@ public: #if USE_EDITOR // Called by Scripting in a middle of hot-reload (after unloading modules but before loading them again). - static void OnMidHotReload(); + static void ReloadScriptingAssemblyLoadContext(); #endif public: diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 8d75d7a95..36f1f16af 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -160,6 +160,7 @@ DECLARE_ENUM_OPERATORS(MTypeAttributes); DECLARE_ENUM_OPERATORS(MMethodAttributes); DECLARE_ENUM_OPERATORS(MFieldAttributes); +// Multiple AppDomains are superseded by AssemblyLoadContext in .NET extern MDomain* MRootDomain; extern MDomain* MActiveDomain; extern Array> MDomains; @@ -301,11 +302,14 @@ void MCore::UnloadEngine() #if USE_EDITOR -void MCore::OnMidHotReload() +void MCore::ReloadScriptingAssemblyLoadContext() { // Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108) for (auto e : CachedClassHandles) e.Value->_attributes.Clear(); + + static void* ReloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("ReloadScriptingAssemblyLoadContext")); + CallStaticMethod(ReloadScriptingAssemblyLoadContextPtr); } #endif @@ -723,16 +727,12 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa bool MAssembly::UnloadImage(bool isReloading) { - if (_handle) + if (_handle && isReloading) { - // TODO: closing assembly on reload only is copy-paste from mono, do we need do this on .NET too? - if (isReloading) - { - LOG(Info, "Unloading managed assembly \'{0}\' (is reloading)", String(_name)); + LOG(Info, "Unloading managed assembly \'{0}\' (is reloading)", String(_name)); - static void* CloseAssemblyPtr = GetStaticMethodPointer(TEXT("CloseAssembly")); - CallStaticMethod(CloseAssemblyPtr, _handle); - } + static void* CloseAssemblyPtr = GetStaticMethodPointer(TEXT("CloseAssembly")); + CallStaticMethod(CloseAssemblyPtr, _handle); CachedAssemblyHandles.Remove(_handle); _handle = nullptr; diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index ca4ef367b..0a60db42f 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -716,7 +716,7 @@ void MCore::UnloadEngine() #if USE_EDITOR -void MCore::OnMidHotReload() +void MCore::ReloadScriptingAssemblyLoadContext() { } diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp index c8f8926e5..ef9c118cf 100644 --- a/Source/Engine/Scripting/Runtime/None.cpp +++ b/Source/Engine/Scripting/Runtime/None.cpp @@ -61,7 +61,7 @@ void MCore::UnloadEngine() #if USE_EDITOR -void MCore::OnMidHotReload() +void MCore::ReloadScriptingAssemblyLoadContext() { } diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index bbfa802f1..6210063b8 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -706,7 +706,9 @@ void Scripting::Reload(bool canTriggerSceneReload) modules.Clear(); _nonNativeModules.ClearDelete(); _hasGameModulesLoaded = false; - MCore::OnMidHotReload(); + + // Release and create a new assembly load context for user assemblies + MCore::ReloadScriptingAssemblyLoadContext(); // Give GC a try to cleanup old user objects and the other mess MCore::GC::Collect(); From aa3dd14bfcdcdd1e1f698d0cbcd345b95872aae6 Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Thu, 10 Aug 2023 22:24:55 -0400 Subject: [PATCH 10/76] add locked focus --- Source/Editor/Options/InputOptions.cs | 4 ++ .../Viewport/MainEditorGizmoViewport.cs | 46 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 4f2b40514..225f79b83 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -60,6 +60,10 @@ namespace FlaxEditor.Options [EditorDisplay("Common"), EditorOrder(200)] public InputBinding FocusSelection = new InputBinding(KeyboardKeys.F); + [DefaultValue(typeof(InputBinding), "F")] + [EditorDisplay("Common"), EditorOrder(200)] + public InputBinding LockFocusSelection = new InputBinding(KeyboardKeys.F, KeyboardKeys.Shift); + [DefaultValue(typeof(InputBinding), "Ctrl+F")] [EditorDisplay("Common"), EditorOrder(210)] public InputBinding Search = new InputBinding(KeyboardKeys.F, KeyboardKeys.Control); diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 48cd4fd98..78e5cbf56 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -133,6 +133,7 @@ namespace FlaxEditor.Viewport } } + private bool _lockedFocus; private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); private StaticModel _previewStaticModel; private int _previewModelEntryIndex; @@ -386,11 +387,40 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); + + InputActions.Add(options => options.LockFocusSelection, LockFocusSelection); InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.RotateSelection, RotateSelection); InputActions.Add(options => options.Delete, _editor.SceneEditing.Delete); } + public override void Update(float deltaTime) + { + base.Update(deltaTime); + + var hasSelections = TransformGizmo.SelectedParents.Count != 0; + var requestUnlockFocus = FlaxEngine.Input.Mouse.GetButtonDown(MouseButton.Right) || FlaxEngine.Input.Mouse.GetButtonDown(MouseButton.Left); + + if ((IsFocused && requestUnlockFocus) || !hasSelections) + { + UnlockFocusSelection(); + } + + if (_lockedFocus) + { + BoundingSphere selectionBounds = BoundingSphere.Empty; + for (int i = 0; i < TransformGizmo.SelectedParents.Count; i++) + { + TransformGizmo.SelectedParents[i].GetEditorSphere(out var sphere); + BoundingSphere.Merge(ref selectionBounds, ref sphere, out selectionBounds); + } + + var focusDistance = Mathf.Max(selectionBounds.Radius * 2f, 100f); + var viewportPosition = selectionBounds.Center + (-ViewDirection * focusDistance); + ViewPosition = viewportPosition; + } + } + /// /// Overrides the selection outline effect or restored the default one. /// @@ -753,6 +783,22 @@ namespace FlaxEditor.Viewport FocusSelection(ref orientation); } + /// + /// Lock focus on the current selection gizmo. + /// + public void LockFocusSelection() + { + _lockedFocus = true; + } + + /// + /// Unlock focus on the current selection. + /// + public void UnlockFocusSelection() + { + _lockedFocus = false; + } + /// /// Focuses the viewport on the current selection of the gizmo. /// From f2072028d4e5a488296784f14d1ca7dee6d8775e Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Thu, 10 Aug 2023 22:31:03 -0400 Subject: [PATCH 11/76] change default key --- Source/Editor/Options/InputOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index 225f79b83..e0a51622b 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -60,7 +60,7 @@ namespace FlaxEditor.Options [EditorDisplay("Common"), EditorOrder(200)] public InputBinding FocusSelection = new InputBinding(KeyboardKeys.F); - [DefaultValue(typeof(InputBinding), "F")] + [DefaultValue(typeof(InputBinding), "Shift+F")] [EditorDisplay("Common"), EditorOrder(200)] public InputBinding LockFocusSelection = new InputBinding(KeyboardKeys.F, KeyboardKeys.Shift); From 645977f14824623b409601cfd6d96c127e4bc339 Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Thu, 10 Aug 2023 22:41:48 -0400 Subject: [PATCH 12/76] add scroll to focus --- Source/Editor/Viewport/MainEditorGizmoViewport.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 78e5cbf56..90a5b8f22 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -134,6 +134,7 @@ namespace FlaxEditor.Viewport } private bool _lockedFocus; + private float _lockedFocusOffset; private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); private StaticModel _previewStaticModel; private int _previewModelEntryIndex; @@ -408,7 +409,7 @@ namespace FlaxEditor.Viewport if (_lockedFocus) { - BoundingSphere selectionBounds = BoundingSphere.Empty; + var selectionBounds = BoundingSphere.Empty; for (int i = 0; i < TransformGizmo.SelectedParents.Count; i++) { TransformGizmo.SelectedParents[i].GetEditorSphere(out var sphere); @@ -416,7 +417,14 @@ namespace FlaxEditor.Viewport } var focusDistance = Mathf.Max(selectionBounds.Radius * 2f, 100f); - var viewportPosition = selectionBounds.Center + (-ViewDirection * focusDistance); + + if (IsFocused) + { + _lockedFocusOffset += -FlaxEngine.Input.Mouse.ScrollDelta * focusDistance; + } + + var viewportPosition = selectionBounds.Center + (-ViewDirection * (focusDistance + _lockedFocusOffset)); + ViewPosition = viewportPosition; } } @@ -797,6 +805,7 @@ namespace FlaxEditor.Viewport public void UnlockFocusSelection() { _lockedFocus = false; + _lockedFocusOffset = 0f; } /// From db806ad500a9c07cdd318654d0f76b26f58b4c76 Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Fri, 11 Aug 2023 08:28:28 -0400 Subject: [PATCH 13/76] fix for large worlds --- Source/Editor/Viewport/MainEditorGizmoViewport.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 90a5b8f22..a40ff5369 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -134,7 +134,7 @@ namespace FlaxEditor.Viewport } private bool _lockedFocus; - private float _lockedFocusOffset; + private double _lockedFocusOffset; private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); private StaticModel _previewStaticModel; private int _previewModelEntryIndex; @@ -416,7 +416,7 @@ namespace FlaxEditor.Viewport BoundingSphere.Merge(ref selectionBounds, ref sphere, out selectionBounds); } - var focusDistance = Mathf.Max(selectionBounds.Radius * 2f, 100f); + var focusDistance = Mathf.Max(selectionBounds.Radius * 2d, 100d); if (IsFocused) { From b35065ab3f0cdd211a4dcbf9d426af3802de59c2 Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Mon, 7 Aug 2023 10:16:50 +0300 Subject: [PATCH 14/76] Fix annoying error that happens due to an oversight --- Source/Engine/Networking/NetworkReplicator.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 279391e31..4a0f9437d 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -1127,13 +1127,6 @@ bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa rpc.Info = *info; rpc.ArgsData.Copy(Span(argsStream->GetBuffer(), argsStream->GetPosition())); rpc.Targets.Copy(targetIds); -#if USE_EDITOR || !BUILD_RELEASE - auto it = Objects.Find(obj->GetID()); - if (it == Objects.End()) - { - LOG(Error, "Cannot invoke RPC method '{0}.{1}' on object '{2}' that is not registered in networking (use 'NetworkReplicator.AddObject').", type.ToString(), String(name), obj->GetID()); - } -#endif ObjectsLock.Unlock(); // Check if skip local execution (eg. server rpc called from client or client rpc with specific targets) @@ -1554,7 +1547,13 @@ void NetworkInternal::NetworkReplicatorUpdate() continue; auto it = Objects.Find(obj->GetID()); if (it == Objects.End()) + { +#if USE_EDITOR || !BUILD_RELEASE + if(!DespawnedObjects.Contains(obj->GetID())) + LOG(Error, "Cannot invoke RPC method '{0}.{1}' on object '{2}' that is not registered in networking (use 'NetworkReplicator.AddObject').", e.Name.First.ToString(), String(e.Name.Second), obj->GetID()); +#endif continue; + } auto& item = it->Item; // Send RPC message From be33fc6018fb4241f7848b876e3bed8efff2c173 Mon Sep 17 00:00:00 2001 From: Ruan Lucas <79365912+RuanLucasGD@users.noreply.github.com> Date: Fri, 11 Aug 2023 22:31:12 -0400 Subject: [PATCH 15/76] improv scroll locked focus --- Source/Editor/Viewport/MainEditorGizmoViewport.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index a40ff5369..3132faf39 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -420,7 +420,8 @@ namespace FlaxEditor.Viewport if (IsFocused) { - _lockedFocusOffset += -FlaxEngine.Input.Mouse.ScrollDelta * focusDistance; + var viewportFocusDistance = Vector3.Distance(ViewPosition, selectionBounds.Center) / 10f; + _lockedFocusOffset -= FlaxEngine.Input.Mouse.ScrollDelta * viewportFocusDistance; } var viewportPosition = selectionBounds.Center + (-ViewDirection * (focusDistance + _lockedFocusOffset)); From 1413da189ea391ba386dd70c6ead668121cbe3d1 Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Sun, 13 Aug 2023 06:39:35 +0300 Subject: [PATCH 16/76] NetworkReplicator::ResolveForeignObject --- Source/Engine/Networking/NetworkReplicator.cpp | 7 +++++++ Source/Engine/Networking/NetworkReplicator.h | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 279391e31..f63543126 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -941,6 +941,13 @@ bool NetworkReplicator::HasObject(const ScriptingObject* obj) return false; } +ScriptingObject* NetworkReplicator::ResolveForeignObject(Guid objectId) +{ + if (const auto& object = ResolveObject(objectId)) + return object->Object.Get(); + return nullptr; +} + uint32 NetworkReplicator::GetObjectOwnerClientId(const ScriptingObject* obj) { uint32 id = NetworkManager::ServerClientId; diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index ecccc52cd..3806f8c9d 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -116,6 +116,13 @@ public: /// The network object. /// True if object exists in networking, otherwise false. API_FUNCTION() static bool HasObject(const ScriptingObject* obj); + + /// + /// Resolves foreign Guid into a local ScriptingObject + /// + /// The Guid of a foreign object. + /// Object if managed to resolve, otherwise null. + API_FUNCTION() static ScriptingObject* ResolveForeignObject(Guid objectId); /// /// Gets the Client Id of the network object owner. From f44156eb80edd04999e1a26aa077db8429a76671 Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski <118038102+Withaust@users.noreply.github.com> Date: Sun, 13 Aug 2023 07:39:03 +0300 Subject: [PATCH 17/76] Remove another unnecessary exposed CommonValue.h --- Source/Engine/Particles/Graph/ParticleEmitterGraph.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h index e184b0660..cfe259a72 100644 --- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h +++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h @@ -8,7 +8,8 @@ #include "Engine/Particles/Types.h" #include "Engine/Particles/ParticlesSimulation.h" #include "Engine/Particles/ParticlesData.h" -#include "Engine/Core/Types/CommonValue.h" +#include "Engine/Core/Math/Vector4.h" +#include "Engine/Core/Types/BaseTypes.h" class ParticleEffect; From 3df044d07bd73888ca5aa4154419bf61ce4a976c Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 12 Aug 2023 14:34:06 +0300 Subject: [PATCH 18/76] Add build option to change code optimization level in C# modules --- .../Flax.Build/Build/DotNet/Builder.DotNet.cs | 10 ++++++---- .../Flax.Build/Build/NativeCpp/BuildOptions.cs | 15 +++++++++++++-- .../Build/NativeCpp/Builder.NativeCpp.cs | 4 ++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index d6772379f..43c54a92d 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -255,11 +255,13 @@ namespace Flax.Build #endif if (buildOptions.ScriptingAPI.IgnoreMissingDocumentationWarnings) args.Add("-nowarn:1591"); -#if USE_NETCORE + // Optimizations prevent debugging, only enable in release builds - args.Add(buildData.Configuration == TargetConfiguration.Release ? "/optimize+" : "/optimize-"); -#else - args.Add(buildData.Configuration == TargetConfiguration.Debug ? "/optimize-" : "/optimize+"); + var optimize = buildData.Configuration == TargetConfiguration.Release; + if (buildData.TargetOptions.ScriptingAPI.Optimization.HasValue) + optimize = buildData.TargetOptions.ScriptingAPI.Optimization.Value; + args.Add(optimize ? "/optimize+" : "/optimize-"); +#if !USE_NETCORE args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies)); #endif args.Add(string.Format("/out:\"{0}\"", outputFile)); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs index e612c0a95..72cefbfca 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs @@ -185,7 +185,7 @@ namespace Flax.Build.NativeCpp public string DepsFolder => Path.Combine(Globals.EngineRoot, "Source", "Platforms", Platform.Target.ToString(), "Binaries", "ThirdParty", Architecture.ToString()); /// - /// The scripting API building options. + /// The C# scripting API building options. /// public struct ScriptingAPIOptions { @@ -224,6 +224,11 @@ namespace Flax.Build.NativeCpp /// public CSharpNullableReferences CSharpNullableReferences = CSharpNullableReferences.Disable; + /// + /// Enable code optimization. + /// + public bool? Optimization; + public ScriptingAPIOptions() { } @@ -232,13 +237,19 @@ namespace Flax.Build.NativeCpp /// Adds the other options into this. /// /// The other. - public void Add(ScriptingAPIOptions other) + public void Add(ScriptingAPIOptions other, bool addBuildOptions = true) { Defines.AddRange(other.Defines); SystemReferences.AddRange(other.SystemReferences); FileReferences.AddRange(other.FileReferences); Analyzers.AddRange(other.Analyzers); IgnoreMissingDocumentationWarnings |= other.IgnoreMissingDocumentationWarnings; + + if (addBuildOptions) + { + if (other.Optimization.HasValue) + Optimization |= other.Optimization; + } } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 225e46ba7..1e8550c4e 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -403,7 +403,7 @@ namespace Flax.Build moduleOptions.PrivateIncludePaths.AddRange(dependencyOptions.PublicIncludePaths); moduleOptions.Libraries.AddRange(dependencyOptions.Libraries); moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries); - moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI); + moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI, false); moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules); } } @@ -418,7 +418,7 @@ namespace Flax.Build moduleOptions.PublicIncludePaths.AddRange(dependencyOptions.PublicIncludePaths); moduleOptions.Libraries.AddRange(dependencyOptions.Libraries); moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries); - moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI); + moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI, false); moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules); } } From 4ce1f31f12d512010a86542c098742e0453d7335 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 12 Aug 2023 14:34:42 +0300 Subject: [PATCH 19/76] Enable optimizations for Editor module in Development builds --- Source/Editor/Editor.Build.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs index cc7c48d23..a971dbefa 100644 --- a/Source/Editor/Editor.Build.cs +++ b/Source/Editor/Editor.Build.cs @@ -42,6 +42,10 @@ public class Editor : EditorModule options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions"); options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter"); + // Enable optimizations for Editor, disable this for debugging the editor + if (options.Configuration == TargetConfiguration.Development) + options.ScriptingAPI.Optimization = true; + options.PublicDependencies.Add("Engine"); options.PrivateDependencies.Add("pugixml"); options.PrivateDependencies.Add("curl"); From e6878942f909e691cda5b56e0ba9282d7b009a32 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 12 Aug 2023 02:33:54 +0300 Subject: [PATCH 20/76] Fix missing C++ standard version in VC++ projects intellisense options --- .../VisualStudio/VCProjectGenerator.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index 4538ea8c1..76a0cf04b 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using Flax.Build.NativeCpp; using System; using System.Collections.Generic; using System.IO; @@ -317,12 +318,31 @@ namespace Flax.Build.Projects.VisualStudio vcFiltersFileContent.AppendLine(" "); // IntelliSense information + + List additionalOptions = new List(); + switch (project.Configurations[0].TargetBuildOptions.CompileEnv.CppVersion) + { + case CppVersion.Cpp14: + additionalOptions.Add("/std:c++14"); + break; + case CppVersion.Cpp17: + additionalOptions.Add("/std:c++17"); + break; + case CppVersion.Cpp20: + additionalOptions.Add("/std:c++20"); + break; + case CppVersion.Latest: + additionalOptions.Add("/std:c++latest"); + break; + } + vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(string.Format(" $(NMakePreprocessorDefinitions){0}", (project.Defines.Count > 0 ? (";" + string.Join(";", project.Defines)) : ""))); vcProjectFileContent.AppendLine(string.Format(" $(NMakeIncludeSearchPath){0}", (project.SearchPaths.Length > 0 ? (";" + string.Join(";", project.SearchPaths)) : ""))); vcProjectFileContent.AppendLine(" $(NMakeForcedIncludes)"); vcProjectFileContent.AppendLine(" $(NMakeAssemblySearchPath)"); vcProjectFileContent.AppendLine(" $(NMakeForcedUsingAssemblies)"); + vcProjectFileContent.AppendLine(string.Format(" {0}", string.Join(" ", additionalOptions))); vcProjectFileContent.AppendLine(" "); foreach (var platform in platforms) From 44b70d87e5980ffd0c3c8c9558e9f26ecdf6dd13 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 4 Aug 2023 21:38:05 +0300 Subject: [PATCH 21/76] Cache MakeArrayType results in native interop --- Source/Engine/Engine/NativeInterop.Managed.cs | 21 ++++++++++--------- .../Engine/Engine/NativeInterop.Unmanaged.cs | 4 ++-- Source/Engine/Engine/NativeInterop.cs | 10 +++++++++ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index 95146a95c..86b41f458 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -91,18 +91,19 @@ namespace FlaxEngine.Interop internal void Allocate(int length) where T : unmanaged { + _length = length; + _arrayType = typeof(T[]); + _elementType = typeof(T); + _elementSize = Unsafe.SizeOf(); + // Try to reuse existing allocated buffer - if (length * Unsafe.SizeOf() > _unmanagedAllocationSize) + if (length * _elementSize > _unmanagedAllocationSize) { if (_unmanagedAllocationSize > 0) NativeInterop.NativeFree(_unmanagedData.ToPointer()); - _unmanagedData = (IntPtr)NativeInterop.NativeAlloc(length, Unsafe.SizeOf()); - _unmanagedAllocationSize = Unsafe.SizeOf() * length; + _unmanagedData = (IntPtr)NativeInterop.NativeAlloc(length, _elementSize); + _unmanagedAllocationSize = _elementSize * length; } - _length = length; - _arrayType = typeof(T).MakeArrayType(); - _elementType = typeof(T); - _elementSize = Unsafe.SizeOf(); } private ManagedArray() @@ -112,12 +113,12 @@ namespace FlaxEngine.Interop private ManagedArray(IntPtr ptr, int length, Type arrayType, Type elementType) { Assert.IsTrue(arrayType.IsArray); + _elementType = elementType; + _elementSize = NativeInterop.GetTypeSize(elementType); _unmanagedData = ptr; - _unmanagedAllocationSize = Marshal.SizeOf(elementType) * length; + _unmanagedAllocationSize = _elementSize * length; _length = length; _arrayType = arrayType; - _elementType = elementType; - _elementSize = NativeInterop.GetTypeSize(_elementType); } ~ManagedArray() diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index d2a8ca55e..86fe3036e 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -526,7 +526,7 @@ namespace FlaxEngine.Interop { Type elementType = Unsafe.As(typeHandle.Target); Type marshalledType = ArrayFactory.GetMarshalledType(elementType); - Type arrayType = elementType.MakeArrayType(); + Type arrayType = ArrayFactory.GetArrayType(elementType); if (marshalledType.IsValueType) { ManagedArray managedArray = ManagedArray.AllocateNewArray((int)size, arrayType, marshalledType); @@ -544,7 +544,7 @@ namespace FlaxEngine.Interop internal static ManagedHandle GetArrayTypeFromElementType(ManagedHandle elementTypeHandle) { Type elementType = Unsafe.As(elementTypeHandle.Target); - Type classType = elementType.MakeArrayType(); + Type classType = ArrayFactory.GetArrayType(elementType); return GetTypeGCHandle(classType); } diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index da372ac90..0949533ce 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -967,6 +967,7 @@ namespace FlaxEngine.Interop private delegate Array CreateArrayDelegate(long size); private static ConcurrentDictionary marshalledTypes = new ConcurrentDictionary(1, 3); + private static ConcurrentDictionary arrayTypes = new ConcurrentDictionary(1, 3); private static ConcurrentDictionary createArrayDelegates = new ConcurrentDictionary(1, 3); internal static Type GetMarshalledType(Type elementType) @@ -986,6 +987,15 @@ namespace FlaxEngine.Interop return marshalledTypes.GetOrAdd(elementType, Factory); } + internal static Type GetArrayType(Type elementType) + { + static Type Factory(Type type) => type.MakeArrayType(); + + if (arrayTypes.TryGetValue(elementType, out var arrayType)) + return arrayType; + return arrayTypes.GetOrAdd(elementType, Factory); + } + internal static Array CreateArray(Type type, long size) { static CreateArrayDelegate Factory(Type type) From 1254af8bbba136c5b0244ea93d93541f7c5b870c Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 13 Aug 2023 14:18:57 +0300 Subject: [PATCH 22/76] Optimize UnboxValue performance, safety and memory usage - Avoids unnecessary boxing of the converted values by storing them in unmanaged memory. - Wrap ToNative-method in a delegate and cache it - Fixes returning address to unpinned memory by pinning POD-types for a short period of time. --- .../Engine/Engine/NativeInterop.Unmanaged.cs | 1 - Source/Engine/Engine/NativeInterop.cs | 63 ++++++++++++++----- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index d2a8ca55e..af739b55e 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -650,7 +650,6 @@ namespace FlaxEngine.Interop if (!type.IsValueType) return ManagedHandle.ToIntPtr(handle); - // HACK: Get the address of a non-pinned value return ValueTypeUnboxer.GetPointer(value, type); } diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index da372ac90..de2afec04 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -17,6 +17,7 @@ using FlaxEngine.Assertions; using System.Collections.Concurrent; using System.IO; using System.Text; +using System.Threading; namespace FlaxEngine.Interop { @@ -1009,44 +1010,78 @@ namespace FlaxEngine.Interop internal static class ValueTypeUnboxer { - private delegate IntPtr UnboxerDelegate(object value); + private static GCHandle[] pinnedBoxedValues = new GCHandle[256]; + private static uint pinnedBoxedValuesPointer = 0; + private static (IntPtr ptr, int size)[] pinnedAllocations = new (IntPtr ptr, int size)[256]; + private static uint pinnedAllocationsPointer = 0; + + private delegate TInternal ToNativeDelegate(T value); + private delegate IntPtr UnboxerDelegate(object value, object converter); - private static ConcurrentDictionary unboxers = new ConcurrentDictionary(1, 3); + private static ConcurrentDictionary unboxers = new (1, 3); private static MethodInfo unboxerMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointer), BindingFlags.Static | BindingFlags.NonPublic); private static MethodInfo unboxerToNativeMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointerWithConverter), BindingFlags.Static | BindingFlags.NonPublic); internal static IntPtr GetPointer(object value, Type type) { - if (!unboxers.TryGetValue(type, out var deleg)) + if (!unboxers.TryGetValue(type, out var tuple)) { // Non-POD structures use internal layout (eg. SpriteHandleManaged in C++ with SpriteHandleMarshaller.SpriteHandleInternal in C#) so convert C# data into C++ data var attr = type.GetCustomAttribute(); var toNativeMethod = attr?.NativeType.GetMethod("ToNative", BindingFlags.Static | BindingFlags.NonPublic); if (toNativeMethod != null) { - deleg = unboxerToNativeMethod.MakeGenericMethod(toNativeMethod.ReturnType).CreateDelegate(); + tuple.deleg = unboxerToNativeMethod.MakeGenericMethod(type, toNativeMethod.ReturnType).CreateDelegate(); + tuple.toNativeDeleg = toNativeMethod.CreateDelegate(typeof(ToNativeDelegate<,>).MakeGenericType(type, toNativeMethod.ReturnType)); } else { - deleg = unboxerMethod.MakeGenericMethod(type).CreateDelegate(); + tuple.deleg = unboxerMethod.MakeGenericMethod(type).CreateDelegate(); } - deleg = unboxers.GetOrAdd(type, deleg); + tuple = unboxers.GetOrAdd(type, tuple); } - return deleg(value); + return tuple.deleg(value, tuple.toNativeDeleg); } - private static IntPtr UnboxPointer(object value) where T : struct + private static void PinValue(object value) { + // Prevent garbage collector from relocating the boxed value by pinning it temporarily. + // The pointer should remain valid quite long time but will be eventually unpinned. + uint index = Interlocked.Increment(ref pinnedBoxedValuesPointer) % (uint)pinnedBoxedValues.Length; + ref GCHandle handle = ref pinnedBoxedValues[index]; + if (handle.IsAllocated) + handle.Free(); + handle = GCHandle.Alloc(value, GCHandleType.Pinned); + } + + private static IntPtr PinValue(T value) where T : struct + { + // Store the converted value in unmanaged memory so it will not be relocated by the garbage collector. + int size = Unsafe.SizeOf(); + uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length; + ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index]; + if (alloc.size < size) + { + if (alloc.ptr != IntPtr.Zero) + NativeFree(alloc.ptr.ToPointer()); + alloc.ptr = new IntPtr(NativeAlloc(size)); + alloc.size = size; + } + + Unsafe.Write(alloc.ptr.ToPointer(), value); + return alloc.ptr; + } + + private static IntPtr UnboxPointer(object value, object converter) where T : struct + { + PinValue(value); return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox(value))); } - private static IntPtr UnboxPointerWithConverter(object value) where T : struct + private static IntPtr UnboxPointerWithConverter(object value, object converter) where T : struct where TInternal : struct { - var type = value.GetType(); - var attr = type.GetCustomAttribute(); - var toNative = attr.NativeType.GetMethod("ToNative", BindingFlags.Static | BindingFlags.NonPublic); - value = toNative.Invoke(null, new[] { value }); - return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox(value))); + ToNativeDelegate toNative = Unsafe.As>(converter); + return PinValue(toNative(Unsafe.Unbox(value))); } } From 27e1401fc7452b61e92f815c11d5e1097af05c93 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 13 Aug 2023 14:24:48 +0300 Subject: [PATCH 23/76] Slightly improve `MClass::GetMethod` method iteration Check the number of parameters first before expensive string comparison --- Source/Engine/Scripting/Runtime/DotNet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 36f1f16af..f4ae5b1ec 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -878,7 +878,7 @@ MMethod* MClass::GetMethod(const char* name, int32 numParams) const GetMethods(); for (int32 i = 0; i < _methods.Count(); i++) { - if (_methods[i]->GetName() == name && _methods[i]->GetParametersCount() == numParams) + if (_methods[i]->GetParametersCount() == numParams && _methods[i]->GetName() == name) return _methods[i]; } return nullptr; From d9ee8f46654767809e03c332e1fd4c300e538c73 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 5 Aug 2023 15:55:27 +0300 Subject: [PATCH 24/76] Cache pooled ManagedArray managed handles --- Source/Engine/Engine/NativeInterop.Managed.cs | 40 +++++++-- .../Engine/NativeInterop.Marshallers.cs | 88 +++++++------------ 2 files changed, 67 insertions(+), 61 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index 95146a95c..0a2ce5e31 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -29,6 +29,8 @@ namespace FlaxEngine.Interop private int _elementSize; private int _length; + [ThreadStatic] private static Dictionary pooledArrayHandles; + public static ManagedArray WrapNewArray(Array arr) => new ManagedArray(arr, arr.GetType()); public static ManagedArray WrapNewArray(Array arr, Type arrayType) => new ManagedArray(arr, arrayType); @@ -37,22 +39,38 @@ namespace FlaxEngine.Interop /// Returns an instance of ManagedArray from shared pool. /// /// The resources must be released by calling FreePooled() instead of Free()-method. - public static ManagedArray WrapPooledArray(Array arr) + public static (ManagedHandle managedHandle, ManagedArray managedArray) WrapPooledArray(Array arr) { ManagedArray managedArray = ManagedArrayPool.Get(); managedArray.WrapArray(arr, arr.GetType()); - return managedArray; + + if (pooledArrayHandles == null) + pooledArrayHandles = new(); + if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle)) + { + handle = ManagedHandle.Alloc(managedArray); + pooledArrayHandles.Add(managedArray, handle); + } + return (handle, managedArray); } /// /// Returns an instance of ManagedArray from shared pool. /// /// The resources must be released by calling FreePooled() instead of Free()-method. - public static ManagedArray WrapPooledArray(Array arr, Type arrayType) + public static ManagedHandle WrapPooledArray(Array arr, Type arrayType) { ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * NativeInterop.GetTypeSize(arr.GetType().GetElementType())); managedArray.WrapArray(arr, arrayType); - return managedArray; + + if (pooledArrayHandles == null) + pooledArrayHandles = new(); + if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle)) + { + handle = ManagedHandle.Alloc(managedArray); + pooledArrayHandles.Add(managedArray, handle); + } + return handle; } internal static ManagedArray AllocateNewArray(int length, Type arrayType, Type elementType) @@ -64,12 +82,20 @@ namespace FlaxEngine.Interop /// /// Returns an instance of ManagedArray from shared pool. /// - /// The resources must be released by calling FreePooled() instead of Free()-method. - public static ManagedArray AllocatePooledArray(int length) where T : unmanaged + /// The resources must be released by calling FreePooled() instead of Free()-method. Do not release the returned ManagedHandle. + public static (ManagedHandle managedHandle, ManagedArray managedArray) AllocatePooledArray(int length) where T : unmanaged { ManagedArray managedArray = ManagedArrayPool.Get(length * Unsafe.SizeOf()); managedArray.Allocate(length); - return managedArray; + + if (pooledArrayHandles == null) + pooledArrayHandles = new(); + if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle)) + { + handle = ManagedHandle.Alloc(managedArray); + pooledArrayHandles.Add(managedArray, handle); + } + return (handle, managedArray); } public ManagedArray(Array arr, Type elementType) => WrapArray(arr, elementType); diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs index 2e3bc9183..0f7d238bc 100644 --- a/Source/Engine/Engine/NativeInterop.Marshallers.cs +++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs @@ -200,14 +200,13 @@ namespace FlaxEngine.Interop public void FromManaged(Array managed) { if (managed != null) - managedArray = ManagedArray.WrapPooledArray(managed); + (handle, managedArray) = ManagedArray.WrapPooledArray(managed); } public IntPtr ToUnmanaged() { if (managedArray == null) return IntPtr.Zero; - handle = ManagedHandle.Alloc(managedArray, GCHandleType.Weak); return ManagedHandle.ToIntPtr(handle); } @@ -216,7 +215,6 @@ namespace FlaxEngine.Interop if (managedArray == null) return; managedArray.FreePooled(); - //handle.Free(); // No need to free weak handles } } @@ -335,7 +333,6 @@ namespace FlaxEngine.Interop #endif [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ManagedToUnmanagedIn, typeof(ArrayMarshaller<,>.ManagedToNative))] [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.UnmanagedToManagedOut, typeof(ArrayMarshaller<,>.ManagedToNative))] - [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ElementIn, typeof(ArrayMarshaller<,>.ManagedToNative))] [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ManagedToUnmanagedOut, typeof(ArrayMarshaller<,>.NativeToManaged))] [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.UnmanagedToManagedIn, typeof(ArrayMarshaller<,>.NativeToManaged))] [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ElementOut, typeof(ArrayMarshaller<,>.NativeToManaged))] @@ -388,38 +385,28 @@ namespace FlaxEngine.Interop #if FLAX_EDITOR [HideInEditor] #endif - public static class ManagedToNative + public ref struct ManagedToNative { - public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements) + T[] sourceArray; + ManagedArray managedArray; + ManagedHandle managedHandle; + + public void FromManaged(T[] managed) { if (managed is null) - { - numElements = 0; - return null; - } - numElements = managed.Length; - ManagedArray managedArray = ManagedArray.AllocatePooledArray(managed.Length); - return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Normal); - } - - public static ReadOnlySpan GetManagedValuesSource(T[] managed) => managed; - - public static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) - { - if (unmanaged == null) - return Span.Empty; - ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target); - return managedArray.ToSpan(); - } - - public static void Free(TUnmanagedElement* unmanaged) - { - if (unmanaged == null) return; - ManagedHandle handle = ManagedHandle.FromIntPtr(new IntPtr(unmanaged)); - (Unsafe.As(handle.Target)).FreePooled(); - //handle.Free(); // No need to free weak handles + + sourceArray = managed; + (managedHandle, managedArray) = ManagedArray.AllocatePooledArray(managed.Length); } + + public ReadOnlySpan GetManagedValuesSource() => sourceArray; + + public Span GetUnmanagedValuesDestination() => managedArray.ToSpan(); + + public TUnmanagedElement* ToUnmanaged() => (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedHandle); + + public void Free() => managedArray.FreePooled(); } #if FLAX_EDITOR @@ -427,26 +414,25 @@ namespace FlaxEngine.Interop #endif public struct Bidirectional { - T[] managedArray; - ManagedArray unmanagedArray; + T[] sourceArray; + ManagedArray managedArray; ManagedHandle handle; public void FromManaged(T[] managed) { if (managed == null) return; - managedArray = managed; - unmanagedArray = ManagedArray.AllocatePooledArray(managed.Length); - handle = ManagedHandle.Alloc(unmanagedArray); + sourceArray = managed; + (handle, managedArray) = ManagedArray.AllocatePooledArray(managed.Length); } - public ReadOnlySpan GetManagedValuesSource() => managedArray; + public ReadOnlySpan GetManagedValuesSource() => sourceArray; public Span GetUnmanagedValuesDestination() { - if (unmanagedArray == null) + if (managedArray == null) return Span.Empty; - return unmanagedArray.ToSpan(); + return managedArray.ToSpan(); } public TUnmanagedElement* ToUnmanaged() => (TUnmanagedElement*)ManagedHandle.ToIntPtr(handle); @@ -454,26 +440,22 @@ namespace FlaxEngine.Interop public void FromUnmanaged(TUnmanagedElement* unmanaged) { ManagedArray arr = Unsafe.As(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target); - if (managedArray == null || managedArray.Length != arr.Length) - managedArray = new T[arr.Length]; + if (sourceArray == null || sourceArray.Length != arr.Length) + sourceArray = new T[arr.Length]; } public ReadOnlySpan GetUnmanagedValuesSource(int numElements) { - if (unmanagedArray == null) + if (managedArray == null) return ReadOnlySpan.Empty; - return unmanagedArray.ToSpan(); + return managedArray.ToSpan(); } - public Span GetManagedValuesDestination(int numElements) => managedArray; + public Span GetManagedValuesDestination(int numElements) => sourceArray; - public T[] ToManaged() => managedArray; + public T[] ToManaged() => sourceArray; - public void Free() - { - unmanagedArray.FreePooled(); - handle.Free(); - } + public void Free() => managedArray.FreePooled(); } public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements) @@ -484,9 +466,8 @@ namespace FlaxEngine.Interop return null; } numElements = managed.Length; - ManagedArray managedArray = ManagedArray.AllocatePooledArray(managed.Length); - IntPtr handle = ManagedHandle.ToIntPtr(managedArray); - return (TUnmanagedElement*)handle; + (ManagedHandle managedArrayHandle, _) = ManagedArray.AllocatePooledArray(managed.Length); + return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArrayHandle); } public static ReadOnlySpan GetManagedValuesSource(T[] managed) => managed; @@ -517,7 +498,6 @@ namespace FlaxEngine.Interop return; ManagedHandle handle = ManagedHandle.FromIntPtr(new IntPtr(unmanaged)); Unsafe.As(handle.Target).FreePooled(); - handle.Free(); } } From 65de284a66e11ae729bae909395efe9fb76768e0 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 5 Aug 2023 17:36:35 +0300 Subject: [PATCH 25/76] Optimize managed method invokers return value marshalling --- Source/Engine/Engine/NativeInterop.Invoker.cs | 264 ++++++++++++------ 1 file changed, 175 insertions(+), 89 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Invoker.cs b/Source/Engine/Engine/NativeInterop.Invoker.cs index 2fc54597c..ade1e3570 100644 --- a/Source/Engine/Engine/NativeInterop.Invoker.cs +++ b/Source/Engine/Engine/NativeInterop.Invoker.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Diagnostics; +using System.Collections.Generic; namespace FlaxEngine.Interop { @@ -20,28 +21,145 @@ namespace FlaxEngine.Interop { // TODO: Use .NET8 Unsafe.BitCast(returnValue) for more efficient casting of value types over boxing cast - internal static IntPtr MarshalReturnValue(ref TRet returnValue) + internal static class InvokerMarshallers + { + internal delegate IntPtr Delegate(ref T value); + internal static Delegate deleg; + internal static Delegate delegThunk; + + static InvokerMarshallers() + { + Type type = typeof(T); + if (type == typeof(string)) + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueString), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(ManagedHandle)) + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueManagedHandle), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(Type)) + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueType), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type.IsArray) + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueArray), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate(); + else if (type == typeof(bool)) + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueBool), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueWrapped), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate(); + + if (type == typeof(string)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueString), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(ManagedHandle)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueManagedHandle), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(Type)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueType), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type.IsArray) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueArray), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate(); + else if (type == typeof(System.Boolean)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueMonoBoolean), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(IntPtr)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueIntPtr), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.Int16)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueInt16), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.Int32)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueInt32), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.Int64)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueInt64), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.UInt16)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueUInt16), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.UInt32)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueUInt32), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.UInt64)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueUInt64), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueWrapped), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate(); + } + } + + internal static IntPtr MarshalReturnValueString(ref string returnValue) + { + if (returnValue == null) + return IntPtr.Zero; + return ManagedString.ToNativeWeak(returnValue); + } + + internal static IntPtr MarshalReturnValueManagedHandle(ref ManagedHandle returnValue) + { + if (returnValue == null) + return IntPtr.Zero; + return ManagedHandle.ToIntPtr(returnValue); + } + + internal static IntPtr MarshalReturnValueType(ref Type returnValue) + { + if (returnValue == null) + return IntPtr.Zero; + return ManagedHandle.ToIntPtr(GetTypeGCHandle(returnValue)); + } + + internal static IntPtr MarshalReturnValueArray(ref TRet returnValue) + { + if (returnValue == null) + return IntPtr.Zero; + var elementType = typeof(TRet).GetElementType(); + if (ArrayFactory.GetMarshalledType(elementType) == elementType) + return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnValue)), GCHandleType.Weak); + return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As(returnValue)), GCHandleType.Weak); + } + + internal static IntPtr MarshalReturnValueBool(ref bool returnValue) + { + return returnValue ? boolTruePtr : boolFalsePtr; + } + + internal static IntPtr MarshalReturnValueIntPtr(ref IntPtr returnValue) + { + return returnValue; + } + + internal static IntPtr MarshalReturnValueMonoBoolean(ref bool returnValue) + { + return returnValue ? 1 : 0; + } + + internal static IntPtr MarshalReturnValueInt16(ref Int16 returnValue) + { + return returnValue; + } + + internal static IntPtr MarshalReturnValueInt32(ref Int32 returnValue) + { + return returnValue; + } + + internal static IntPtr MarshalReturnValueInt64(ref Int64 returnValue) + { + return new IntPtr(returnValue); + } + + internal static IntPtr MarshalReturnValueUInt16(ref UInt16 returnValue) + { + return returnValue; + } + + internal static IntPtr MarshalReturnValueUInt32(ref UInt32 returnValue) + { + return new IntPtr(returnValue); + } + + internal static IntPtr MarshalReturnValueUInt64(ref UInt64 returnValue) + { + return new IntPtr((long)returnValue); + } + + internal static IntPtr MarshalReturnValueWrapped(ref TRet returnValue) { if (returnValue == null) return IntPtr.Zero; - if (typeof(TRet) == typeof(string)) - return ManagedString.ToNativeWeak(Unsafe.As(returnValue)); - if (typeof(TRet) == typeof(ManagedHandle)) - return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnValue); - if (typeof(TRet) == typeof(bool)) - return (bool)(object)returnValue ? boolTruePtr : boolFalsePtr; - if (typeof(TRet) == typeof(Type)) - return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnValue))); - if (typeof(TRet).IsArray) - { - var elementType = typeof(TRet).GetElementType(); - if (ArrayFactory.GetMarshalledType(elementType) == elementType) - return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnValue)), GCHandleType.Weak); - return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As(returnValue)), GCHandleType.Weak); - } return ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak); } + internal static IntPtr MarshalReturnValue(ref TRet returnValue) + { + return InvokerMarshallers.deleg(ref returnValue); + } + internal static IntPtr MarshalReturnValueGeneric(Type returnType, object returnObject) { if (returnObject == null) @@ -63,39 +181,7 @@ namespace FlaxEngine.Interop internal static IntPtr MarshalReturnValueThunk(ref TRet returnValue) { - if (returnValue == null) - return IntPtr.Zero; - if (typeof(TRet) == typeof(string)) - return ManagedString.ToNativeWeak(Unsafe.As(returnValue)); - if (typeof(TRet) == typeof(IntPtr)) - return (IntPtr)(object)returnValue; - if (typeof(TRet) == typeof(ManagedHandle)) - return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnValue); - if (typeof(TRet) == typeof(Type)) - return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnValue))); - if (typeof(TRet).IsArray) - { - var elementType = typeof(TRet).GetElementType(); - if (ArrayFactory.GetMarshalledType(elementType) == elementType) - return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnValue)), GCHandleType.Weak); - return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As(returnValue)), GCHandleType.Weak); - } - // Match Mono bindings and pass value as pointer to prevent boxing it - if (typeof(TRet) == typeof(System.Boolean)) - return new IntPtr(((System.Boolean)(object)returnValue) ? 1 : 0); - if (typeof(TRet) == typeof(System.Int16)) - return new IntPtr((int)(System.Int16)(object)returnValue); - if (typeof(TRet) == typeof(System.Int32)) - return new IntPtr((int)(System.Int32)(object)returnValue); - if (typeof(TRet) == typeof(System.Int64)) - return new IntPtr((long)(System.Int64)(object)returnValue); - if (typeof(TRet) == typeof(System.UInt16)) - return (IntPtr)new UIntPtr((ulong)(System.UInt16)(object)returnValue); - if (typeof(TRet) == typeof(System.UInt32)) - return (IntPtr)new UIntPtr((ulong)(System.UInt32)(object)returnValue); - if (typeof(TRet) == typeof(System.UInt64)) - return (IntPtr)new UIntPtr((ulong)(System.UInt64)(object)returnValue); - return ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak); + return InvokerMarshallers.delegThunk(ref returnValue); } internal static IntPtr MarshalReturnValueThunkGeneric(Type returnType, object returnObject) @@ -205,7 +291,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -215,7 +301,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -242,7 +328,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -262,7 +348,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -291,7 +377,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -317,7 +403,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -347,7 +433,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -379,7 +465,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -410,7 +496,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -448,7 +534,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -480,7 +566,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -490,7 +576,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -517,7 +603,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -537,7 +623,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -566,7 +652,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -592,7 +678,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -622,7 +708,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -654,7 +740,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -685,7 +771,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -723,7 +809,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -755,7 +841,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -765,7 +851,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -792,7 +878,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -812,7 +898,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -841,7 +927,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -867,7 +953,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -897,7 +983,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -929,7 +1015,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -960,7 +1046,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -998,7 +1084,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -1030,7 +1116,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -1040,7 +1126,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -1067,7 +1153,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -1087,7 +1173,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -1116,7 +1202,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -1142,7 +1228,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -1172,7 +1258,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -1204,7 +1290,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -1235,7 +1321,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -1273,7 +1359,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); From 1b0976d99b4f828c41fabe0b5e58479d49e997c8 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 4 Aug 2023 22:48:28 +0300 Subject: [PATCH 26/76] Refactor ManagedHandlePool Weak handles are now stored in one sets of dictionarys synchronized with other threads. This so far seems the fastest way to manage the dictionaries for now. --- Source/Engine/Engine/NativeInterop.Managed.cs | 269 ++++++++++-------- 1 file changed, 147 insertions(+), 122 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index 95146a95c..78aa4dc37 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -330,15 +330,9 @@ namespace FlaxEngine.Interop { private IntPtr handle; - private ManagedHandle(IntPtr handle) - { - this.handle = handle; - } + private ManagedHandle(IntPtr handle) => this.handle = handle; - private ManagedHandle(object value, GCHandleType type) - { - handle = ManagedHandlePool.AllocateHandle(value, type); - } + private ManagedHandle(object value, GCHandleType type) => handle = ManagedHandlePool.AllocateHandle(value, type); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ManagedHandle Alloc(object value) => new ManagedHandle(value, GCHandleType.Normal); @@ -383,7 +377,7 @@ namespace FlaxEngine.Interop public override int GetHashCode() => handle.GetHashCode(); - public override bool Equals(object o) => o is ManagedHandle other && Equals(other); + public override bool Equals(object obj) => obj is ManagedHandle other && handle == other.handle; public bool Equals(ManagedHandle other) => handle == other.handle; @@ -391,42 +385,44 @@ namespace FlaxEngine.Interop public static bool operator !=(ManagedHandle a, ManagedHandle b) => a.handle != b.handle; - private static class ManagedHandlePool + internal static class ManagedHandlePool { private const int WeakPoolCollectionSizeThreshold = 10000000; private const int WeakPoolCollectionTimeThreshold = 500; - private static ulong normalHandleAccumulator = 0; - private static ulong pinnedHandleAccumulator = 0; - private static ulong weakHandleAccumulator = 0; + // Rolling numbers for handles, two bits reserved for the type + private static ulong normalHandleAccumulator = ((ulong)GCHandleType.Normal << 62) & 0xC000000000000000; + private static ulong pinnedHandleAccumulator = ((ulong)GCHandleType.Pinned << 62) & 0xC000000000000000; + private static ulong weakHandleAccumulator = ((ulong)GCHandleType.Weak << 62) & 0xC000000000000000; - private static object poolLock = new object(); - private static Dictionary persistentPool = new Dictionary(); - private static Dictionary pinnedPool = new Dictionary(); + // Dictionaries for storing the valid handles. + // Note: Using locks seems to be generally the fastest when adding or fetching from the dictionary. + // Concurrent dictionaries could also be considered, but they perform much slower when adding to the dictionary. + private static Dictionary persistentPool = new(); + private static Dictionary pinnedPool = new(); - // Manage double-buffered pool for weak handles in order to avoid collecting in-flight handles - [ThreadStatic] private static Dictionary weakPool; - [ThreadStatic] private static Dictionary weakPoolOther; - [ThreadStatic] private static ulong nextWeakPoolCollection; - [ThreadStatic] private static int nextWeakPoolGCCollection; - [ThreadStatic] private static long lastWeakPoolCollectionTime; + // TODO: Performance of pinned handles are poor at the moment due to GCHandle wrapping. + // TODO: .NET8: Experiment with pinned arrays for faster pinning: https://github.com/dotnet/runtime/pull/89293 + + // Manage double-buffered pool for weak handles in order to avoid collecting in-flight handles. + // Periodically when the pools are being accessed and conditions are met, the other pool is cleared and swapped. + private static Dictionary weakPool = new(); + private static Dictionary weakPoolOther = new(); + private static object weakPoolLock = new object(); + private static ulong nextWeakPoolCollection; + private static int nextWeakPoolGCCollection; + private static long lastWeakPoolCollectionTime; /// /// Tries to free all references to old weak handles so GC can collect them. /// - private static void TryCollectWeakHandles() + internal static void TryCollectWeakHandles() { if (weakHandleAccumulator < nextWeakPoolCollection) return; nextWeakPoolCollection = weakHandleAccumulator + 1000; - if (weakPool == null) - { - weakPool = new Dictionary(); - weakPoolOther = new Dictionary(); - nextWeakPoolGCCollection = GC.CollectionCount(0) + 1; - return; - } + // Try to swap pools after garbage collection or whenever the pool gets too large var gc0CollectionCount = GC.CollectionCount(0); if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold) @@ -443,130 +439,159 @@ namespace FlaxEngine.Interop weakPool.Clear(); } - private static IntPtr NewHandle(GCHandleType type) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static IntPtr NewHandle(GCHandleType type) => type switch { - IntPtr handle; - if (type == GCHandleType.Normal) - handle = (IntPtr)Interlocked.Increment(ref normalHandleAccumulator); - else if (type == GCHandleType.Pinned) - handle = (IntPtr)Interlocked.Increment(ref pinnedHandleAccumulator); - else //if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection) - handle = (IntPtr)Interlocked.Increment(ref weakHandleAccumulator); - - // Two bits reserved for the type - handle |= (IntPtr)(((ulong)type << 62) & 0xC000000000000000); - return handle; - } + GCHandleType.Normal => (IntPtr)Interlocked.Increment(ref normalHandleAccumulator), + GCHandleType.Pinned => (IntPtr)Interlocked.Increment(ref pinnedHandleAccumulator), + GCHandleType.Weak => (IntPtr)Interlocked.Increment(ref weakHandleAccumulator), + GCHandleType.WeakTrackResurrection => (IntPtr)Interlocked.Increment(ref weakHandleAccumulator), + _ => throw new NotImplementedException(type.ToString()) + }; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static GCHandleType GetHandleType(IntPtr handle) - { - return (GCHandleType)(((ulong)handle & 0xC000000000000000) >> 62); - } + private static GCHandleType GetHandleType(IntPtr handle) => (GCHandleType)(((ulong)handle & 0xC000000000000000) >> 62); internal static IntPtr AllocateHandle(object value, GCHandleType type) { - TryCollectWeakHandles(); IntPtr handle = NewHandle(type); - if (type == GCHandleType.Normal) + switch (type) { - lock (poolLock) - persistentPool.Add(handle, value); + case GCHandleType.Normal: + lock (persistentPool) + persistentPool.Add(handle, value); + break; + case GCHandleType.Pinned: + lock (pinnedPool) + pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned)); + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + weakPool.Add(handle, value); + } + break; } - else if (type == GCHandleType.Pinned) - { - lock (poolLock) - pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned)); - } - else if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection) - weakPool.Add(handle, value); - return handle; } internal static object GetObject(IntPtr handle) { - TryCollectWeakHandles(); - object value; - GCHandleType type = GetHandleType(handle); - if (type == GCHandleType.Normal) + switch (GetHandleType(handle)) { - lock (poolLock) - { - if (persistentPool.TryGetValue(handle, out value)) - return value; - } + case GCHandleType.Normal: + lock (persistentPool) + { + if (persistentPool.TryGetValue(handle, out object value)) + return value; + } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + if (pinnedPool.TryGetValue(handle, out GCHandle gchandle)) + return gchandle.Target; + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + { + lock (weakPoolLock) + { + TryCollectWeakHandles(); + if (weakPool.TryGetValue(handle, out object value)) + return value; + else if (weakPoolOther.TryGetValue(handle, out value)) + return value; + } + } + break; } - else if (type == GCHandleType.Pinned) - { - lock (poolLock) - { - if (pinnedPool.TryGetValue(handle, out GCHandle gchandle)) - return gchandle.Target; - } - } - else if (weakPool.TryGetValue(handle, out value)) - return value; - else if (weakPoolOther.TryGetValue(handle, out value)) - return value; - throw new NativeInteropException("Invalid ManagedHandle"); } internal static void SetObject(IntPtr handle, object value) { - TryCollectWeakHandles(); - GCHandleType type = GetHandleType(handle); - if (type == GCHandleType.Normal) + switch (GetHandleType(handle)) { - lock (poolLock) - { - if (persistentPool.ContainsKey(handle)) - persistentPool[handle] = value; - } + case GCHandleType.Normal: + lock (persistentPool) + { + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle); + if (!Unsafe.IsNullRef(ref obj)) + { + obj = value; + return; + } + } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + ref GCHandle gchandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle); + if (!Unsafe.IsNullRef(ref gchandle)) + { + gchandle.Target = value; + return; + } + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + { + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPool, handle); + if (!Unsafe.IsNullRef(ref obj)) + { + obj = value; + return; + } + } + { + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPoolOther, handle); + if (!Unsafe.IsNullRef(ref obj)) + { + obj = value; + return; + } + } + } + break; } - else if (type == GCHandleType.Pinned) - { - lock (poolLock) - { - if (pinnedPool.TryGetValue(handle, out GCHandle gchandle)) - gchandle.Target = value; - } - } - else if (weakPool.ContainsKey(handle)) - weakPool[handle] = value; - else if (weakPoolOther.ContainsKey(handle)) - weakPoolOther[handle] = value; - throw new NativeInteropException("Invalid ManagedHandle"); } internal static void FreeHandle(IntPtr handle) { - TryCollectWeakHandles(); - GCHandleType type = GetHandleType(handle); - if (type == GCHandleType.Normal) + switch (GetHandleType(handle)) { - lock (poolLock) - { - if (persistentPool.Remove(handle)) - return; - } - } - else if (type == GCHandleType.Pinned) - { - lock (poolLock) - { - if (pinnedPool.Remove(handle, out GCHandle gchandle)) + case GCHandleType.Normal: + lock (persistentPool) { - gchandle.Free(); - return; + if (persistentPool.Remove(handle)) + return; } - } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + if (pinnedPool.Remove(handle, out GCHandle gchandle)) + { + gchandle.Free(); + return; + } + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + TryCollectWeakHandles(); + return; } - else - return; - throw new NativeInteropException("Invalid ManagedHandle"); } } From 0008123e7638b7a7bc3bcba60ad6383be418546f Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 4 Aug 2023 21:37:38 +0300 Subject: [PATCH 27/76] Cache ManagedDictionary types and helper method thunks --- .../Scripting/Internal/ManagedDictionary.cpp | 14 ++ .../Scripting/Internal/ManagedDictionary.h | 140 +++++++++++++++--- 2 files changed, 130 insertions(+), 24 deletions(-) create mode 100644 Source/Engine/Scripting/Internal/ManagedDictionary.cpp diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.cpp b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp new file mode 100644 index 000000000..7cb4d1cfb --- /dev/null +++ b/Source/Engine/Scripting/Internal/ManagedDictionary.cpp @@ -0,0 +1,14 @@ +#include "ManagedDictionary.h" + +Dictionary ManagedDictionary::CachedDictionaryTypes; +#if !USE_MONO_AOT +ManagedDictionary::MakeGenericTypeThunk ManagedDictionary::MakeGenericType; +ManagedDictionary::CreateInstanceThunk ManagedDictionary::CreateInstance; +ManagedDictionary::AddDictionaryItemThunk ManagedDictionary::AddDictionaryItem; +ManagedDictionary::GetDictionaryKeysThunk ManagedDictionary::GetDictionaryKeys; +#else +MMethod* ManagedDictionary::MakeGenericType; +MMethod* ManagedDictionary::CreateInstance; +MMethod* ManagedDictionary::AddDictionaryItem; +MMethod* ManagedDictionary::GetDictionaryKeys; +#endif diff --git a/Source/Engine/Scripting/Internal/ManagedDictionary.h b/Source/Engine/Scripting/Internal/ManagedDictionary.h index c42eb1d27..f957dfcec 100644 --- a/Source/Engine/Scripting/Internal/ManagedDictionary.h +++ b/Source/Engine/Scripting/Internal/ManagedDictionary.h @@ -12,17 +12,96 @@ #include "Engine/Scripting/ManagedCLR/MAssembly.h" #include "Engine/Scripting/ManagedCLR/MException.h" #include "Engine/Scripting/Internal/StdTypesContainer.h" +#include "Engine/Core/Collections/Dictionary.h" /// /// Utility interop between C++ and C# for Dictionary collection. /// struct FLAXENGINE_API ManagedDictionary { +public: + struct KeyValueType + { + MType* keyType; + MType* valueType; + + bool operator==(const KeyValueType& other) const + { + return keyType == other.keyType && valueType == other.valueType; + } + }; + +private: + static Dictionary CachedDictionaryTypes; + +#if !USE_MONO_AOT + typedef MTypeObject* (*MakeGenericTypeThunk)(MObject* instance, MTypeObject* genericType, MArray* genericArgs, MObject** exception); + static MakeGenericTypeThunk MakeGenericType; + + typedef MObject* (*CreateInstanceThunk)(MObject* instance, MTypeObject* type, void* arr, MObject** exception); + static CreateInstanceThunk CreateInstance; + + typedef void (*AddDictionaryItemThunk)(MObject* instance, MObject* dictionary, MObject* key, MObject* value, MObject** exception); + static AddDictionaryItemThunk AddDictionaryItem; + + typedef MArray* (*GetDictionaryKeysThunk)(MObject* instance, MObject* dictionary, MObject** exception); + static GetDictionaryKeysThunk GetDictionaryKeys; +#else + static MMethod* MakeGenericType; + static MMethod* CreateInstance; + static MMethod* AddDictionaryItem; + static MMethod* GetDictionaryKeys; +#endif + +public: MObject* Instance; ManagedDictionary(MObject* instance = nullptr) { Instance = instance; + +#if !USE_MONO_AOT + // Cache the thunks of the dictionary helper methods + if (MakeGenericType == nullptr) + { + MClass* scriptingClass = Scripting::GetStaticClass(); + CHECK(scriptingClass); + + MMethod* makeGenericTypeMethod = scriptingClass->GetMethod("MakeGenericType", 2); + CHECK(makeGenericTypeMethod); + MakeGenericType = (MakeGenericTypeThunk)makeGenericTypeMethod->GetThunk(); + + MMethod* createInstanceMethod = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); + CHECK(createInstanceMethod); + CreateInstance = (CreateInstanceThunk)createInstanceMethod->GetThunk(); + + MMethod* addDictionaryItemMethod = scriptingClass->GetMethod("AddDictionaryItem", 3); + CHECK(addDictionaryItemMethod); + AddDictionaryItem = (AddDictionaryItemThunk)addDictionaryItemMethod->GetThunk(); + + MMethod* getDictionaryKeysItemMethod = scriptingClass->GetMethod("GetDictionaryKeys", 1); + CHECK(getDictionaryKeysItemMethod); + GetDictionaryKeys = (GetDictionaryKeysThunk)getDictionaryKeysItemMethod->GetThunk(); + } +#else + if (MakeGenericType == nullptr) + { + MClass* scriptingClass = Scripting::GetStaticClass(); + CHECK(scriptingClass); + + MakeGenericType = scriptingClass->GetMethod("MakeGenericType", 2); + CHECK(MakeGenericType); + + CreateInstance = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); + CHECK(CreateInstance); + + AddDictionaryItem = scriptingClass->GetMethod("AddDictionaryItem", 3); + CHECK(AddDictionaryItem); + + GetDictionaryKeys = scriptingClass->GetMethod("GetDictionaryKeys", 1); + CHECK(GetDictionaryKeys); + } +#endif } template @@ -76,10 +155,11 @@ struct FLAXENGINE_API ManagedDictionary static MTypeObject* GetClass(MType* keyType, MType* valueType) { - MClass* scriptingClass = Scripting::GetStaticClass(); - CHECK_RETURN(scriptingClass, nullptr); - MMethod* makeGenericMethod = scriptingClass->GetMethod("MakeGenericType", 2); - CHECK_RETURN(makeGenericMethod, nullptr); + // Check if the generic type was generated earlier + KeyValueType cacheKey = { keyType, valueType }; + MTypeObject* dictionaryType; + if (CachedDictionaryTypes.TryGet(cacheKey, dictionaryType)) + return dictionaryType; MTypeObject* genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass); #if USE_NETCORE @@ -91,18 +171,23 @@ struct FLAXENGINE_API ManagedDictionary genericArgsPtr[0] = INTERNAL_TYPE_GET_OBJECT(keyType); genericArgsPtr[1] = INTERNAL_TYPE_GET_OBJECT(valueType); + MObject* exception = nullptr; +#if !USE_MONO_AOT + dictionaryType = MakeGenericType(nullptr, genericType, genericArgs, &exception); +#else void* params[2]; params[0] = genericType; params[1] = genericArgs; - MObject* exception = nullptr; - MObject* dictionaryType = makeGenericMethod->Invoke(nullptr, params, &exception); + dictionaryType = (MTypeObject*)MakeGenericType->Invoke(nullptr, params, &exception); +#endif if (exception) { MException ex(exception); ex.Log(LogType::Error, TEXT("")); return nullptr; } - return (MTypeObject*)dictionaryType; + CachedDictionaryTypes.Add(cacheKey, dictionaryType); + return dictionaryType; } static ManagedDictionary New(MType* keyType, MType* valueType) @@ -112,16 +197,15 @@ struct FLAXENGINE_API ManagedDictionary if (!dictionaryType) return result; - MClass* scriptingClass = Scripting::GetStaticClass(); - CHECK_RETURN(scriptingClass, result); - MMethod* createMethod = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); - CHECK_RETURN(createMethod, result); - MObject* exception = nullptr; +#if !USE_MONO_AOT + MObject* instance = CreateInstance(nullptr, dictionaryType, nullptr, &exception); +#else void* params[2]; params[0] = dictionaryType; params[1] = nullptr; - MObject* instance = createMethod->Invoke(nullptr, params, &exception); + MObject* instance = CreateInstance->Invoke(nullptr, params, &exception); +#endif if (exception) { MException ex(exception); @@ -136,16 +220,17 @@ struct FLAXENGINE_API ManagedDictionary void Add(MObject* key, MObject* value) { CHECK(Instance); - MClass* scriptingClass = Scripting::GetStaticClass(); - CHECK(scriptingClass); - MMethod* addDictionaryItemMethod = scriptingClass->GetMethod("AddDictionaryItem", 3); - CHECK(addDictionaryItemMethod); + + MObject* exception = nullptr; +#if !USE_MONO_AOT + AddDictionaryItem(nullptr, Instance, key, value, &exception); +#else void* params[3]; params[0] = Instance; params[1] = key; params[2] = value; - MObject* exception = nullptr; - addDictionaryItemMethod->Invoke(Instance, params, &exception); + AddDictionaryItem->Invoke(Instance, params, &exception); +#endif if (exception) { MException ex(exception); @@ -156,13 +241,13 @@ struct FLAXENGINE_API ManagedDictionary MArray* GetKeys() const { CHECK_RETURN(Instance, nullptr); - MClass* scriptingClass = Scripting::GetStaticClass(); - CHECK_RETURN(scriptingClass, nullptr); - MMethod* getDictionaryKeysMethod = scriptingClass->GetMethod("GetDictionaryKeys", 1); - CHECK_RETURN(getDictionaryKeysMethod, nullptr); +#if !USE_MONO_AOT + return GetDictionaryKeys(nullptr, Instance, nullptr); +#else void* params[1]; params[0] = Instance; - return (MArray*)getDictionaryKeysMethod->Invoke( nullptr, params, nullptr); + return (MArray*)GetDictionaryKeys->Invoke(nullptr, params, nullptr); +#endif } MObject* GetValue(MObject* key) const @@ -177,4 +262,11 @@ struct FLAXENGINE_API ManagedDictionary } }; +inline uint32 GetHash(const ManagedDictionary::KeyValueType& other) +{ + uint32 hash = ::GetHash((void*)other.keyType); + CombineHash(hash, ::GetHash((void*)other.valueType)); + return hash; +} + #endif From cd56101aa3ebbb269d72274bd7138d5380ffab82 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 7 Aug 2023 21:51:45 +0300 Subject: [PATCH 28/76] Expose `Object::DeleteObjectNow` as `Object.DestroyNow` in managed scripting --- Source/Engine/Scripting/Object.cs | 15 +++++++++++++++ Source/Engine/Scripting/ScriptingObject.cpp | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/Source/Engine/Scripting/Object.cs b/Source/Engine/Scripting/Object.cs index 0180c0935..170367b7e 100644 --- a/Source/Engine/Scripting/Object.cs +++ b/Source/Engine/Scripting/Object.cs @@ -179,6 +179,18 @@ namespace FlaxEngine Internal_Destroy(GetUnmanagedPtr(obj), timeLeft); } + /// + /// Destroys the specified object and clears the reference variable. + /// The object obj will be destroyed immediately. + /// If obj is a Script it will be removed from the Actor and deleted. + /// If obj is an Actor it will be removed from the Scene and deleted as well as all its Scripts and all children of the Actor. + /// + /// The object to destroy. + public static void DestroyNow(Object obj) + { + Internal_DestroyNow(GetUnmanagedPtr(obj)); + } + /// /// Destroys the specified object and clears the reference variable. /// The object obj will be destroyed now or after the time specified in seconds from now. @@ -316,6 +328,9 @@ namespace FlaxEngine [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_Destroy", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] internal static partial void Internal_Destroy(IntPtr obj, float timeLeft); + [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_DestroyNow", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] + internal static partial void Internal_DestroyNow(IntPtr obj); + [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_GetTypeName", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] internal static partial string Internal_GetTypeName(IntPtr obj); diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index 9ece58f2a..12144385d 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -681,6 +681,12 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_Destroy(ScriptingObject* obj, float ti obj->DeleteObject(timeLeft, useGameTime); } +DEFINE_INTERNAL_CALL(void) ObjectInternal_DestroyNow(ScriptingObject* obj) +{ + if (obj) + obj->DeleteObjectNow(); +} + DEFINE_INTERNAL_CALL(MString*) ObjectInternal_GetTypeName(ScriptingObject* obj) { INTERNAL_CALL_CHECK_RETURN(obj, nullptr); @@ -777,6 +783,7 @@ public: ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ManagedInstanceCreated", &ObjectInternal_ManagedInstanceCreated); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ManagedInstanceDeleted", &ObjectInternal_ManagedInstanceDeleted); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_Destroy", &ObjectInternal_Destroy); + ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_DestroyNow", &ObjectInternal_DestroyNow); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_GetTypeName", &ObjectInternal_GetTypeName); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_FindObject", &ObjectInternal_FindObject); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_TryFindObject", &ObjectInternal_TryFindObject); From 5ae27a0e92e2940e98d873b941935585a57265c1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 15:25:12 +0200 Subject: [PATCH 29/76] Code style fix --- .../VisualStudio/VCProjectGenerator.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index 76a0cf04b..3e2a6c9b7 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -108,7 +108,7 @@ namespace Flax.Build.Projects.VisualStudio vcProjectFileContent.AppendLine(" false"); vcProjectFileContent.AppendLine(" ./"); vcProjectFileContent.AppendLine(" "); - + // Default properties vcProjectFileContent.AppendLine(" "); @@ -319,21 +319,21 @@ namespace Flax.Build.Projects.VisualStudio // IntelliSense information - List additionalOptions = new List(); + var additionalOptions = new List(); switch (project.Configurations[0].TargetBuildOptions.CompileEnv.CppVersion) { - case CppVersion.Cpp14: - additionalOptions.Add("/std:c++14"); - break; - case CppVersion.Cpp17: - additionalOptions.Add("/std:c++17"); - break; - case CppVersion.Cpp20: - additionalOptions.Add("/std:c++20"); - break; - case CppVersion.Latest: - additionalOptions.Add("/std:c++latest"); - break; + case CppVersion.Cpp14: + additionalOptions.Add("/std:c++14"); + break; + case CppVersion.Cpp17: + additionalOptions.Add("/std:c++17"); + break; + case CppVersion.Cpp20: + additionalOptions.Add("/std:c++20"); + break; + case CppVersion.Latest: + additionalOptions.Add("/std:c++latest"); + break; } vcProjectFileContent.AppendLine(" "); From 13e0582ef20ab15358d126bbe9a1358896fb4125 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 15:36:19 +0200 Subject: [PATCH 30/76] Codestyle fixes --- Source/Engine/Engine/NativeInterop.Invoker.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Invoker.cs b/Source/Engine/Engine/NativeInterop.Invoker.cs index ade1e3570..381e6dac0 100644 --- a/Source/Engine/Engine/NativeInterop.Invoker.cs +++ b/Source/Engine/Engine/NativeInterop.Invoker.cs @@ -74,23 +74,17 @@ namespace FlaxEngine.Interop internal static IntPtr MarshalReturnValueString(ref string returnValue) { - if (returnValue == null) - return IntPtr.Zero; - return ManagedString.ToNativeWeak(returnValue); + return returnValue != null ? ManagedString.ToNativeWeak(returnValue) : IntPtr.Zero; } internal static IntPtr MarshalReturnValueManagedHandle(ref ManagedHandle returnValue) { - if (returnValue == null) - return IntPtr.Zero; - return ManagedHandle.ToIntPtr(returnValue); + return returnValue != null ? ManagedHandle.ToIntPtr(returnValue) : IntPtr.Zero; } internal static IntPtr MarshalReturnValueType(ref Type returnValue) { - if (returnValue == null) - return IntPtr.Zero; - return ManagedHandle.ToIntPtr(GetTypeGCHandle(returnValue)); + return returnValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(returnValue)) : IntPtr.Zero; } internal static IntPtr MarshalReturnValueArray(ref TRet returnValue) @@ -150,9 +144,7 @@ namespace FlaxEngine.Interop internal static IntPtr MarshalReturnValueWrapped(ref TRet returnValue) { - if (returnValue == null) - return IntPtr.Zero; - return ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak); + return returnValue != null ? ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak) : IntPtr.Zero; } internal static IntPtr MarshalReturnValue(ref TRet returnValue) From 860dce487f6e923537ae2dc16cb3428761f4bbd9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 15:47:24 +0200 Subject: [PATCH 31/76] Codestyle fixes --- Source/Engine/Engine/NativeInterop.Managed.cs | 192 +++++++++--------- 1 file changed, 95 insertions(+), 97 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index 3e1d0fa8c..e4dc449bc 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -62,7 +62,7 @@ namespace FlaxEngine.Interop { ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * NativeInterop.GetTypeSize(arr.GetType().GetElementType())); managedArray.WrapArray(arr, arrayType); - + if (pooledArrayHandles == null) pooledArrayHandles = new(); if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle)) @@ -484,22 +484,22 @@ namespace FlaxEngine.Interop IntPtr handle = NewHandle(type); switch (type) { - case GCHandleType.Normal: - lock (persistentPool) - persistentPool.Add(handle, value); - break; - case GCHandleType.Pinned: - lock (pinnedPool) - pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned)); - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - lock (weakPoolLock) - { - TryCollectWeakHandles(); - weakPool.Add(handle, value); - } - break; + case GCHandleType.Normal: + lock (persistentPool) + persistentPool.Add(handle, value); + break; + case GCHandleType.Pinned: + lock (pinnedPool) + pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned)); + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + weakPool.Add(handle, value); + } + break; } return handle; } @@ -508,33 +508,31 @@ namespace FlaxEngine.Interop { switch (GetHandleType(handle)) { - case GCHandleType.Normal: - lock (persistentPool) - { - if (persistentPool.TryGetValue(handle, out object value)) - return value; - } - break; - case GCHandleType.Pinned: - lock (pinnedPool) - { - if (pinnedPool.TryGetValue(handle, out GCHandle gchandle)) - return gchandle.Target; - } - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - { - lock (weakPoolLock) - { - TryCollectWeakHandles(); - if (weakPool.TryGetValue(handle, out object value)) - return value; - else if (weakPoolOther.TryGetValue(handle, out value)) - return value; - } - } - break; + case GCHandleType.Normal: + lock (persistentPool) + { + if (persistentPool.TryGetValue(handle, out object value)) + return value; + } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + if (pinnedPool.TryGetValue(handle, out GCHandle gcHandle)) + return gcHandle.Target; + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + if (weakPool.TryGetValue(handle, out object value)) + return value; + else if (weakPoolOther.TryGetValue(handle, out value)) + return value; + } + break; } throw new NativeInteropException("Invalid ManagedHandle"); } @@ -543,51 +541,51 @@ namespace FlaxEngine.Interop { switch (GetHandleType(handle)) { - case GCHandleType.Normal: - lock (persistentPool) + case GCHandleType.Normal: + lock (persistentPool) + { + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle); + if (!Unsafe.IsNullRef(ref obj)) { - ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle); + obj = value; + return; + } + } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + ref GCHandle gcHandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle); + if (!Unsafe.IsNullRef(ref gcHandle)) + { + gcHandle.Target = value; + return; + } + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + { + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPool, handle); if (!Unsafe.IsNullRef(ref obj)) { obj = value; return; } } - break; - case GCHandleType.Pinned: - lock (pinnedPool) { - ref GCHandle gchandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle); - if (!Unsafe.IsNullRef(ref gchandle)) + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPoolOther, handle); + if (!Unsafe.IsNullRef(ref obj)) { - gchandle.Target = value; + obj = value; return; } } - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - lock (weakPoolLock) - { - TryCollectWeakHandles(); - { - ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPool, handle); - if (!Unsafe.IsNullRef(ref obj)) - { - obj = value; - return; - } - } - { - ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPoolOther, handle); - if (!Unsafe.IsNullRef(ref obj)) - { - obj = value; - return; - } - } - } - break; + } + break; } throw new NativeInteropException("Invalid ManagedHandle"); } @@ -596,28 +594,28 @@ namespace FlaxEngine.Interop { switch (GetHandleType(handle)) { - case GCHandleType.Normal: - lock (persistentPool) + case GCHandleType.Normal: + lock (persistentPool) + { + if (persistentPool.Remove(handle)) + return; + } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + if (pinnedPool.Remove(handle, out GCHandle gcHandle)) { - if (persistentPool.Remove(handle)) - return; + gcHandle.Free(); + return; } - break; - case GCHandleType.Pinned: - lock (pinnedPool) - { - if (pinnedPool.Remove(handle, out GCHandle gchandle)) - { - gchandle.Free(); - return; - } - } - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - lock (weakPoolLock) - TryCollectWeakHandles(); - return; + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + TryCollectWeakHandles(); + return; } throw new NativeInteropException("Invalid ManagedHandle"); } From 66042845e4a4016c49cca3a1b61240a51e4702be Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 16:13:55 +0200 Subject: [PATCH 32/76] Codestyle fixe --- Source/Engine/Networking/NetworkReplicator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 4a0f9437d..c9651a055 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -1549,7 +1549,7 @@ void NetworkInternal::NetworkReplicatorUpdate() if (it == Objects.End()) { #if USE_EDITOR || !BUILD_RELEASE - if(!DespawnedObjects.Contains(obj->GetID())) + if (!DespawnedObjects.Contains(obj->GetID())) LOG(Error, "Cannot invoke RPC method '{0}.{1}' on object '{2}' that is not registered in networking (use 'NetworkReplicator.AddObject').", e.Name.First.ToString(), String(e.Name.Second), obj->GetID()); #endif continue; From 5e3018817cb47f66ca5df56110e4e9daba40b061 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 14 Aug 2023 18:24:53 +0300 Subject: [PATCH 33/76] Fix marshalling issue with PostFxMaterialSettings Materials --- Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index fc3e6a534..f399dd67c 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1596,7 +1596,9 @@ namespace Flax.Build.Bindings toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)) : null"); toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = (Unsafe.As(handle.Target)).ToSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); - freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = (Unsafe.As(handle.Target)).ToSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); + + // Permanent ScriptingObject handle is passed from native side, do not release it + //freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = (Unsafe.As(handle.Target)).ToSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } else { From 1948a84301099ba73c910e03434f5e3149dff6e8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 18:40:03 +0200 Subject: [PATCH 34/76] Add crash to soft-return if managed event bind target object native instance is gone #1278 --- Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index d69b8dcbf..da94a0056 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1116,7 +1116,7 @@ namespace Flax.Build.Bindings var signatureEnd = contents.Length; if (useSeparateImpl) { - // Write declarion only, function definition wil be put in the end of the file + // Write declaration only, function definition wil be put in the end of the file CppContentsEnd.AppendFormat("{0} {2}::{1}(", returnValueType, functionInfo.UniqueName, callerName); CppContentsEnd.Append(contents.ToString(signatureStart, signatureEnd - signatureStart)); contents.Append(';').AppendLine(); @@ -2016,7 +2016,7 @@ namespace Flax.Build.Bindings var indent = " "; if (useSeparateImpl) { - // Write declarion only, function definition wil be put in the end of the file + // Write declaration only, function definition wil be put in the end of the file CppContentsEnd.AppendFormat("void {1}::{0}_ManagedBind(", eventInfo.Name, internalTypeName); var sig = contents.ToString(signatureStart, contents.Length - signatureStart); CppContentsEnd.Append(contents.ToString(signatureStart, contents.Length - signatureStart)); @@ -2027,6 +2027,8 @@ namespace Flax.Build.Bindings contents.AppendLine().Append(indent).Append('{').AppendLine(); if (buildData.Toolchain?.Compiler == TargetCompiler.MSVC) contents.Append(indent).AppendLine($" MSVC_FUNC_EXPORT(\"{classTypeNameManaged}::Internal_{eventInfo.Name}_Bind\")"); // Export generated function binding under the C# name + if (!eventInfo.IsStatic) + contents.Append(indent).Append(" if (__obj == nullptr) return;").AppendLine(); contents.Append(indent).Append(" Function Date: Mon, 14 Aug 2023 18:44:21 +0200 Subject: [PATCH 35/76] Codestyle fix --- Source/Editor/Modules/SimulationModule.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Modules/SimulationModule.cs b/Source/Editor/Modules/SimulationModule.cs index 97676c2eb..72b197123 100644 --- a/Source/Editor/Modules/SimulationModule.cs +++ b/Source/Editor/Modules/SimulationModule.cs @@ -76,12 +76,12 @@ namespace FlaxEditor.Modules { switch (Editor.Options.Options.Interface.PlayButtonAction) { - case Options.InterfaceOptions.PlayAction.PlayGame: - Editor.Simulation.RequestPlayGameOrStopPlay(); - return; - case Options.InterfaceOptions.PlayAction.PlayScenes: - Editor.Simulation.RequestPlayScenesOrStopPlay(); - return; + case Options.InterfaceOptions.PlayAction.PlayGame: + Editor.Simulation.RequestPlayGameOrStopPlay(); + return; + case Options.InterfaceOptions.PlayAction.PlayScenes: + Editor.Simulation.RequestPlayScenesOrStopPlay(); + return; } } From 95fdcf01bea08b91eed198c22f396576ed58ece1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 18:53:11 +0200 Subject: [PATCH 36/76] Codestyle fix --- .../Viewport/MainEditorGizmoViewport.cs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 3132faf39..8d7039dc6 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -388,45 +388,40 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); - InputActions.Add(options => options.LockFocusSelection, LockFocusSelection); InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.RotateSelection, RotateSelection); InputActions.Add(options => options.Delete, _editor.SceneEditing.Delete); } + /// public override void Update(float deltaTime) { base.Update(deltaTime); - var hasSelections = TransformGizmo.SelectedParents.Count != 0; + var selection = TransformGizmo.SelectedParents; var requestUnlockFocus = FlaxEngine.Input.Mouse.GetButtonDown(MouseButton.Right) || FlaxEngine.Input.Mouse.GetButtonDown(MouseButton.Left); - - if ((IsFocused && requestUnlockFocus) || !hasSelections) + if (TransformGizmo.SelectedParents.Count == 0 || (requestUnlockFocus && ContainsFocus)) { UnlockFocusSelection(); } - - if (_lockedFocus) + else if (_lockedFocus) { var selectionBounds = BoundingSphere.Empty; - for (int i = 0; i < TransformGizmo.SelectedParents.Count; i++) + for (int i = 0; i < selection.Count; i++) { - TransformGizmo.SelectedParents[i].GetEditorSphere(out var sphere); + selection[i].GetEditorSphere(out var sphere); BoundingSphere.Merge(ref selectionBounds, ref sphere, out selectionBounds); } - var focusDistance = Mathf.Max(selectionBounds.Radius * 2d, 100d); - - if (IsFocused) + if (ContainsFocus) { var viewportFocusDistance = Vector3.Distance(ViewPosition, selectionBounds.Center) / 10f; _lockedFocusOffset -= FlaxEngine.Input.Mouse.ScrollDelta * viewportFocusDistance; } - var viewportPosition = selectionBounds.Center + (-ViewDirection * (focusDistance + _lockedFocusOffset)); - - ViewPosition = viewportPosition; + var focusDistance = Mathf.Max(selectionBounds.Radius * 2d, 100d); + ViewPosition = selectionBounds.Center + (-ViewDirection * (focusDistance + _lockedFocusOffset)); } } From cfd11a7e779d12a954a70f690003a3260eb873a9 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 14 Aug 2023 18:11:05 -0500 Subject: [PATCH 37/76] Add borderless window on Windows platform and add easy function in Screen class to change window mode. --- Source/Engine/Engine/Screen.cpp | 22 ++++++ Source/Engine/Engine/Screen.h | 10 +++ Source/Engine/Platform/Base/WindowBase.h | 9 +++ .../Engine/Platform/Windows/WindowsWindow.cpp | 70 +++++++++++++++++++ .../Engine/Platform/Windows/WindowsWindow.h | 1 + 5 files changed, 112 insertions(+) diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index 2fc58c7a3..b4e35ca7a 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -135,6 +135,28 @@ void Screen::SetCursorLock(CursorLockMode mode) CursorLock = mode; } +void Screen::SetGameWindowMode(GameWindowMode windowMode) +{ +#if (PLATFORM_WINDOWS) && !USE_EDITOR + switch (windowMode) + { + case GameWindowMode::Windowed: + Engine::MainWindow->SetBorderless(false, false); + break; + case GameWindowMode::Fullscreen: + SetIsFullscreen(true); + break; + case GameWindowMode::Borderless: + Engine::MainWindow->SetBorderless(true, false); + break; + case GameWindowMode::FullscreenBorderless: + Engine::MainWindow->SetBorderless(true, true); + break; + default: ; + } +#endif +} + void ScreenService::Update() { #if USE_EDITOR diff --git a/Source/Engine/Engine/Screen.h b/Source/Engine/Engine/Screen.h index 532529980..8f7d21766 100644 --- a/Source/Engine/Engine/Screen.h +++ b/Source/Engine/Engine/Screen.h @@ -2,6 +2,7 @@ #pragma once +#include "Engine/Core/Config/PlatformSettingsBase.h" #include "Engine/Scripting/ScriptingType.h" #include "Engine/Input/Enums.h" #include "Engine/Core/Math/Vector2.h" @@ -80,4 +81,13 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen); /// /// The mode. API_PROPERTY() static void SetCursorLock(CursorLockMode mode); + + /// + /// Sets the game window mode. + /// + /// + /// A fullscreen mode switch may not happen immediately. It will be performed before next frame rendering. Will not work in editor. + /// + /// The window mode. + API_PROPERTY() static void SetGameWindowMode(GameWindowMode windowMode); }; diff --git a/Source/Engine/Platform/Base/WindowBase.h b/Source/Engine/Platform/Base/WindowBase.h index 4220f1054..10b99d1cf 100644 --- a/Source/Engine/Platform/Base/WindowBase.h +++ b/Source/Engine/Platform/Base/WindowBase.h @@ -445,6 +445,15 @@ public: { } + /// + /// Sets the window to be borderless or not and to be fullscreen. + /// + /// Whether or not to have borders on window. + /// Whether or not to make the borderless window fullscreen. + API_FUNCTION() virtual void SetBorderless(bool isBorderless, bool fullscreen) + { + } + /// /// Restores the window state before minimizing or maximizing. /// diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index 687eeed28..ec5850b13 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -260,6 +260,76 @@ void WindowsWindow::Maximize() _isDuringMaximize = false; } +void WindowsWindow::SetBorderless(bool isBorderless, bool fullscreen) +{ + ASSERT(HasHWND()); + + if (IsFullscreen()) + SetIsFullscreen(false); + + // Fixes issue of borderless window not going full screen + if (IsMaximized()) + Restore(); + + _settings.HasBorder = !isBorderless; + + BringToFront(); + + if (isBorderless) + { + LONG lStyle = GetWindowLong(_handle, GWL_STYLE); + lStyle &= ~(WS_THICKFRAME | WS_SYSMENU | WS_OVERLAPPED | WS_BORDER | WS_CAPTION); + lStyle |= WS_POPUP; + lStyle |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; +#if WINDOWS_USE_NEW_BORDER_LESS + if (_settings.IsRegularWindow) + style |= WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_SYSMENU | WS_THICKFRAME | WS_GROUP; +#elif WINDOWS_USE_NEWER_BORDER_LESS + if (_settings.IsRegularWindow) + lStyle |= WS_THICKFRAME | WS_SYSMENU; +#endif + + SetWindowLong(_handle, GWL_STYLE, lStyle); + SetWindowPos(_handle, HWND_TOP, 0, 0,0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + + + if (fullscreen) + { + ShowWindow(_handle, SW_SHOWMAXIMIZED); + } + else + { + ShowWindow(_handle, SW_SHOW); + } + } + else + { + LONG lStyle = GetWindowLong(_handle, GWL_STYLE); + lStyle &= ~(WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS); + if (_settings.AllowMaximize) + lStyle |= WS_MAXIMIZEBOX; + if (_settings.AllowMinimize) + lStyle |= WS_MINIMIZEBOX; + if (_settings.HasSizingFrame) + lStyle |= WS_THICKFRAME; + // Create window style flags + lStyle |= WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION; + + SetWindowLong(_handle, GWL_STYLE, lStyle); + SetWindowPos(_handle, nullptr, 0, 0, (int)_settings.Size.X, (int)_settings.Size.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + + if (fullscreen) + { + Maximize(); + } + else + { + ShowWindow(_handle, SW_SHOW); + } + } + CheckForWindowResize(); +} + void WindowsWindow::Restore() { ASSERT(HasHWND()); diff --git a/Source/Engine/Platform/Windows/WindowsWindow.h b/Source/Engine/Platform/Windows/WindowsWindow.h index 74e923f86..cbbd8342d 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.h +++ b/Source/Engine/Platform/Windows/WindowsWindow.h @@ -100,6 +100,7 @@ public: void Hide() override; void Minimize() override; void Maximize() override; + void SetBorderless(bool isBorderless, bool fullscreen) override; void Restore() override; bool IsClosed() const override; bool IsForegroundWindow() const override; From 6e5f6934592bcb50f030cda2e3b1628f33693054 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 14 Aug 2023 18:25:19 -0500 Subject: [PATCH 38/76] Small change to allow other platforms to use fullscreen and windowed in WindowMode function --- Source/Engine/Engine/Screen.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index b4e35ca7a..cb2994129 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -137,20 +137,28 @@ void Screen::SetCursorLock(CursorLockMode mode) void Screen::SetGameWindowMode(GameWindowMode windowMode) { -#if (PLATFORM_WINDOWS) && !USE_EDITOR +#if !USE_EDITOR switch (windowMode) { case GameWindowMode::Windowed: + if (GetIsFullscreen()) + SetIsFullscreen(false); +#if (PLATFORM_WINDOWS) Engine::MainWindow->SetBorderless(false, false); +#endif break; case GameWindowMode::Fullscreen: SetIsFullscreen(true); break; case GameWindowMode::Borderless: +#if (PLATFORM_WINDOWS) Engine::MainWindow->SetBorderless(true, false); +#endif break; case GameWindowMode::FullscreenBorderless: +#if (PLATFORM_WINDOWS) Engine::MainWindow->SetBorderless(true, true); +#endif break; default: ; } From a3063258e5349eafdaa3f9ffe5aff3d7c10da9c3 Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Tue, 15 Aug 2023 12:35:32 +0300 Subject: [PATCH 39/76] Add ability to clean patch caches --- Source/Engine/Terrain/TerrainPatch.cpp | 37 +++++++++++++++++++++----- Source/Engine/Terrain/TerrainPatch.h | 20 ++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index d98827ce1..92a8c09d3 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -1125,42 +1125,67 @@ bool TerrainPatch::InitializeHeightMap() float* TerrainPatch::GetHeightmapData() { + PROFILE_CPU_NAMED("Terrain.GetHeightmapData"); + if (_cachedHeightMap.HasItems()) return _cachedHeightMap.Get(); - PROFILE_CPU_NAMED("Terrain.GetHeightmapData"); - CacheHeightData(); return _cachedHeightMap.Get(); } +void TerrainPatch::ClearHeightmapCache() +{ + PROFILE_CPU_NAMED("Terrain.ClearHeightmapCache"); + _cachedHeightMap.Clear(); +} + byte* TerrainPatch::GetHolesMaskData() { + PROFILE_CPU_NAMED("Terrain.GetHolesMaskData"); + if (_cachedHolesMask.HasItems()) return _cachedHolesMask.Get(); - PROFILE_CPU_NAMED("Terrain.GetHolesMaskData"); - CacheHeightData(); return _cachedHolesMask.Get(); } +void TerrainPatch::ClearHolesMaskCache() +{ + PROFILE_CPU_NAMED("Terrain.ClearHolesMaskCache"); + _cachedHolesMask.Clear(); +} + Color32* TerrainPatch::GetSplatMapData(int32 index) { ASSERT(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT); + PROFILE_CPU_NAMED("Terrain.GetSplatMapData"); + if (_cachedSplatMap[index].HasItems()) return _cachedSplatMap[index].Get(); - PROFILE_CPU_NAMED("Terrain.GetSplatMapData"); - CacheSplatData(); return _cachedSplatMap[index].Get(); } +void TerrainPatch::ClearSplatMapCache() +{ + PROFILE_CPU_NAMED("Terrain.ClearSplatMapCache"); + _cachedSplatMap->Clear(); +} + +void TerrainPatch::ClearCache() +{ + ClearHeightmapCache(); + ClearHolesMaskCache(); + ClearSplatMapCache(); +} + void TerrainPatch::CacheHeightData() { PROFILE_CPU_NAMED("Terrain.CacheHeightData"); diff --git a/Source/Engine/Terrain/TerrainPatch.h b/Source/Engine/Terrain/TerrainPatch.h index 44a768f90..689c629c5 100644 --- a/Source/Engine/Terrain/TerrainPatch.h +++ b/Source/Engine/Terrain/TerrainPatch.h @@ -229,12 +229,22 @@ public: /// The heightmap data. float* GetHeightmapData(); + /// + /// Clears cache of the heightmap data. + /// + void ClearHeightmapCache(); + /// /// Gets the raw pointer to the holes mask data. /// /// The holes mask data. byte* GetHolesMaskData(); + /// + /// Clears cache of the holes mask data. + /// + void ClearHolesMaskCache(); + /// /// Gets the raw pointer to the splat map data. /// @@ -242,6 +252,16 @@ public: /// The splat map data. Color32* GetSplatMapData(int32 index); + /// + /// Clears cache of the splat map data. + /// + void ClearSplatMapCache(); + + /// + /// Clears all caches. + /// + void ClearCache(); + /// /// Modifies the terrain patch heightmap with the given samples. /// From 3e33c63957dfadf614d8f0e9e2a709e474cf3800 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 16 Aug 2023 20:23:08 +0300 Subject: [PATCH 40/76] Optimize actor pasting performance with huge amount of actors - Name lookups are cached to avoid checking the name from same Actor multiple times - Avoid getting actor children multiple times for same parent --- .../Editor/Undo/Actions/PasteActorsAction.cs | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs index c90e94189..8b0d040d8 100644 --- a/Source/Editor/Undo/Actions/PasteActorsAction.cs +++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs @@ -138,6 +138,8 @@ namespace FlaxEditor.Actions } } + // Store previously looked up names and the results + Dictionary foundNamesResults = new(); for (int i = 0; i < nodeParents.Count; i++) { var node = nodeParents[i]; @@ -145,15 +147,28 @@ namespace FlaxEditor.Actions var parent = actor?.Parent; if (parent != null) { + bool IsNameValid(string name) + { + if (!foundNamesResults.TryGetValue(name, out bool found)) + { + found = parent.GetChild(name) != null; + foundNamesResults.Add(name, found); + } + return !found; + } + // Fix name collisions var name = actor.Name; - for (int j = 0; j < parent.ChildrenCount; j++) + var children = parent.Children; + for (int j = 0; j < children.Length; j++) { - var child = parent.Children[j]; - if (child != actor && child.Name == actor.Name) + var child = children[j]; + if (child != actor && child.Name == name) { - var children = parent.Children; - actor.Name = Utilities.Utils.IncrementNameNumber(name, x => children.All(y => y.Name != x)); + string newName = Utilities.Utils.IncrementNameNumber(name, x => IsNameValid(x)); + foundNamesResults[newName] = true; + actor.Name = newName; + // Multiple actors may have the same name, continue } } } @@ -162,10 +177,7 @@ namespace FlaxEditor.Actions } for (int i = 0; i < nodeParents.Count; i++) - { - var node = nodeParents[i]; - node.PostPaste(); - } + nodeParents[i].PostPaste(); } /// From b78db755ec1b807abaeafad1a5ca394dc722e040 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Aug 2023 21:40:36 +0200 Subject: [PATCH 41/76] Fix crash regression d9ee8f46654767809e03c332e1fd4c300e538c73 #1318 --- Source/Engine/Engine/NativeInterop.Marshallers.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs index 0f7d238bc..3ea8a5faa 100644 --- a/Source/Engine/Engine/NativeInterop.Marshallers.cs +++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs @@ -395,18 +395,17 @@ namespace FlaxEngine.Interop { if (managed is null) return; - sourceArray = managed; (managedHandle, managedArray) = ManagedArray.AllocatePooledArray(managed.Length); } public ReadOnlySpan GetManagedValuesSource() => sourceArray; - public Span GetUnmanagedValuesDestination() => managedArray.ToSpan(); + public Span GetUnmanagedValuesDestination() => managedArray != null ? managedArray.ToSpan() : Span.Empty; public TUnmanagedElement* ToUnmanaged() => (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedHandle); - public void Free() => managedArray.FreePooled(); + public void Free() => managedArray?.FreePooled(); } #if FLAX_EDITOR From 925b5abb78695b4587f8b1a030a23ff301809152 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 17 Aug 2023 21:47:02 +0200 Subject: [PATCH 42/76] Fix crash on Linux when using unmapped keyboard Keyboard #1321 --- Source/Engine/Input/Input.cpp | 4 ++++ Source/Engine/Platform/Linux/LinuxPlatform.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index 22969c752..81411ed76 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -333,6 +333,8 @@ void Keyboard::OnCharInput(Char c, Window* target) void Keyboard::OnKeyUp(KeyboardKeys key, Window* target) { + if (key >= KeyboardKeys::MAX) + return; Event& e = _queue.AddOne(); e.Type = EventType::KeyUp; e.Target = target; @@ -341,6 +343,8 @@ void Keyboard::OnKeyUp(KeyboardKeys key, Window* target) void Keyboard::OnKeyDown(KeyboardKeys key, Window* target) { + if (key >= KeyboardKeys::MAX) + return; Event& e = _queue.AddOne(); e.Type = EventType::KeyDown; e.Target = target; diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 0434d8414..5a74d8f68 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -2206,6 +2206,10 @@ bool LinuxPlatform::Init() KeyCodeMap[keyCode] = key; } } + else + { + KeyCodeMap[keyCode] = KeyboardKeys::None; + } } Input::Mouse = Impl::Mouse = New(); From 595bb2b7fa55525658b8b5251c88baf061c2d34a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 18 Aug 2023 00:17:27 +0200 Subject: [PATCH 43/76] Fix nested animation playrate when the framerate is different #1258 --- Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 82842376e..73ddbc41c 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -233,11 +233,10 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* // Get nested animation time position float nestedAnimPrevPos = animPrevPos - nestedAnim.Time; const float nestedAnimLength = nestedAnim.Anim->GetLength(); - const float nestedAnimDuration = nestedAnim.Anim->GetDuration(); const float nestedAnimSpeed = nestedAnim.Speed * speed; - const float frameRateMatchScale = (float)nestedAnim.Anim->Data.FramesPerSecond / (float)anim->Data.FramesPerSecond; - nestedAnimPos = nestedAnimPos / nestedAnimDuration * nestedAnimSpeed * frameRateMatchScale; - nestedAnimPrevPos = nestedAnimPrevPos / nestedAnimDuration * nestedAnimSpeed * frameRateMatchScale; + const float frameRateMatchScale = nestedAnimSpeed / (float)anim->Data.FramesPerSecond; + nestedAnimPos = nestedAnimPos * frameRateMatchScale; + nestedAnimPrevPos = nestedAnimPrevPos * frameRateMatchScale; GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos); ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode); From 122524bd1965345c3421aea42c3c2131dc790256 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 18 Aug 2023 09:12:10 +0200 Subject: [PATCH 44/76] Fix linux build --- Source/Engine/Platform/Linux/LinuxPlatform.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 5a74d8f68..ab314ab72 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -2191,6 +2191,7 @@ bool LinuxPlatform::Init() // Initialize "X11 keycode" -> "Flax KeyboardKeys" map KeyCodeMap.Resize(desc->max_key_code + 1); + Platform::MemoryClear(KeyCodeMap.Get(), KeyCodeMap.Count() * sizeof(KeyboardKeys)); XkbFreeNames(desc, XkbKeyNamesMask, 1); X11::XkbFreeKeyboard(desc, 0, 1); for (int32 keyIdx = (int32)KeyboardKeys::None; keyIdx < MAX_uint8; keyIdx++) @@ -2206,10 +2207,6 @@ bool LinuxPlatform::Init() KeyCodeMap[keyCode] = key; } } - else - { - KeyCodeMap[keyCode] = KeyboardKeys::None; - } } Input::Mouse = Impl::Mouse = New(); From 769a20002d832a478866367a087e91e367d63877 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 19 Aug 2023 13:12:18 +0300 Subject: [PATCH 45/76] Fix Variant move assignment operator with managed objects --- Source/Engine/Core/Types/Variant.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 973e494f1..ca89b3966 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -495,8 +495,13 @@ Variant::Variant(Variant&& other) noexcept other.AsDictionary = nullptr; break; case VariantType::ManagedObject: +#if USE_NETCORE + AsUint64 = other.AsUint64; + other.AsUint64 = 0; +#elif USE_MONO AsUint = other.AsUint; other.AsUint = 0; +#endif break; case VariantType::Null: case VariantType::Void: @@ -1019,8 +1024,13 @@ Variant& Variant::operator=(Variant&& other) other.AsDictionary = nullptr; break; case VariantType::ManagedObject: +#if USE_NETCORE + AsUint64 = other.AsUint64; + other.AsUint64 = 0; +#elif USE_MONO AsUint = other.AsUint; other.AsUint = 0; +#endif break; case VariantType::Null: case VariantType::Void: @@ -2419,7 +2429,11 @@ void Variant::SetType(const VariantType& type) AsDictionary = New>(); break; case VariantType::ManagedObject: +#if USE_NETCORE + AsUint64 = 0; +#elif USE_MONO AsUint = 0; +#endif break; case VariantType::Structure: AllocStructure(); @@ -2532,7 +2546,11 @@ void Variant::SetType(VariantType&& type) AsDictionary = New>(); break; case VariantType::ManagedObject: +#if USE_NETCORE + AsUint64 = 0; +#elif USE_MONO AsUint = 0; +#endif break; case VariantType::Structure: AllocStructure(); @@ -2671,7 +2689,11 @@ void Variant::SetManagedObject(MObject* object) { if (Type.Type != VariantType::ManagedObject || Type.TypeName) SetType(VariantType(VariantType::ManagedObject)); +#if USE_NETCORE + AsUint64 = 0; +#elif USE_MONO AsUint = 0; +#endif } #endif } From 5354063129471250be58840adfeb65e279711693 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 19 Aug 2023 10:43:04 -0500 Subject: [PATCH 46/76] Add simple way to remove button border. --- Source/Engine/UI/GUI/Common/Button.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index 8ab6366eb..88ee0437e 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -80,21 +80,27 @@ namespace FlaxEngine.GUI public Color BackgroundColorSelected { get; set; } /// - /// Gets or sets the color of the border. + /// Gets or sets whether the button has a border. /// [EditorDisplay("Border Style"), EditorOrder(2010), ExpandGroups] + public bool HasBorder { get; set; } = true; + + /// + /// Gets or sets the color of the border. + /// + [EditorDisplay("Border Style"), EditorOrder(2011), ExpandGroups] public Color BorderColor { get; set; } /// /// Gets or sets the border color when button is highlighted. /// - [EditorDisplay("Border Style"), EditorOrder(2011)] + [EditorDisplay("Border Style"), EditorOrder(2012)] public Color BorderColorHighlighted { get; set; } /// /// Gets or sets the border color when button is selected. /// - [EditorDisplay("Border Style"), EditorOrder(2012)] + [EditorDisplay("Border Style"), EditorOrder(2013)] public Color BorderColorSelected { get; set; } /// @@ -245,7 +251,8 @@ namespace FlaxEngine.GUI BackgroundBrush.Draw(clientRect, backgroundColor); else Render2D.FillRectangle(clientRect, backgroundColor); - Render2D.DrawRectangle(clientRect, borderColor); + if (HasBorder) + Render2D.DrawRectangle(clientRect, borderColor); // Draw text Render2D.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); From 134273abd96f149006cd681e2f5cde7445f3d3f2 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 19 Aug 2023 12:47:29 -0500 Subject: [PATCH 47/76] Remove popup from appearing if the content item can not re renamed. --- Source/Editor/Windows/ContentWindow.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index f993b79f9..f439c8229 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -340,6 +340,9 @@ namespace FlaxEditor.Windows /// The created renaming popup. public void Rename(ContentItem item) { + if (!item.CanRename) + return; + // Show element in the view Select(item, true); From 1286a29462e7afef4b39c6b0d52287888ff28753 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 19 Aug 2023 14:30:08 -0500 Subject: [PATCH 48/76] Add not closing some cm buttons --- Source/Editor/Viewport/EditorViewport.cs | 1 + Source/Editor/Viewport/MainEditorGizmoViewport.cs | 2 ++ Source/Editor/Viewport/Previews/AnimatedModelPreview.cs | 4 ++++ Source/Editor/Viewport/Previews/AssetPreview.cs | 1 + Source/Editor/Viewport/Previews/ModelPreview.cs | 6 ++++++ Source/Editor/Viewport/Previews/ParticleSystemPreview.cs | 3 +++ Source/Editor/Viewport/Previews/SkinnedModelPreview.cs | 1 + 7 files changed, 18 insertions(+) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 3281d130a..ad7e06ba5 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -479,6 +479,7 @@ namespace FlaxEditor.Viewport { InitFpsCounter(); _showFpsButon = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter); + _showFpsButon.CloseMenuOnClick = false; } } diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 8d7039dc6..9cbfce562 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -371,9 +371,11 @@ namespace FlaxEditor.Viewport // Show grid widget _showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled); _showGridButton.Icon = Style.Current.CheckBoxTick; + _showGridButton.CloseMenuOnClick = false; // Show navigation widget _showNavigationButton = ViewWidgetShowMenu.AddButton("Navigation", () => ShowNavigation = !ShowNavigation); + _showNavigationButton.CloseMenuOnClick = false; // Create camera widget ViewWidgetButtonMenu.AddSeparator(); diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs index 12371f947..d96d7a8f9 100644 --- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs @@ -194,16 +194,20 @@ namespace FlaxEditor.Viewport.Previews { // Show Bounds _showBoundsButton = ViewWidgetShowMenu.AddButton("Bounds", () => ShowBounds = !ShowBounds); + _showBoundsButton.CloseMenuOnClick = false; // Show Skeleton _showNodesButton = ViewWidgetShowMenu.AddButton("Skeleton", () => ShowNodes = !ShowNodes); + _showNodesButton.CloseMenuOnClick = false; // Show Skeleton Names _showNodesNamesButton = ViewWidgetShowMenu.AddButton("Skeleton Names", () => ShowNodesNames = !ShowNodesNames); + _showNodesNamesButton.CloseMenuOnClick = false; // Show Floor _showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor); _showFloorButton.IndexInParent = 1; + _showFloorButton.CloseMenuOnClick = false; } // Enable shadows diff --git a/Source/Editor/Viewport/Previews/AssetPreview.cs b/Source/Editor/Viewport/Previews/AssetPreview.cs index 0bcc4e115..0fb274b5d 100644 --- a/Source/Editor/Viewport/Previews/AssetPreview.cs +++ b/Source/Editor/Viewport/Previews/AssetPreview.cs @@ -171,6 +171,7 @@ namespace FlaxEditor.Viewport.Previews // Show Default Scene _showDefaultSceneButton = ViewWidgetShowMenu.AddButton("Default Scene", () => ShowDefaultSceneActors = !ShowDefaultSceneActors); _showDefaultSceneButton.Checked = true; + _showDefaultSceneButton.CloseMenuOnClick = false; } // Setup preview scene diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs index a0b6d0ca0..a6496aafe 100644 --- a/Source/Editor/Viewport/Previews/ModelPreview.cs +++ b/Source/Editor/Viewport/Previews/ModelPreview.cs @@ -199,13 +199,18 @@ namespace FlaxEditor.Viewport.Previews if (useWidgets) { _showBoundsButton = ViewWidgetShowMenu.AddButton("Bounds", () => ShowBounds = !ShowBounds); + _showBoundsButton.CloseMenuOnClick = false; _showNormalsButton = ViewWidgetShowMenu.AddButton("Normals", () => ShowNormals = !ShowNormals); + _showNormalsButton.CloseMenuOnClick = false; _showTangentsButton = ViewWidgetShowMenu.AddButton("Tangents", () => ShowTangents = !ShowTangents); + _showTangentsButton.CloseMenuOnClick = false; _showBitangentsButton = ViewWidgetShowMenu.AddButton("Bitangents", () => ShowBitangents = !ShowBitangents); + _showBitangentsButton.CloseMenuOnClick = false; // Show Floor _showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor); _showFloorButton.IndexInParent = 1; + _showFloorButton.CloseMenuOnClick = false; // Show current LOD widget _showCurrentLODButton = ViewWidgetShowMenu.AddButton("Current LOD", button => @@ -214,6 +219,7 @@ namespace FlaxEditor.Viewport.Previews _showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; }); _showCurrentLODButton.IndexInParent = 2; + _showCurrentLODButton.CloseMenuOnClick = false; // Preview LODs mode widget var PreviewLODsMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); diff --git a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs index 1e0c89528..fc7fcdbe1 100644 --- a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs @@ -186,8 +186,11 @@ namespace FlaxEditor.Viewport.Previews if (!useWidgets) return; _showBoundsButton = ViewWidgetShowMenu.AddButton("Bounds", () => ShowBounds = !ShowBounds); + _showBoundsButton.CloseMenuOnClick = false; _showOriginButton = ViewWidgetShowMenu.AddButton("Origin", () => ShowOrigin = !ShowOrigin); + _showOriginButton.CloseMenuOnClick = false; _showParticleCounterButton = ViewWidgetShowMenu.AddButton("Particles Counter", () => ShowParticlesCounter = !ShowParticlesCounter); + _showParticleCounterButton.CloseMenuOnClick = false; // Play/Pause widget { diff --git a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs index 08f8d8509..8bcc506d9 100644 --- a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs @@ -49,6 +49,7 @@ namespace FlaxEditor.Viewport.Previews _showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; }); _showCurrentLODButton.IndexInParent = 2; + _showCurrentLODButton.CloseMenuOnClick = false; // PreviewLODS mode widget var PreviewLODSMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); From 50c85aec6d6929b119f92d7dd0672587c62ae826 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 21 Aug 2023 23:33:33 +0200 Subject: [PATCH 49/76] Minor improvements to #1315 and add getter of current window style --- Flax.sln.DotSettings | 1 + Source/Engine/Engine/Screen.cpp | 36 +++++++++++++------ Source/Engine/Engine/Screen.h | 6 ++++ Source/Engine/Platform/Base/WindowBase.h | 4 +-- .../Engine/Platform/Windows/WindowsWindow.cpp | 9 +++-- .../Engine/Platform/Windows/WindowsWindow.h | 2 +- 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index fe1d1463e..e8af1461c 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -248,6 +248,7 @@ True True True + True True True True diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index cb2994129..2bc6e1f58 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -135,32 +135,48 @@ void Screen::SetCursorLock(CursorLockMode mode) CursorLock = mode; } +GameWindowMode Screen::GetGameWindowMode() +{ + GameWindowMode result = GameWindowMode::Windowed; +#if !USE_EDITOR + auto win = Engine::MainWindow; + if (win) + { + if (GetIsFullscreen() || win->IsFullscreen()) + result = GameWindowMode::Fullscreen; + else if (win->GetSettings().HasBorder) + result = GameWindowMode::Windowed; + else if (win->GetClientPosition().IsZero() && win->GetSize() == Platform::GetDesktopSize()) + result = GameWindowMode::FullscreenBorderless; + else + result = GameWindowMode::Borderless; + } +#endif + return result; +} + void Screen::SetGameWindowMode(GameWindowMode windowMode) { #if !USE_EDITOR + auto win = Engine::MainWindow; + if (!win) + return; switch (windowMode) { case GameWindowMode::Windowed: if (GetIsFullscreen()) SetIsFullscreen(false); -#if (PLATFORM_WINDOWS) - Engine::MainWindow->SetBorderless(false, false); -#endif + win->SetBorderless(false, false); break; case GameWindowMode::Fullscreen: SetIsFullscreen(true); break; case GameWindowMode::Borderless: -#if (PLATFORM_WINDOWS) - Engine::MainWindow->SetBorderless(true, false); -#endif + win->SetBorderless(true, false); break; case GameWindowMode::FullscreenBorderless: -#if (PLATFORM_WINDOWS) - Engine::MainWindow->SetBorderless(true, true); -#endif + win->SetBorderless(true, true); break; - default: ; } #endif } diff --git a/Source/Engine/Engine/Screen.h b/Source/Engine/Engine/Screen.h index 8f7d21766..42be20a38 100644 --- a/Source/Engine/Engine/Screen.h +++ b/Source/Engine/Engine/Screen.h @@ -82,6 +82,12 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen); /// The mode. API_PROPERTY() static void SetCursorLock(CursorLockMode mode); + /// + /// Gets the game window mode. + /// + /// The current window mode. + API_PROPERTY() static GameWindowMode GetGameWindowMode(); + /// /// Sets the game window mode. /// diff --git a/Source/Engine/Platform/Base/WindowBase.h b/Source/Engine/Platform/Base/WindowBase.h index 10b99d1cf..e4e7d2080 100644 --- a/Source/Engine/Platform/Base/WindowBase.h +++ b/Source/Engine/Platform/Base/WindowBase.h @@ -449,8 +449,8 @@ public: /// Sets the window to be borderless or not and to be fullscreen. /// /// Whether or not to have borders on window. - /// Whether or not to make the borderless window fullscreen. - API_FUNCTION() virtual void SetBorderless(bool isBorderless, bool fullscreen) + /// Whether or not to make the borderless window fullscreen (maximize to cover whole screen). + API_FUNCTION() virtual void SetBorderless(bool isBorderless, bool maximized = false) { } diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index ec5850b13..d712102fa 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -260,7 +260,7 @@ void WindowsWindow::Maximize() _isDuringMaximize = false; } -void WindowsWindow::SetBorderless(bool isBorderless, bool fullscreen) +void WindowsWindow::SetBorderless(bool isBorderless, bool maximized) { ASSERT(HasHWND()); @@ -292,8 +292,7 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool fullscreen) SetWindowLong(_handle, GWL_STYLE, lStyle); SetWindowPos(_handle, HWND_TOP, 0, 0,0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); - - if (fullscreen) + if (maximized) { ShowWindow(_handle, SW_SHOWMAXIMIZED); } @@ -312,13 +311,12 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool fullscreen) lStyle |= WS_MINIMIZEBOX; if (_settings.HasSizingFrame) lStyle |= WS_THICKFRAME; - // Create window style flags lStyle |= WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION; SetWindowLong(_handle, GWL_STYLE, lStyle); SetWindowPos(_handle, nullptr, 0, 0, (int)_settings.Size.X, (int)_settings.Size.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); - if (fullscreen) + if (maximized) { Maximize(); } @@ -327,6 +325,7 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool fullscreen) ShowWindow(_handle, SW_SHOW); } } + CheckForWindowResize(); } diff --git a/Source/Engine/Platform/Windows/WindowsWindow.h b/Source/Engine/Platform/Windows/WindowsWindow.h index cbbd8342d..e15f86a45 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.h +++ b/Source/Engine/Platform/Windows/WindowsWindow.h @@ -100,7 +100,7 @@ public: void Hide() override; void Minimize() override; void Maximize() override; - void SetBorderless(bool isBorderless, bool fullscreen) override; + void SetBorderless(bool isBorderless, bool maximized = false) override; void Restore() override; bool IsClosed() const override; bool IsForegroundWindow() const override; From ce4cf9b34bfd1ea1091ea1e14aa17f4f3a5ca6a5 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 22 Aug 2023 15:42:16 -0500 Subject: [PATCH 50/76] Add timeline position numbers and add to GUI. --- Source/Editor/GUI/Timeline/GUI/Background.cs | 4 +-- .../Editor/GUI/Timeline/GUI/PositionHandle.cs | 30 +++++++++++++++++-- Source/Editor/GUI/Timeline/Timeline.UI.cs | 21 +++++++++++++ Source/Editor/GUI/Timeline/Timeline.cs | 2 +- 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs index dc4909abb..b9eff562a 100644 --- a/Source/Editor/GUI/Timeline/GUI/Background.cs +++ b/Source/Editor/GUI/Timeline/GUI/Background.cs @@ -282,7 +282,7 @@ namespace FlaxEditor.GUI.Timeline.GUI var x = time * zoom + Timeline.StartOffset; // Header line - var lineRect = new Rectangle(x - 0.5f, -verticalLinesHeaderExtend + timeAxisHeaderOffset, 1.0f, verticalLinesHeaderExtend); + var lineRect = new Rectangle(x - 0.5f, -verticalLinesHeaderExtend * 0.6f + timeAxisHeaderOffset, 1.0f, verticalLinesHeaderExtend * 0.6f); Render2D.FillRectangle(lineRect, lineColor); // Time label @@ -300,7 +300,7 @@ namespace FlaxEditor.GUI.Timeline.GUI break; default: throw new ArgumentOutOfRangeException(); } - var labelRect = new Rectangle(x + 2, -verticalLinesHeaderExtend + timeAxisHeaderOffset, 50, verticalLinesHeaderExtend); + var labelRect = new Rectangle(x + 2, -verticalLinesHeaderExtend * 0.8f + timeAxisHeaderOffset, 50, verticalLinesHeaderExtend); Render2D.DrawText(style.FontSmall, labelText, labelRect, labelColor, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.8f); } } diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs index 7b42e86ab..046078d54 100644 --- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs +++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System; +using System.Globalization; using FlaxEngine; using FlaxEngine.GUI; @@ -29,15 +31,39 @@ namespace FlaxEditor.GUI.Timeline.GUI var icon = Editor.Instance.Icons.VisjectArrowClosed32; var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f; var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap; + + var timeShowMode = _timeline.TimeShowMode; + // Time label + string labelText; + switch (timeShowMode) + { + case Timeline.TimeShowModes.Frames: + labelText = _timeline.CurrentFrame.ToString("###0", CultureInfo.InvariantCulture); + break; + case Timeline.TimeShowModes.Seconds: + labelText = _timeline.CurrentTime.ToString("###0.##'s'", CultureInfo.InvariantCulture); + break; + case Timeline.TimeShowModes.Time: + labelText = TimeSpan.FromSeconds(_timeline.CurrentTime).ToString("g"); + break; + default: throw new ArgumentOutOfRangeException(); + } + var color = (_timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground).AlphaMultiplied(0.6f); Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1); var m2 = Matrix3x3.Translation2D(0, timeAxisHeaderOffset); Matrix3x3.Multiply(ref m1, ref m2, out var m3); Render2D.PushTransform(ref m3); - Render2D.DrawSprite(icon, new Rectangle(new Float2(4, -Width), Size), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground); + // TODO: Convert to its own sprite or 9 slice + Render2D.DrawSprite(icon, new Rectangle(new Float2(10, -icon.Size.X * 0.5f), Size), color); + Render2D.FillRectangle(new Rectangle(new Float2(-6, -icon.Size.Y * 0.5f + 8), new Float2(timeAxisOverlap, 4)), color); + Render2D.PopTransform(); + var textMatrix = Matrix3x3.Translation2D(12, timeAxisHeaderOffset); + Render2D.PushTransform(ref textMatrix); + Render2D.DrawText(style.FontSmall, labelText, style.Foreground, new Float2(2, -6)); Render2D.PopTransform(); - Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f)); + Render2D.FillRectangle(new Rectangle(Width * 0.5f - 1, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f)); base.Draw(); } diff --git a/Source/Editor/GUI/Timeline/Timeline.UI.cs b/Source/Editor/GUI/Timeline/Timeline.UI.cs index a977d487e..604db541c 100644 --- a/Source/Editor/GUI/Timeline/Timeline.UI.cs +++ b/Source/Editor/GUI/Timeline/Timeline.UI.cs @@ -50,6 +50,27 @@ namespace FlaxEditor.GUI.Timeline } } + /// + public override void OnMouseEnter(Float2 location) + { + base.OnMouseEnter(location); + Cursor = CursorType.Hand; + } + + /// + public override void OnMouseLeave() + { + Cursor = CursorType.Default; + base.OnMouseLeave(); + } + + /// + public override void Defocus() + { + Cursor = CursorType.Default; + base.Defocus(); + } + private void Seek(ref Float2 location) { if (_timeline.PlaybackState == PlaybackStates.Disabled) diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 9800b2105..a32b35692 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -167,7 +167,7 @@ namespace FlaxEditor.GUI.Timeline /// /// The header top area height (in pixels). /// - public static readonly float HeaderTopAreaHeight = 22.0f; + public static readonly float HeaderTopAreaHeight = 40.0f; /// /// The timeline units per second (on time axis). From a737e7bb05ef09f7923b771f270e994d964fe424 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 22 Aug 2023 17:36:09 -0500 Subject: [PATCH 51/76] Small fix to UI --- Source/Editor/GUI/Timeline/GUI/PositionHandle.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs index 046078d54..316ade9b7 100644 --- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs +++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs @@ -55,15 +55,15 @@ namespace FlaxEditor.GUI.Timeline.GUI Matrix3x3.Multiply(ref m1, ref m2, out var m3); Render2D.PushTransform(ref m3); // TODO: Convert to its own sprite or 9 slice - Render2D.DrawSprite(icon, new Rectangle(new Float2(10, -icon.Size.X * 0.5f), Size), color); - Render2D.FillRectangle(new Rectangle(new Float2(-6, -icon.Size.Y * 0.5f + 8), new Float2(timeAxisOverlap, 4)), color); + Render2D.DrawSprite(icon, new Rectangle(new Float2(10, -icon.Size.X * 0.5f - 1), Size + new Float2(0, 1)), color); + Render2D.FillRectangle(new Rectangle(new Float2(-6, -icon.Size.Y * 0.5f + 7), new Float2(timeAxisOverlap, 5)), color); Render2D.PopTransform(); var textMatrix = Matrix3x3.Translation2D(12, timeAxisHeaderOffset); Render2D.PushTransform(ref textMatrix); Render2D.DrawText(style.FontSmall, labelText, style.Foreground, new Float2(2, -6)); Render2D.PopTransform(); - Render2D.FillRectangle(new Rectangle(Width * 0.5f - 1, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f)); + Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f)); base.Draw(); } From e16e2e2877d2271c8a68e44ed96d9a7a81919d17 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 23 Aug 2023 08:35:35 -0500 Subject: [PATCH 52/76] Prevent AbstractWrapper from showing in Add AnimEvent CM. --- Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs index 4e78ca8ae..8f2909d2f 100644 --- a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs @@ -298,7 +298,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks var animEventTypes = Editor.Instance.CodeEditing.All.Get().Where(x => new ScriptType(typeof(AnimEvent)).IsAssignableFrom(x)); foreach (var type in animEventTypes) { - if (type.IsAbstract || !type.CanCreateInstance) + if (type.IsAbstract || !type.CanCreateInstance || string.Equals(type.TypeName, "FlaxEngine.AnimEvent+AbstractWrapper", StringComparison.Ordinal) || string.Equals(type.TypeName, "FlaxEngine.AnimContinuousEvent+AbstractWrapper", StringComparison.Ordinal)) continue; var add = new ScriptType(typeof(AnimContinuousEvent)).IsAssignableFrom(type) ? addContinuousEvent : addEvent; var b = add.ContextMenu.AddButton(type.Name); From e1645210b3de957ba30d492e4a499a0e19cd2a0e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 23 Aug 2023 08:46:21 -0500 Subject: [PATCH 53/76] Add check and indication if no anim events are found. --- Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs index 8f2909d2f..359f589ec 100644 --- a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs @@ -307,6 +307,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks b.Parent.Tag = time; b.ButtonClicked += OnAddAnimEvent; } + if (!addEvent.ContextMenu.Items.Any()) + addEvent.ContextMenu.AddButton("No Anim Events Found.").CloseMenuOnClick = false; + if (!addContinuousEvent.ContextMenu.Items.Any()) + addContinuousEvent.ContextMenu.AddButton("No Continuous Anim Events Found.").CloseMenuOnClick = false; } From 7e429854d6f0495c47333ca7c919f1ed91ee41e5 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 23 Aug 2023 09:03:56 -0500 Subject: [PATCH 54/76] Simplify check to use attribute. --- Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs index 359f589ec..6ebddb1d2 100644 --- a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs @@ -298,7 +298,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks var animEventTypes = Editor.Instance.CodeEditing.All.Get().Where(x => new ScriptType(typeof(AnimEvent)).IsAssignableFrom(x)); foreach (var type in animEventTypes) { - if (type.IsAbstract || !type.CanCreateInstance || string.Equals(type.TypeName, "FlaxEngine.AnimEvent+AbstractWrapper", StringComparison.Ordinal) || string.Equals(type.TypeName, "FlaxEngine.AnimContinuousEvent+AbstractWrapper", StringComparison.Ordinal)) + if (type.IsAbstract || !type.CanCreateInstance || type.HasAttribute(typeof(HideInEditorAttribute), true)) continue; var add = new ScriptType(typeof(AnimContinuousEvent)).IsAssignableFrom(type) ? addContinuousEvent : addEvent; var b = add.ContextMenu.AddButton(type.Name); From 87e36bcfd51b6a9b22e27b7d543af314de1c391f Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Fri, 25 Aug 2023 01:41:52 +0300 Subject: [PATCH 55/76] Stop replication if NetworkManager::NetworkFPS < 0 --- Source/Engine/Networking/NetworkReplicator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 529e6460b..a63e27471 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -1412,9 +1412,9 @@ void NetworkInternal::NetworkReplicatorUpdate() if (!CachedReplicationResult) CachedReplicationResult = New(); CachedReplicationResult->Init(); - if (!isClient && NetworkManager::Clients.IsEmpty()) + if ((!isClient && NetworkManager::Clients.IsEmpty()) || NetworkManager::NetworkFPS < -ZeroTolerance) { - // No need to update replication when nobody's around + // No need to update replication when nobody's around or when replication is disabled } else if (Hierarchy) { From 3939106e78214921b1c029754129414acb75ca9a Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 25 Aug 2023 22:08:20 +0300 Subject: [PATCH 56/76] Fix crash with XAudio2 backend when source is playing The array containing the sources might grow and invalidate existing pointers to sources while some of the previous sources are still playing. --- Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp index 698a6025a..f90c58875 100644 --- a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp +++ b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp @@ -5,6 +5,7 @@ #include "AudioBackendXAudio2.h" #include "Engine/Audio/AudioSettings.h" #include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/ChunkedArray.h" #include "Engine/Core/Log.h" #include "Engine/Audio/Audio.h" #include "Engine/Audio/AudioSource.h" @@ -208,8 +209,8 @@ namespace XAudio2 bool ForceDirty = true; Listener Listeners[AUDIO_MAX_LISTENERS]; CriticalSection Locker; - Array Sources(32); // TODO: use ChunkedArray for better performance - Array Buffers(64); // TODO: use ChunkedArray for better performance or use buffers pool? + ChunkedArray Sources; + ChunkedArray Buffers; // TODO: use ChunkedArray for better performance or use buffers pool? EngineCallback Callback; Listener* GetListener() From 8ad4c945456577e2ce1a1105e8d4767b3417413f Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 25 Aug 2023 15:25:51 -0500 Subject: [PATCH 57/76] Add not closing contnent filter cm on click --- Source/Editor/Windows/ContentWindow.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index f439c8229..b1f04c8f5 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -247,8 +247,17 @@ namespace FlaxEditor.Windows for (int i = 0; i < _viewDropdown.Items.Count; i++) { var filterButton = filters.ContextMenu.AddButton(_viewDropdown.Items[i], OnFilterClicked); + filterButton.CloseMenuOnClick = false; filterButton.Tag = i; } + filters.ContextMenu.ButtonClicked += button => + { + foreach (var item in (filters.ContextMenu).Items) + { + if (item is ContextMenuButton filterButton) + filterButton.Checked = _viewDropdown.IsSelected(filterButton.Text); + } + }; filters.ContextMenu.VisibleChanged += control => { if (!control.Visible) From 514bc0031079b06a60656bcfc5b5432c34d65615 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 26 Aug 2023 16:10:59 -0500 Subject: [PATCH 58/76] Show Json Asset Type on ToolStrip of Json Asset Window. --- Source/Editor/Windows/Assets/JsonAssetWindow.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs index e6606f35f..8cf0595df 100644 --- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs +++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs @@ -136,6 +136,17 @@ namespace FlaxEditor.Windows.Assets } } _presenter.Select(_object); + + var typeText = new Label + { + Text = $"Type: {Asset.DataTypeName}", + AnchorPreset = AnchorPresets.TopRight, + AutoWidth = true, + Parent = this, + }; + typeText.LocalX += -(typeText.Width + 4); + typeText.LocalY += (_toolstrip.Height - typeText.Height) * 0.5f; + _undo.Clear(); ClearEditedFlag(); From c5ff2c8c2f3d4796058e82a37d4481c5e04b1243 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 26 Aug 2023 16:17:21 -0500 Subject: [PATCH 59/76] Add tooltip text. --- Source/Editor/Windows/Assets/JsonAssetWindow.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs index 8cf0595df..abaaacc76 100644 --- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs +++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs @@ -139,7 +139,8 @@ namespace FlaxEditor.Windows.Assets var typeText = new Label { - Text = $"Type: {Asset.DataTypeName}", + Text = $"{Asset.DataTypeName}", + TooltipText = "The Asset Type.", AnchorPreset = AnchorPresets.TopRight, AutoWidth = true, Parent = this, From 87aba232995048e524868363be340e41162a8c02 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 26 Aug 2023 16:49:37 -0500 Subject: [PATCH 60/76] Add tab breaks for window tabs. --- Source/Editor/GUI/Docking/DockPanelProxy.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs index ac5e0bc4f..15ff2cad0 100644 --- a/Source/Editor/GUI/Docking/DockPanelProxy.cs +++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs @@ -253,6 +253,12 @@ namespace FlaxEditor.GUI.Docking tabColor = style.BackgroundHighlighted; Render2D.FillRectangle(tabRect, tabColor); } + else + { + tabColor = style.BackgroundHighlighted; + Render2D.DrawLine(tabRect.BottomLeft - new Float2(0 , 1), tabRect.UpperLeft, tabColor); + Render2D.DrawLine(tabRect.BottomRight - new Float2(0 , 1), tabRect.UpperRight, tabColor); + } if (tab.Icon.IsValid) { From 87e19ef4bcf07aeae724af55a4200bd48724dd08 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 27 Aug 2023 16:19:25 +0200 Subject: [PATCH 61/76] Fix crash on terrain export in Editor #1330 --- Source/Engine/Platform/Windows/WindowsFileSystem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp index 83f042730..38384f05f 100644 --- a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp +++ b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp @@ -316,7 +316,8 @@ bool WindowsFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const Strin if (SUCCEEDED(SHCreateItemFromParsingName(initialDirectory.Get(), NULL, IID_PPV_ARGS(&defaultFolder)))) fd->SetFolder(defaultFolder); - if (SUCCEEDED(fd->Show(parentWindow->GetHWND()))) + HWND hwndOwner = parentWindow ? parentWindow->GetHWND() : NULL; + if (SUCCEEDED(fd->Show(hwndOwner))) { ComPtr si; if (SUCCEEDED(fd->GetResult(&si))) From 97b56d1e728790139524fa037c3372073d88451b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 27 Aug 2023 16:28:35 +0200 Subject: [PATCH 62/76] Minor tweak #1332 --- Source/Editor/GUI/Timeline/GUI/PositionHandle.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs index 316ade9b7..bedb61a5e 100644 --- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs +++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs @@ -31,11 +31,10 @@ namespace FlaxEditor.GUI.Timeline.GUI var icon = Editor.Instance.Icons.VisjectArrowClosed32; var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f; var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap; - - var timeShowMode = _timeline.TimeShowMode; + // Time label string labelText; - switch (timeShowMode) + switch (_timeline.TimeShowMode) { case Timeline.TimeShowModes.Frames: labelText = _timeline.CurrentFrame.ToString("###0", CultureInfo.InvariantCulture); @@ -48,7 +47,6 @@ namespace FlaxEditor.GUI.Timeline.GUI break; default: throw new ArgumentOutOfRangeException(); } - var color = (_timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground).AlphaMultiplied(0.6f); Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1); var m2 = Matrix3x3.Translation2D(0, timeAxisHeaderOffset); From 3b163b6c4bd8b6823c4d21ccf25bdb17e96edf4b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 27 Aug 2023 16:36:37 +0200 Subject: [PATCH 63/76] Minor cleanup --- .../Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs | 4 ++-- Source/Editor/Windows/Assets/JsonAssetWindow.cs | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs index 6ebddb1d2..11a1bfe47 100644 --- a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs @@ -308,9 +308,9 @@ namespace FlaxEditor.GUI.Timeline.Tracks b.ButtonClicked += OnAddAnimEvent; } if (!addEvent.ContextMenu.Items.Any()) - addEvent.ContextMenu.AddButton("No Anim Events Found.").CloseMenuOnClick = false; + addEvent.ContextMenu.AddButton("No Anim Events found").CloseMenuOnClick = false; if (!addContinuousEvent.ContextMenu.Items.Any()) - addContinuousEvent.ContextMenu.AddButton("No Continuous Anim Events Found.").CloseMenuOnClick = false; + addContinuousEvent.ContextMenu.AddButton("No Continuous Anim Events found").CloseMenuOnClick = false; } diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs index abaaacc76..47ce09274 100644 --- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs +++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs @@ -23,6 +23,7 @@ namespace FlaxEditor.Windows.Assets private readonly Undo _undo; private object _object; private bool _isRegisteredForScriptsReload; + private Label _typeText; /// /// Gets the instance of the Json asset object that is being edited. @@ -137,16 +138,20 @@ namespace FlaxEditor.Windows.Assets } _presenter.Select(_object); - var typeText = new Label + if (_typeText != null) + _typeText.Dispose(); + var typeText = new ClickableLabel { Text = $"{Asset.DataTypeName}", - TooltipText = "The Asset Type.", + TooltipText = "Asset data type (full name)", AnchorPreset = AnchorPresets.TopRight, AutoWidth = true, Parent = this, }; typeText.LocalX += -(typeText.Width + 4); typeText.LocalY += (_toolstrip.Height - typeText.Height) * 0.5f; + typeText.RightClick = () => Clipboard.Text = Asset.DataTypeName; + _typeText = typeText; _undo.Clear(); ClearEditedFlag(); @@ -187,6 +192,7 @@ namespace FlaxEditor.Windows.Assets _isRegisteredForScriptsReload = false; ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; } + _typeText = null; base.OnDestroy(); } From fa77a52a2cd314537fc997c8218145faa81068c7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 27 Aug 2023 20:08:27 +0200 Subject: [PATCH 64/76] Fix prefab apply bug when one of the prefabs in project is broken --- Source/Engine/Level/Prefabs/Prefab.Apply.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index 53453afe9..5ea8ad0a4 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -675,20 +675,12 @@ bool Prefab::ApplyAll(Actor* targetActor) } } - // Setup default instances - for (int32 i = 0; i < allPrefabs.Count(); i++) + // Setup default instances (skip invalid prefabs) + for (int32 i = allPrefabs.Count() - 1; i >= 0; i--) { Prefab* prefab = allPrefabs[i]; - if (prefab->WaitForLoaded()) - { - LOG(Warning, "Waiting for nesting prefab asset load failed."); - return true; - } - if (prefab->GetDefaultInstance() == nullptr) - { - LOG(Warning, "Failed to create default prefab instance for the nested prefab asset."); - return true; - } + if (prefab->WaitForLoaded() || prefab->GetDefaultInstance() == nullptr) + allPrefabs.RemoveAt(i); } } From bd750d26348728ed2be2df7ea5bb86698ba9e0f8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 21 Jul 2023 13:35:21 +0200 Subject: [PATCH 65/76] Fix incoming drag drop location on macOS --- Source/Engine/Platform/Mac/MacWindow.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index aeee93e37..000accbfa 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -202,8 +202,7 @@ void GetDragDropData(const MacWindow* window, id sender, Float2& { NSRect frame = [(NSWindow*)window->GetNativePtr() frame]; NSPoint point = [sender draggingLocation]; - Float2 titleSize = GetWindowTitleSize(window); - mousePos = Float2(point.x, frame.size.height - point.y) - titleSize; + mousePos = Float2(point.x, frame.size.height - point.y) * MacPlatform::ScreenScale - GetWindowTitleSize(window); NSPasteboard* pasteboard = [sender draggingPasteboard]; if ([[pasteboard types] containsObject:NSPasteboardTypeString]) { From c32a139dbd5281a9fa355f8ec4478289aa0bae85 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 29 Aug 2023 11:42:47 +0200 Subject: [PATCH 66/76] Fix crash when unboxing managed structure with refs into `Variant` --- Source/Engine/Engine/NativeInterop.Unmanaged.cs | 1 - Source/Engine/Engine/NativeInterop.cs | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 8837d460b..879f804d2 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -649,7 +649,6 @@ namespace FlaxEngine.Interop Type type = value.GetType(); if (!type.IsValueType) return ManagedHandle.ToIntPtr(handle); - return ValueTypeUnboxer.GetPointer(value, type); } diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index e75877878..dd6087b98 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -1077,13 +1077,14 @@ namespace FlaxEngine.Interop alloc.ptr = new IntPtr(NativeAlloc(size)); alloc.size = size; } - Unsafe.Write(alloc.ptr.ToPointer(), value); return alloc.ptr; } private static IntPtr UnboxPointer(object value, object converter) where T : struct { + if (RuntimeHelpers.IsReferenceOrContainsReferences()) // Cannot pin structure with references + return IntPtr.Zero; PinValue(value); return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox(value))); } From d7112dc534e270e03afbd030205d963a8f41b069 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 30 Aug 2023 17:59:39 -0500 Subject: [PATCH 67/76] Improve progress bar to add switch between clipping and stretch and add direction. --- Source/Engine/UI/GUI/Common/ProgressBar.cs | 78 ++++++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/ProgressBar.cs b/Source/Engine/UI/GUI/Common/ProgressBar.cs index a742d3b43..ee7d32dad 100644 --- a/Source/Engine/UI/GUI/Common/ProgressBar.cs +++ b/Source/Engine/UI/GUI/Common/ProgressBar.cs @@ -10,6 +10,32 @@ namespace FlaxEngine.GUI /// public class ProgressBar : ContainerControl { + /// + /// The direction to move the progress bar + /// + public enum BarDirection + { + /// + /// Move the bar horizontally to the left. + /// + HorizontalLeft, + + /// + /// Move the bar horizontally to the right. + /// + HorizontalRight, + + /// + /// Move the bar vertically up. + /// + VerticalUp, + + /// + /// Move the bar vertically down. + /// + VerticalDown, + } + /// /// The value. /// @@ -40,6 +66,18 @@ namespace FlaxEngine.GUI /// Gets a value indicating whether use progress value smoothing. /// public bool UseSmoothing => !Mathf.IsZero(SmoothingScale); + + /// + /// If true, the progress bar will clip instead of stretch. + /// + [EditorOrder(42), Tooltip("Whether or not to clip vs stretch the progress bar.")] + public bool ClipBar = false; + + /// + /// The direction to clip or stretch the bar. + /// + [EditorOrder(42), Tooltip("The direction to clip or stretch the bar.")] + public BarDirection Direction = BarDirection.HorizontalLeft; /// /// Gets or sets the minimum value. @@ -168,12 +206,42 @@ namespace FlaxEngine.GUI float progressNormalized = (_current - _minimum) / _maximum; if (progressNormalized > 0.001f) { - var barRect = new Rectangle(0, 0, Width * progressNormalized, Height); - BarMargin.ShrinkRectangle(ref barRect); - if (BarBrush != null) - BarBrush.Draw(barRect, BarColor); + Rectangle barRect = new Rectangle(0, 0, Width * progressNormalized, Height); + switch (Direction) + { + case BarDirection.HorizontalLeft: + break; + case BarDirection.HorizontalRight: + barRect = new Rectangle(Width - Width * progressNormalized, 0, Width * progressNormalized, Height); + break; + case BarDirection.VerticalUp: + barRect = new Rectangle(0, 0, Width, Height * progressNormalized); + break; + case BarDirection.VerticalDown: + barRect = new Rectangle(0, Height - Height * progressNormalized, Width, Height * progressNormalized); + break; + default: break; + } + + if (ClipBar) + { + var rect = new Rectangle(0, 0, Width, Height); + BarMargin.ShrinkRectangle(ref rect); + Render2D.PushClip(ref barRect); + if (BarBrush != null) + BarBrush.Draw(rect, BarColor); + else + Render2D.FillRectangle(rect, BarColor); + Render2D.PopClip(); + } else - Render2D.FillRectangle(barRect, BarColor); + { + BarMargin.ShrinkRectangle(ref barRect); + if (BarBrush != null) + BarBrush.Draw(barRect, BarColor); + else + Render2D.FillRectangle(barRect, BarColor); + } } } } From 53fd158f2d76a2afe49bb953520a16136ae367ae Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 30 Aug 2023 18:09:59 -0500 Subject: [PATCH 68/76] Add a way to reset an IBrush to null for default functionality. --- Source/Editor/CustomEditors/Editors/IBrushEditor.cs | 1 + Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Editors/IBrushEditor.cs b/Source/Editor/CustomEditors/Editors/IBrushEditor.cs index beb28ca80..387364df6 100644 --- a/Source/Editor/CustomEditors/Editors/IBrushEditor.cs +++ b/Source/Editor/CustomEditors/Editors/IBrushEditor.cs @@ -16,6 +16,7 @@ namespace FlaxEditor.CustomEditors.Editors /// protected override OptionType[] Options => new[] { + new OptionType("null", null), new OptionType("Texture", typeof(TextureBrush)), new OptionType("Sprite", typeof(SpriteBrush)), new OptionType("GPU Texture", typeof(GPUTextureBrush)), diff --git a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs index 25a058d86..4d0c0f662 100644 --- a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs @@ -158,7 +158,9 @@ namespace FlaxEditor.CustomEditors.Editors if (comboBox.SelectedIndex != -1) { var option = _options[comboBox.SelectedIndex]; - value = option.Creator(option.Type); + if (option.Type != null) + value = option.Creator(option.Type); + } SetValue(value); RebuildLayoutOnRefresh(); From e4dfda72028fcbb9123021eadd128b1380914c90 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 31 Aug 2023 14:07:17 -0500 Subject: [PATCH 69/76] Add method enum. --- Source/Engine/UI/GUI/Common/ProgressBar.cs | 52 +++++++++++++++------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/ProgressBar.cs b/Source/Engine/UI/GUI/Common/ProgressBar.cs index ee7d32dad..ebeed1fa1 100644 --- a/Source/Engine/UI/GUI/Common/ProgressBar.cs +++ b/Source/Engine/UI/GUI/Common/ProgressBar.cs @@ -10,6 +10,22 @@ namespace FlaxEngine.GUI /// public class ProgressBar : ContainerControl { + /// + /// The method used to effect the bar. + /// + public enum BarMethod + { + /// + /// Stretch the bar. + /// + Stretch, + + /// + /// Clip the bar. + /// + Clip, + } + /// /// The direction to move the progress bar /// @@ -28,12 +44,12 @@ namespace FlaxEngine.GUI /// /// Move the bar vertically up. /// - VerticalUp, + VerticalTop, /// /// Move the bar vertically down. /// - VerticalDown, + VerticalBottom, } /// @@ -66,12 +82,12 @@ namespace FlaxEngine.GUI /// Gets a value indicating whether use progress value smoothing. /// public bool UseSmoothing => !Mathf.IsZero(SmoothingScale); - + /// - /// If true, the progress bar will clip instead of stretch. + /// The method used to effect the bar. /// - [EditorOrder(42), Tooltip("Whether or not to clip vs stretch the progress bar.")] - public bool ClipBar = false; + [EditorOrder(41), Tooltip("The method used to effect the bar.")] + public BarMethod Method = BarMethod.Stretch; /// /// The direction to clip or stretch the bar. @@ -214,17 +230,25 @@ namespace FlaxEngine.GUI case BarDirection.HorizontalRight: barRect = new Rectangle(Width - Width * progressNormalized, 0, Width * progressNormalized, Height); break; - case BarDirection.VerticalUp: + case BarDirection.VerticalTop: barRect = new Rectangle(0, 0, Width, Height * progressNormalized); break; - case BarDirection.VerticalDown: + case BarDirection.VerticalBottom: barRect = new Rectangle(0, Height - Height * progressNormalized, Width, Height * progressNormalized); break; default: break; } - if (ClipBar) + switch (Method) { + case BarMethod.Stretch: + BarMargin.ShrinkRectangle(ref barRect); + if (BarBrush != null) + BarBrush.Draw(barRect, BarColor); + else + Render2D.FillRectangle(barRect, BarColor); + break; + case BarMethod.Clip: var rect = new Rectangle(0, 0, Width, Height); BarMargin.ShrinkRectangle(ref rect); Render2D.PushClip(ref barRect); @@ -233,14 +257,8 @@ namespace FlaxEngine.GUI else Render2D.FillRectangle(rect, BarColor); Render2D.PopClip(); - } - else - { - BarMargin.ShrinkRectangle(ref barRect); - if (BarBrush != null) - BarBrush.Draw(barRect, BarColor); - else - Render2D.FillRectangle(barRect, BarColor); + break; + default: break; } } } From a98458be1fe3e0d91fa62d785699876bb82b91b0 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 31 Aug 2023 14:14:01 -0500 Subject: [PATCH 70/76] Small improvements. --- Source/Engine/UI/GUI/Common/ProgressBar.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/ProgressBar.cs b/Source/Engine/UI/GUI/Common/ProgressBar.cs index ebeed1fa1..760f031a7 100644 --- a/Source/Engine/UI/GUI/Common/ProgressBar.cs +++ b/Source/Engine/UI/GUI/Common/ProgressBar.cs @@ -27,9 +27,9 @@ namespace FlaxEngine.GUI } /// - /// The direction to move the progress bar + /// The origin to move the progress bar to. /// - public enum BarDirection + public enum BarOrigin { /// /// Move the bar horizontally to the left. @@ -90,10 +90,10 @@ namespace FlaxEngine.GUI public BarMethod Method = BarMethod.Stretch; /// - /// The direction to clip or stretch the bar. + /// The origin or where the bar decreases to. /// - [EditorOrder(42), Tooltip("The direction to clip or stretch the bar.")] - public BarDirection Direction = BarDirection.HorizontalLeft; + [EditorOrder(42), Tooltip("The origin or where the bar decreases to.")] + public BarOrigin Origin = BarOrigin.HorizontalLeft; /// /// Gets or sets the minimum value. @@ -223,17 +223,17 @@ namespace FlaxEngine.GUI if (progressNormalized > 0.001f) { Rectangle barRect = new Rectangle(0, 0, Width * progressNormalized, Height); - switch (Direction) + switch (Origin) { - case BarDirection.HorizontalLeft: + case BarOrigin.HorizontalLeft: break; - case BarDirection.HorizontalRight: + case BarOrigin.HorizontalRight: barRect = new Rectangle(Width - Width * progressNormalized, 0, Width * progressNormalized, Height); break; - case BarDirection.VerticalTop: + case BarOrigin.VerticalTop: barRect = new Rectangle(0, 0, Width, Height * progressNormalized); break; - case BarDirection.VerticalBottom: + case BarOrigin.VerticalBottom: barRect = new Rectangle(0, Height - Height * progressNormalized, Width, Height * progressNormalized); break; default: break; From 21b90fb8297d21e9b9f35ea277fc5fa1e7477544 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 1 Sep 2023 11:42:31 +0200 Subject: [PATCH 71/76] Fix crash when spawning large amount of network objects at once by sending spawn message in parts #1344 --- Source/Engine/Networking/NetworkInternal.h | 2 + Source/Engine/Networking/NetworkManager.cpp | 3 +- .../Engine/Networking/NetworkReplicator.cpp | 598 +++++++++++------- 3 files changed, 362 insertions(+), 241 deletions(-) diff --git a/Source/Engine/Networking/NetworkInternal.h b/Source/Engine/Networking/NetworkInternal.h index efcabad5f..521e8a7a2 100644 --- a/Source/Engine/Networking/NetworkInternal.h +++ b/Source/Engine/Networking/NetworkInternal.h @@ -12,6 +12,7 @@ enum class NetworkMessageIDs : uint8 ObjectReplicate, ObjectReplicatePart, ObjectSpawn, + ObjectSpawnPart, ObjectDespawn, ObjectRole, ObjectRpc, @@ -30,6 +31,7 @@ public: static void OnNetworkMessageObjectReplicate(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageObjectReplicatePart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); + static void OnNetworkMessageObjectSpawnPart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageObjectRole(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index b18136e23..ee2395f2a 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -15,7 +15,7 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Scripting.h" -#define NETWORK_PROTOCOL_VERSION 2 +#define NETWORK_PROTOCOL_VERSION 3 float NetworkManager::NetworkFPS = 60.0f; NetworkPeer* NetworkManager::Peer = nullptr; @@ -131,6 +131,7 @@ namespace NetworkInternal::OnNetworkMessageObjectReplicate, NetworkInternal::OnNetworkMessageObjectReplicatePart, NetworkInternal::OnNetworkMessageObjectSpawn, + NetworkInternal::OnNetworkMessageObjectSpawnPart, NetworkInternal::OnNetworkMessageObjectDespawn, NetworkInternal::OnNetworkMessageObjectRole, NetworkInternal::OnNetworkMessageObjectRpc, diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index a63e27471..8133b6cf2 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -66,8 +66,17 @@ PACK_STRUCT(struct NetworkMessageObjectSpawn { NetworkMessageIDs ID = NetworkMessageIDs::ObjectSpawn; uint32 OwnerClientId; + uint32 OwnerSpawnId; // Unique for peer who spawned it and matches OwnerSpawnId inside following part messages Guid PrefabId; - uint16 ItemsCount; + uint16 ItemsCount; // Total items count + uint8 UseParts : 1; // True if spawn message is header-only and all items come in the separate parts + }); + +PACK_STRUCT(struct NetworkMessageObjectSpawnPart + { + NetworkMessageIDs ID = NetworkMessageIDs::ObjectSpawnPart; + uint32 OwnerClientId; + uint32 OwnerSpawnId; }); PACK_STRUCT(struct NetworkMessageObjectSpawnItem @@ -173,6 +182,12 @@ struct SpawnItem NetworkObjectRole Role; }; +struct SpawnItemParts +{ + NetworkMessageObjectSpawn MsgData; + Array Items; +}; + struct SpawnGroup { Array> Items; @@ -198,6 +213,7 @@ namespace CriticalSection ObjectsLock; HashSet Objects; Array ReplicationParts; + Array SpawnParts; Array SpawnQueue; Array DespawnQueue; Array RpcQueue; @@ -213,6 +229,7 @@ namespace Dictionary CSharpCachedNames; #endif Array DespawnedObjects; + uint32 SpawnId = 0; } class NetworkReplicationService : public EngineService @@ -258,7 +275,7 @@ NetworkReplicatedObject* ResolveObject(Guid objectId) return it != Objects.End() ? &it->Item : nullptr; } -NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, char objectTypeName[128]) +NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, const char objectTypeName[128]) { // Lookup object NetworkReplicatedObject* obj = ResolveObject(objectId); @@ -398,8 +415,33 @@ FORCE_INLINE void GetNetworkName(char buffer[128], const StringAnsiView& name) buffer[name.Length()] = 0; } +void SetupObjectSpawnMessageItem(SpawnItem* e, NetworkMessage& msg) +{ + ScriptingObject* obj = e->Object.Get(); + auto it = Objects.Find(obj->GetID()); + const auto& item = it->Item; + + // Add object into spawn message + NetworkMessageObjectSpawnItem msgDataItem; + msgDataItem.ObjectId = item.ObjectId; + msgDataItem.ParentId = item.ParentId; + if (NetworkManager::IsClient()) + { + // Remap local client object ids into server ids + IdsRemappingTable.KeyOf(msgDataItem.ObjectId, &msgDataItem.ObjectId); + IdsRemappingTable.KeyOf(msgDataItem.ParentId, &msgDataItem.ParentId); + } + msgDataItem.PrefabObjectID = Guid::Empty; + auto* objScene = ScriptingObject::Cast(obj); + if (objScene && objScene->HasPrefabLink()) + msgDataItem.PrefabObjectID = objScene->GetPrefabObjectID(); + GetNetworkName(msgDataItem.ObjectTypeName, obj->GetType().Fullname); + msg.WriteStructure(msgDataItem); +} + void SendObjectSpawnMessage(const SpawnGroup& group, const Array& clients) { + PROFILE_CPU(); const bool isClient = NetworkManager::IsClient(); auto* peer = NetworkManager::Peer; NetworkMessage msg = peer->BeginSendMessage(); @@ -415,37 +457,56 @@ void SendObjectSpawnMessage(const SpawnGroup& group, const Array // Setup clients that should receive this spawn message auto it = Objects.Find(obj->GetID()); - auto& item = it->Item; + const auto& item = it->Item; BuildCachedTargets(clients, item.TargetClientIds); } - msg.WriteStructure(msgData); - for (SpawnItem* e : group.Items) - { - ScriptingObject* obj = e->Object.Get(); - auto it = Objects.Find(obj->GetID()); - auto& item = it->Item; - // Add object into spawn message - NetworkMessageObjectSpawnItem msgDataItem; - msgDataItem.ObjectId = item.ObjectId; - msgDataItem.ParentId = item.ParentId; + // Network Peer has fixed size of messages so split spawn message into parts if there are too many objects to fit at once + msgData.OwnerSpawnId = ++SpawnId; + msgData.UseParts = msg.BufferSize - msg.Position < group.Items.Count() * sizeof(NetworkMessageObjectSpawnItem); + msg.WriteStructure(msgData); + if (msgData.UseParts) + { if (isClient) + peer->EndSendMessage(NetworkChannelType::Reliable, msg); + else + peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets); + + // Send spawn items in separate parts + NetworkMessageObjectSpawnPart msgDataPart; + msgDataPart.OwnerClientId = msgData.OwnerClientId; + msgDataPart.OwnerSpawnId = msgData.OwnerSpawnId; + uint16 itemIndex = 0; + constexpr uint32 spawnItemMaxSize = sizeof(uint16) + sizeof(NetworkMessageObjectSpawnItem); // Index + Data + while (itemIndex < msgData.ItemsCount) { - // Remap local client object ids into server ids - IdsRemappingTable.KeyOf(msgDataItem.ObjectId, &msgDataItem.ObjectId); - IdsRemappingTable.KeyOf(msgDataItem.ParentId, &msgDataItem.ParentId); + msg = peer->BeginSendMessage(); + msg.WriteStructure(msgDataPart); + + // Write as many items as possible into this message + while (msg.Position + spawnItemMaxSize <= msg.BufferSize && itemIndex < msgData.ItemsCount) + { + msg.WriteUInt16(itemIndex); + SetupObjectSpawnMessageItem(group.Items[itemIndex], msg); + itemIndex++; + } + + if (isClient) + peer->EndSendMessage(NetworkChannelType::Reliable, msg); + else + peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets); } - msgDataItem.PrefabObjectID = Guid::Empty; - auto* objScene = ScriptingObject::Cast(obj); - if (objScene && objScene->HasPrefabLink()) - msgDataItem.PrefabObjectID = objScene->GetPrefabObjectID(); - GetNetworkName(msgDataItem.ObjectTypeName, obj->GetType().Fullname); - msg.WriteStructure(msgDataItem); } - if (isClient) - peer->EndSendMessage(NetworkChannelType::Reliable, msg); else - peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets); + { + // Send all spawn items within the spawn message + for (SpawnItem* e : group.Items) + SetupObjectSpawnMessageItem(e, msg); + if (isClient) + peer->EndSendMessage(NetworkChannelType::Reliable, msg); + else + peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets); + } } void SendObjectRoleMessage(const NetworkReplicatedObject& item, const NetworkClient* excludedClient = nullptr) @@ -655,6 +716,235 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b DirtyObjectImpl(item, obj); } +void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMessageObjectSpawnItem* msgDataItems) +{ + ScopeLock lock(ObjectsLock); + + // Check if that object has been already spawned + auto& rootItem = msgDataItems[0]; + NetworkReplicatedObject* root = ResolveObject(rootItem.ObjectId, rootItem.ParentId, rootItem.ObjectTypeName); + if (root) + { + // Object already exists locally so just synchronize the ownership (and mark as spawned) + for (int32 i = 0; i < msgData.ItemsCount; i++) + { + auto& msgDataItem = msgDataItems[i]; + NetworkReplicatedObject* e = ResolveObject(msgDataItem.ObjectId, msgDataItem.ParentId, msgDataItem.ObjectTypeName); + auto& item = *e; + item.Spawned = true; + if (NetworkManager::IsClient()) + { + // Server always knows the best so update ownership of the existing object + item.OwnerClientId = msgData.OwnerClientId; + if (item.Role == NetworkObjectRole::OwnedAuthoritative) + { + if (Hierarchy) + Hierarchy->AddObject(item.Object); + item.Role = NetworkObjectRole::Replicated; + } + } + else if (item.OwnerClientId != msgData.OwnerClientId) + { + // Other client spawned object with a different owner + // TODO: send reply message to inform about proper object ownership that client + } + } + return; + } + + // Recreate object locally (spawn only root) + Actor* prefabInstance = nullptr; + Array objects; + if (msgData.PrefabId.IsValid()) + { + const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId); + Actor* parentActor = parent && parent->Object && parent->Object->Is() ? parent->Object.As() : nullptr; + if (parentActor && parentActor->GetPrefabID() == msgData.PrefabId) + { + // Reuse parent object as prefab instance + prefabInstance = parentActor; + } + else if ((parentActor = Scripting::TryFindObject(rootItem.ParentId))) + { + // Try to find that spawned prefab (eg. prefab with networked script was spawned before so now we need to link it) + for (Actor* child : parentActor->Children) + { + if (child->GetPrefabID() == msgData.PrefabId) + { + if (Objects.Contains(child->GetID())) + { + ScriptingObject* obj = FindPrefabObject(child, rootItem.PrefabObjectID); + if (Objects.Contains(obj->GetID())) + { + // Other instance with already spawned network object + obj = nullptr; + } + else + { + // Reuse already spawned object within a parent + prefabInstance = child; + break; + } + } + } + } + } + if (!prefabInstance) + { + // Spawn prefab + auto prefab = (Prefab*)LoadAsset(msgData.PrefabId, Prefab::TypeInitializer); + if (!prefab) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", msgData.PrefabId.ToString()); + return; + } + prefabInstance = PrefabManager::SpawnPrefab(prefab, nullptr, nullptr); + if (!prefabInstance) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", msgData.PrefabId.ToString()); + return; + } + } + + // Resolve objects from prefab instance + objects.Resize(msgData.ItemsCount); + for (int32 i = 0; i < msgData.ItemsCount; i++) + { + auto& msgDataItem = msgDataItems[i]; + ScriptingObject* obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID); + if (!obj) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString()); + Delete(prefabInstance); + return; + } + objects[i] = obj; + } + } + else if (msgData.ItemsCount == 1) + { + // Spawn object + const ScriptingTypeHandle objectType = Scripting::FindScriptingType(rootItem.ObjectTypeName); + ScriptingObject* obj = ScriptingObject::NewObject(objectType); + if (!obj) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(rootItem.ObjectTypeName)); + return; + } + objects.Add(obj); + } + else + { + // Spawn objects + objects.Resize(msgData.ItemsCount); + for (int32 i = 0; i < msgData.ItemsCount; i++) + { + auto& msgDataItem = msgDataItems[i]; + const ScriptingTypeHandle objectType = Scripting::FindScriptingType(msgDataItem.ObjectTypeName); + ScriptingObject* obj = ScriptingObject::NewObject(objectType); + if (!obj) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(msgDataItem.ObjectTypeName)); + for (ScriptingObject* e : objects) + Delete(e); + return; + } + objects[i] = obj; + if (i != 0) + { + // Link hierarchy of spawned objects before calling any networking code for them + if (auto sceneObject = ScriptingObject::Cast(obj)) + { + Actor* parent = nullptr; + for (int32 j = 0; j < i; j++) + { + if (msgDataItems[j].ObjectId == msgDataItem.ParentId) + { + parent = ScriptingObject::Cast(objects[j]); + break; + } + } + if (parent) + sceneObject->SetParent(parent); + } + } + } + } + + // Add all newly spawned objects + for (int32 i = 0; i < msgData.ItemsCount; i++) + { + auto& msgDataItem = msgDataItems[i]; + ScriptingObject* obj = objects[i]; + if (!obj->IsRegistered()) + obj->RegisterObject(); + const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId); + + // Add object to the list + NetworkReplicatedObject item; + item.Object = obj; + item.AsNetworkObject = ScriptingObject::ToInterface(obj); + item.ObjectId = obj->GetID(); + item.ParentId = parent ? parent->ObjectId : Guid::Empty; + item.OwnerClientId = msgData.OwnerClientId; + item.Role = NetworkObjectRole::Replicated; + if (item.OwnerClientId == NetworkManager::LocalClientId) + { + // Upgrade ownership automatically (eg. server spawned object that local client should own) + item.Role = NetworkObjectRole::OwnedAuthoritative; + } + item.Spawned = true; + NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Add new object {}:{}, parent {}:{}", item.ToString(), obj->GetType().ToString(), item.ParentId.ToString(), parent ? parent->Object->GetType().ToString() : String::Empty); + Objects.Add(MoveTemp(item)); + if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative) + Hierarchy->AddObject(obj); + + // Boost future lookups by using indirection + NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remap object ID={} into object {}:{}", msgDataItem.ObjectId, item.ToString(), obj->GetType().ToString()); + IdsRemappingTable.Add(msgDataItem.ObjectId, item.ObjectId); + } + + // Spawn all newly spawned objects (ensure to have valid ownership hierarchy set before spawning object) + for (int32 i = 0; i < msgData.ItemsCount; i++) + { + auto& msgDataItem = msgDataItems[i]; + ScriptingObject* obj = objects[i]; + auto it = Objects.Find(obj->GetID()); + auto& item = it->Item; + const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId); + + // Automatic parenting for scene objects + auto sceneObject = ScriptingObject::Cast(obj); + if (sceneObject) + { + if (parent && parent->Object.Get() && parent->Object->Is()) + sceneObject->SetParent(parent->Object.As()); + else if (auto* parentActor = Scripting::TryFindObject(msgDataItem.ParentId)) + sceneObject->SetParent(parentActor); + else if (msgDataItem.ParentId.IsValid()) + { +#if USE_NETWORK_REPLICATOR_LOG + // Ignore case when parent object in a message was a scene (eg. that is already unloaded on a client) + AssetInfo assetInfo; + if (!Content::GetAssetInfo(msgDataItem.ParentId, assetInfo) || assetInfo.TypeName != TEXT("FlaxEngine.SceneAsset")) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString()); + } +#endif + } + } + else if (!parent && msgDataItem.ParentId.IsValid()) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString()); + } + + if (item.AsNetworkObject) + item.AsNetworkObject->OnNetworkSpawn(); + } + + // TODO: if we're server then spawn this object further on other clients (use TargetClientIds for that object - eg. object spawned by client on client for certain set of other clients only) +} + NetworkRpcParams::NetworkRpcParams(const NetworkStream* stream) : SenderId(stream->SenderId) { @@ -1388,7 +1678,7 @@ void NetworkInternal::NetworkReplicatorUpdate() auto& e = ReplicationParts[i]; if (e.PartsLeft > 0) { - // TODO: remove replication items after some TTL to prevent memory leaks + // TODO: remove replication items after some TTL to reduce memory usage continue; } ScriptingObject* obj = e.Object.Get(); @@ -1408,6 +1698,8 @@ void NetworkInternal::NetworkReplicatorUpdate() } } + // TODO: remove items from SpawnParts after some TTL to reduce memory usage + // Replicate all owned networked objects with other clients or server if (!CachedReplicationResult) CachedReplicationResult = New(); @@ -1655,234 +1947,60 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl PROFILE_CPU(); NetworkMessageObjectSpawn msgData; event.Message.ReadStructure(msgData); - auto* msgDataItems = (NetworkMessageObjectSpawnItem*)event.Message.SkipBytes(msgData.ItemsCount * sizeof(NetworkMessageObjectSpawnItem)); if (msgData.ItemsCount == 0) return; - ScopeLock lock(ObjectsLock); - - // Check if that object has been already spawned - auto& rootItem = msgDataItems[0]; - NetworkReplicatedObject* root = ResolveObject(rootItem.ObjectId, rootItem.ParentId, rootItem.ObjectTypeName); - if (root) + if (msgData.UseParts) { - // Object already exists locally so just synchronize the ownership (and mark as spawned) - for (int32 i = 0; i < msgData.ItemsCount; i++) - { - auto& msgDataItem = msgDataItems[i]; - NetworkReplicatedObject* e = ResolveObject(msgDataItem.ObjectId, msgDataItem.ParentId, msgDataItem.ObjectTypeName); - auto& item = *e; - item.Spawned = true; - if (NetworkManager::IsClient()) - { - // Server always knows the best so update ownership of the existing object - item.OwnerClientId = msgData.OwnerClientId; - if (item.Role == NetworkObjectRole::OwnedAuthoritative) - { - if (Hierarchy) - Hierarchy->AddObject(item.Object); - item.Role = NetworkObjectRole::Replicated; - } - } - else if (item.OwnerClientId != msgData.OwnerClientId) - { - // Other client spawned object with a different owner - // TODO: send reply message to inform about proper object ownership that client - } - } - return; - } - - // Recreate object locally (spawn only root) - Actor* prefabInstance = nullptr; - Array objects; - if (msgData.PrefabId.IsValid()) - { - const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId); - Actor* parentActor = parent && parent->Object && parent->Object->Is() ? parent->Object.As() : nullptr; - if (parentActor && parentActor->GetPrefabID() == msgData.PrefabId) - { - // Reuse parent object as prefab instance - prefabInstance = parentActor; - } - else if ((parentActor = Scripting::TryFindObject(rootItem.ParentId))) - { - // Try to find that spawned prefab (eg. prefab with networked script was spawned before so now we need to link it) - for (Actor* child : parentActor->Children) - { - if (child->GetPrefabID() == msgData.PrefabId) - { - if (Objects.Contains(child->GetID())) - { - ScriptingObject* obj = FindPrefabObject(child, rootItem.PrefabObjectID); - if (Objects.Contains(obj->GetID())) - { - // Other instance with already spawned network object - obj = nullptr; - } - else - { - // Reuse already spawned object within a parent - prefabInstance = child; - break; - } - } - } - } - } - if (!prefabInstance) - { - // Spawn prefab - auto prefab = (Prefab*)LoadAsset(msgData.PrefabId, Prefab::TypeInitializer); - if (!prefab) - { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", msgData.PrefabId.ToString()); - return; - } - prefabInstance = PrefabManager::SpawnPrefab(prefab, nullptr, nullptr); - if (!prefabInstance) - { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", msgData.PrefabId.ToString()); - return; - } - } - - // Resolve objects from prefab instance - objects.Resize(msgData.ItemsCount); - for (int32 i = 0; i < msgData.ItemsCount; i++) - { - auto& msgDataItem = msgDataItems[i]; - ScriptingObject* obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID); - if (!obj) - { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString()); - Delete(prefabInstance); - return; - } - objects[i] = obj; - } - } - else if (msgData.ItemsCount == 1) - { - // Spawn object - const ScriptingTypeHandle objectType = Scripting::FindScriptingType(rootItem.ObjectTypeName); - ScriptingObject* obj = ScriptingObject::NewObject(objectType); - if (!obj) - { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(rootItem.ObjectTypeName)); - return; - } - objects.Add(obj); + // Allocate spawn message parts collecting + auto& parts = SpawnParts.AddOne(); + parts.MsgData = msgData; + parts.Items.Resize(msgData.ItemsCount); + for (auto& item : parts.Items) + item.ObjectId = Guid::Empty; // Mark as not yet received } else { - // Spawn objects - objects.Resize(msgData.ItemsCount); - for (int32 i = 0; i < msgData.ItemsCount; i++) - { - auto& msgDataItem = msgDataItems[i]; - const ScriptingTypeHandle objectType = Scripting::FindScriptingType(msgDataItem.ObjectTypeName); - ScriptingObject* obj = ScriptingObject::NewObject(objectType); - if (!obj) - { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(msgDataItem.ObjectTypeName)); - for (ScriptingObject* e : objects) - Delete(e); - return; - } - objects[i] = obj; - if (i != 0) - { - // Link hierarchy of spawned objects before calling any networking code for them - if (auto sceneObject = ScriptingObject::Cast(obj)) - { - Actor* parent = nullptr; - for (int32 j = 0; j < i; j++) - { - if (msgDataItems[j].ObjectId == msgDataItem.ParentId) - { - parent = ScriptingObject::Cast(objects[j]); - break; - } - } - if (parent) - sceneObject->SetParent(parent); - } - } - } + const auto* msgDataItems = (NetworkMessageObjectSpawnItem*)event.Message.SkipBytes(msgData.ItemsCount * sizeof(NetworkMessageObjectSpawnItem)); + InvokeObjectSpawn(msgData, msgDataItems); } +} - // Add all newly spawned objects - for (int32 i = 0; i < msgData.ItemsCount; i++) +void NetworkInternal::OnNetworkMessageObjectSpawnPart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) +{ + PROFILE_CPU(); + NetworkMessageObjectSpawnPart msgData; + event.Message.ReadStructure(msgData); + int32 spawnPartsIndex; + for (spawnPartsIndex = 0; spawnPartsIndex < SpawnParts.Count(); spawnPartsIndex++) { - auto& msgDataItem = msgDataItems[i]; - ScriptingObject* obj = objects[i]; - if (!obj->IsRegistered()) - obj->RegisterObject(); - const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId); - - // Add object to the list - NetworkReplicatedObject item; - item.Object = obj; - item.AsNetworkObject = ScriptingObject::ToInterface(obj); - item.ObjectId = obj->GetID(); - item.ParentId = parent ? parent->ObjectId : Guid::Empty; - item.OwnerClientId = msgData.OwnerClientId; - item.Role = NetworkObjectRole::Replicated; - if (item.OwnerClientId == NetworkManager::LocalClientId) - { - // Upgrade ownership automatically (eg. server spawned object that local client should own) - item.Role = NetworkObjectRole::OwnedAuthoritative; - } - item.Spawned = true; - NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Add new object {}:{}, parent {}:{}", item.ToString(), obj->GetType().ToString(), item.ParentId.ToString(), parent ? parent->Object->GetType().ToString() : String::Empty); - Objects.Add(MoveTemp(item)); - if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative) - Hierarchy->AddObject(obj); - - // Boost future lookups by using indirection - NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remap object ID={} into object {}:{}", msgDataItem.ObjectId, item.ToString(), obj->GetType().ToString()); - IdsRemappingTable.Add(msgDataItem.ObjectId, item.ObjectId); + // Find spawn parts container that matches this spawn message (unique pair of sender and id assigned by sender) + const auto& e = SpawnParts.Get()[spawnPartsIndex]; + if (e.MsgData.OwnerClientId == msgData.OwnerClientId && e.MsgData.OwnerSpawnId == msgData.OwnerSpawnId) + break; } - - // Spawn all newly spawned objects (ensure to have valid ownership hierarchy set before spawning object) - for (int32 i = 0; i < msgData.ItemsCount; i++) + if (spawnPartsIndex >= SpawnParts.Count()) { - auto& msgDataItem = msgDataItems[i]; - ScriptingObject* obj = objects[i]; - auto it = Objects.Find(obj->GetID()); - auto& item = it->Item; - const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId); + // Invalid part or data, ignore it + return; + } + auto& spawnParts = SpawnParts.Get()[spawnPartsIndex]; - // Automatic parenting for scene objects - auto sceneObject = ScriptingObject::Cast(obj); - if (sceneObject) - { - if (parent && parent->Object.Get() && parent->Object->Is()) - sceneObject->SetParent(parent->Object.As()); - else if (auto* parentActor = Scripting::TryFindObject(msgDataItem.ParentId)) - sceneObject->SetParent(parentActor); - else if (msgDataItem.ParentId.IsValid()) - { -#if USE_NETWORK_REPLICATOR_LOG - // Ignore case when parent object in a message was a scene (eg. that is already unloaded on a client) - AssetInfo assetInfo; - if (!Content::GetAssetInfo(msgDataItem.ParentId, assetInfo) || assetInfo.TypeName != TEXT("FlaxEngine.SceneAsset")) - { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString()); - } -#endif - } - } - else if (!parent && msgDataItem.ParentId.IsValid()) - { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString()); - } - - if (item.AsNetworkObject) - item.AsNetworkObject->OnNetworkSpawn(); + // Read all items from this part + constexpr uint32 spawnItemMaxSize = sizeof(uint16) + sizeof(NetworkMessageObjectSpawnItem); // Index + Data + while (event.Message.Position + spawnItemMaxSize <= event.Message.BufferSize) + { + const uint16 itemIndex = event.Message.ReadUInt16(); + event.Message.ReadStructure(spawnParts.Items[itemIndex]); } - // TODO: if we're server then spawn this object further on other clients (use TargetClientIds for that object - eg. object spawned by client on client for certain set of other clients only) + // Invoke spawning if we've got all items + for (auto& e : spawnParts.Items) + { + if (!e.ObjectId.IsValid()) + return; + } + InvokeObjectSpawn(spawnParts.MsgData, spawnParts.Items.Get()); + SpawnParts.RemoveAt(spawnPartsIndex); } void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) From 462da342a7511f6a05cf212223ae2857036dafbc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 1 Sep 2023 11:43:02 +0200 Subject: [PATCH 72/76] Fix `ContextMenuSingleSelectGroup` selection of the first item that had default value --- .../Editor/GUI/ContextMenu/ContextMenuSingleSelectGroup.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuSingleSelectGroup.cs b/Source/Editor/GUI/ContextMenu/ContextMenuSingleSelectGroup.cs index 5abb52b4a..c89e1bb61 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuSingleSelectGroup.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuSingleSelectGroup.cs @@ -23,6 +23,7 @@ namespace FlaxEditor.GUI.ContextMenu private List _menus = new List(); private List _items = new List(); + private bool _hasSelected = false; private SingleSelectGroupItem _selectedItem; public T Selected @@ -31,7 +32,7 @@ namespace FlaxEditor.GUI.ContextMenu set { var index = _items.FindIndex(x => x.Value.Equals(value)); - if (index != -1 && !_selectedItem.Value.Equals(value)) + if (index != -1 && (!_hasSelected || !_selectedItem.Value.Equals(value))) { SetSelected(_items[index]); } @@ -70,7 +71,7 @@ namespace FlaxEditor.GUI.ContextMenu if (item.Tooltip != null) btn.TooltipText = item.Tooltip; item.Buttons.Add(btn); - if (item.Equals(_selectedItem)) + if (_hasSelected && item.Equals(_selectedItem)) btn.Checked = true; } @@ -82,6 +83,7 @@ namespace FlaxEditor.GUI.ContextMenu btn.Checked = false; } _selectedItem = item; + _hasSelected = true; SelectedChanged?.Invoke(item.Value); item.Selected?.Invoke(); From a8cc4d7fcbf336cab76c6bc1e69cac6e15e3f491 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 1 Sep 2023 12:14:26 +0200 Subject: [PATCH 73/76] Fix wheel vehicle drive when `EnableSimulation` is disabled #1323 --- Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 69dfeaa27..4b18804da 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -1001,7 +1001,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) int32 wheelsCount = 0; for (auto wheelVehicle : scenePhysX->WheelVehicles) { - if (!wheelVehicle->IsActiveInHierarchy()) + if (!wheelVehicle->IsActiveInHierarchy() || !wheelVehicle->GetEnableSimulation()) continue; auto drive = (PxVehicleWheels*)wheelVehicle->_vehicle; ASSERT(drive); @@ -1216,7 +1216,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) for (int32 i = 0, ii = 0; i < scenePhysX->WheelVehicles.Count(); i++) { auto wheelVehicle = scenePhysX->WheelVehicles[i]; - if (!wheelVehicle->IsActiveInHierarchy()) + if (!wheelVehicle->IsActiveInHierarchy() || !wheelVehicle->GetEnableSimulation()) continue; auto drive = (PxVehicleWheels*)scenePhysX->WheelVehicles[ii]->_vehicle; auto& perVehicle = WheelVehiclesResultsPerVehicle[ii]; @@ -1237,7 +1237,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) for (int32 i = 0, ii = 0; i < scenePhysX->WheelVehicles.Count(); i++) { auto wheelVehicle = scenePhysX->WheelVehicles[i]; - if (!wheelVehicle->IsActiveInHierarchy()) + if (!wheelVehicle->IsActiveInHierarchy() || !wheelVehicle->GetEnableSimulation()) continue; auto drive = WheelVehiclesCache[ii]; auto& perVehicle = WheelVehiclesResultsPerVehicle[ii]; From b0ec8525aab51fea854d1e71c971244deeab7d83 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 1 Sep 2023 12:40:51 +0200 Subject: [PATCH 74/76] Add support for editing `WheeledVehicle` wheels config at runtime without full physics state rebuild #1324 --- .../Engine/Physics/Actors/WheeledVehicle.cpp | 25 ++++++++++ .../Physics/PhysX/PhysicsBackendPhysX.cpp | 47 +++++++++++++++++++ Source/Engine/Physics/PhysicsBackend.h | 1 + Source/Engine/Physics/PhysicsBackendEmpty.cpp | 4 ++ 4 files changed, 77 insertions(+) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index f5f729d39..0ab8e6518 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -40,6 +40,31 @@ const Array& WheeledVehicle::GetWheels() const void WheeledVehicle::SetWheels(const Array& value) { +#if WITH_VEHICLE + // Don't recreate whole vehicle when some wheel properties are only changed (eg. suspension) + if (_actor && _vehicle && _wheels.Count() == value.Count() && _wheelsData.Count() == value.Count()) + { + bool softUpdate = true; + for (int32 wheelIndex = 0; wheelIndex < value.Count(); wheelIndex++) + { + auto& oldWheel = _wheels.Get()[wheelIndex]; + auto& newWheel = value.Get()[wheelIndex]; + if (oldWheel.Type != newWheel.Type || + Math::NotNearEqual(oldWheel.SuspensionForceOffset, newWheel.SuspensionForceOffset) || + oldWheel.Collider != newWheel.Collider) + { + softUpdate = false; + break; + } + } + if (softUpdate) + { + _wheels = value; + PhysicsBackend::UpdateVehicleWheels(this); + return; + } + } +#endif _wheels = value; Setup(); } diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 4b18804da..619f025ed 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -2923,6 +2923,53 @@ void PhysicsBackend::DestroyVehicle(void* vehicle, int32 driveType) } } +void PhysicsBackend::UpdateVehicleWheels(WheeledVehicle* actor) +{ + auto drive = (PxVehicleWheels*)actor->_vehicle; + PxVehicleWheelsSimData* wheelsSimData = &drive->mWheelsSimData; + for (uint32 i = 0; i < wheelsSimData->getNbWheels(); i++) + { + auto& wheel = actor->_wheels[i]; + + // Update suspension data + PxVehicleSuspensionData suspensionData = wheelsSimData->getSuspensionData(i); + const float suspensionFrequency = 7.0f; + suspensionData.mMaxCompression = wheel.SuspensionMaxRaise; + suspensionData.mMaxDroop = wheel.SuspensionMaxDrop; + suspensionData.mSpringStrength = Math::Square(suspensionFrequency) * suspensionData.mSprungMass; + suspensionData.mSpringDamperRate = wheel.SuspensionDampingRate * 2.0f * Math::Sqrt(suspensionData.mSpringStrength * suspensionData.mSprungMass); + wheelsSimData->setSuspensionData(i, suspensionData); + + // Update tire data + PxVehicleTireData tire; + int32 tireIndex = WheelTireTypes.Find(wheel.TireFrictionScale); + if (tireIndex == -1) + { + // New tire type + tireIndex = WheelTireTypes.Count(); + WheelTireTypes.Add(wheel.TireFrictionScale); + WheelTireFrictionsDirty = true; + } + tire.mType = tireIndex; + tire.mLatStiffX = wheel.TireLateralMax; + tire.mLatStiffY = wheel.TireLateralStiffness; + tire.mLongitudinalStiffnessPerUnitGravity = wheel.TireLongitudinalStiffness; + wheelsSimData->setTireData(i, tire); + + // Update wheel data + PxVehicleWheelData wheelData; + wheelData.mMass = wheel.Mass; + wheelData.mRadius = wheel.Radius; + wheelData.mWidth = wheel.Width; + wheelData.mMOI = 0.5f * wheelData.mMass * Math::Square(wheelData.mRadius); + wheelData.mDampingRate = M2ToCm2(wheel.DampingRate); + wheelData.mMaxSteer = wheel.MaxSteerAngle * DegreesToRadians; + wheelData.mMaxBrakeTorque = M2ToCm2(wheel.MaxBrakeTorque); + wheelData.mMaxHandBrakeTorque = M2ToCm2(wheel.MaxHandBrakeTorque); + wheelsSimData->setWheelData(i, wheelData); + } +} + void PhysicsBackend::SetVehicleGearbox(void* vehicle, const void* value) { auto drive = (PxVehicleDrive*)vehicle; diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index f72c7f679..ba452cda7 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -245,6 +245,7 @@ public: // Vehicles static void* CreateVehicle(class WheeledVehicle* actor); static void DestroyVehicle(void* vehicle, int32 driveType); + static void UpdateVehicleWheels(WheeledVehicle* actor); static void SetVehicleGearbox(void* vehicle, const void* value); static int32 GetVehicleTargetGear(void* vehicle); static void SetVehicleTargetGear(void* vehicle, int32 value); diff --git a/Source/Engine/Physics/PhysicsBackendEmpty.cpp b/Source/Engine/Physics/PhysicsBackendEmpty.cpp index 516562e28..28d849a32 100644 --- a/Source/Engine/Physics/PhysicsBackendEmpty.cpp +++ b/Source/Engine/Physics/PhysicsBackendEmpty.cpp @@ -732,6 +732,10 @@ void PhysicsBackend::DestroyVehicle(void* vehicle, int32 driveType) { } +void PhysicsBackend::UpdateVehicleWheels(WheeledVehicle* actor) +{ +} + void PhysicsBackend::SetVehicleGearbox(void* vehicle, const void* value) { } From 5a8944a82d59e60820663dde13bf3b20d6467db0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 1 Sep 2023 12:52:01 +0200 Subject: [PATCH 75/76] Add support for editing `WheeledVehicle` engine/differential config at runtime without full physics state rebuild #1348 --- .../Engine/Physics/Actors/WheeledVehicle.cpp | 8 ++ .../Physics/PhysX/PhysicsBackendPhysX.cpp | 104 ++++++++++++++++++ Source/Engine/Physics/PhysicsBackend.h | 2 + Source/Engine/Physics/PhysicsBackendEmpty.cpp | 8 ++ 4 files changed, 122 insertions(+) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 0ab8e6518..286f5b24e 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -76,6 +76,10 @@ WheeledVehicle::EngineSettings WheeledVehicle::GetEngine() const void WheeledVehicle::SetEngine(const EngineSettings& value) { +#if WITH_VEHICLE + if (_vehicle) + PhysicsBackend::SetVehicleEngine(_vehicle, &value); +#endif _engine = value; } @@ -86,6 +90,10 @@ WheeledVehicle::DifferentialSettings WheeledVehicle::GetDifferential() const void WheeledVehicle::SetDifferential(const DifferentialSettings& value) { +#if WITH_VEHICLE + if (_vehicle) + PhysicsBackend::SetVehicleDifferential(_vehicle, &value); +#endif _differential = value; } diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 619f025ed..df427ab85 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -2970,12 +2970,116 @@ void PhysicsBackend::UpdateVehicleWheels(WheeledVehicle* actor) } } +void PhysicsBackend::SetVehicleEngine(void* vehicle, const void* value) +{ + auto drive = (PxVehicleDrive*)vehicle; + auto& engine = *(const WheeledVehicle::EngineSettings*)value; + switch (drive->getVehicleType()) + { + case PxVehicleTypes::eDRIVE4W: + { + auto drive4W = (PxVehicleDrive4W*)drive; + PxVehicleDriveSimData4W& driveSimData = drive4W->mDriveSimData; + PxVehicleEngineData engineData; + engineData.mMOI = M2ToCm2(engine.MOI); + engineData.mPeakTorque = M2ToCm2(engine.MaxTorque); + engineData.mMaxOmega = RpmToRadPerS(engine.MaxRotationSpeed); + engineData.mDampingRateFullThrottle = M2ToCm2(0.15f); + engineData.mDampingRateZeroThrottleClutchEngaged = M2ToCm2(2.0f); + engineData.mDampingRateZeroThrottleClutchDisengaged = M2ToCm2(0.35f); + driveSimData.setEngineData(engineData); + break; + } + case PxVehicleTypes::eDRIVENW: + { + auto drive4W = (PxVehicleDriveNW*)drive; + PxVehicleDriveSimDataNW& driveSimData = drive4W->mDriveSimData; + PxVehicleEngineData engineData; + engineData.mMOI = M2ToCm2(engine.MOI); + engineData.mPeakTorque = M2ToCm2(engine.MaxTorque); + engineData.mMaxOmega = RpmToRadPerS(engine.MaxRotationSpeed); + engineData.mDampingRateFullThrottle = M2ToCm2(0.15f); + engineData.mDampingRateZeroThrottleClutchEngaged = M2ToCm2(2.0f); + engineData.mDampingRateZeroThrottleClutchDisengaged = M2ToCm2(0.35f); + driveSimData.setEngineData(engineData); + break; + } + } +} + +void PhysicsBackend::SetVehicleDifferential(void* vehicle, const void* value) +{ + auto drive = (PxVehicleDrive*)vehicle; + auto& differential = *(const WheeledVehicle::DifferentialSettings*)value; + switch (drive->getVehicleType()) + { + case PxVehicleTypes::eDRIVE4W: + { + auto drive4W = (PxVehicleDrive4W*)drive; + PxVehicleDriveSimData4W& driveSimData = drive4W->mDriveSimData; + PxVehicleDifferential4WData differential4WData; + differential4WData.mType = (PxVehicleDifferential4WData::Enum)differential.Type; + differential4WData.mFrontRearSplit = differential.FrontRearSplit; + differential4WData.mFrontLeftRightSplit = differential.FrontLeftRightSplit; + differential4WData.mRearLeftRightSplit = differential.RearLeftRightSplit; + differential4WData.mCentreBias = differential.CentreBias; + differential4WData.mFrontBias = differential.FrontBias; + differential4WData.mRearBias = differential.RearBias; + driveSimData.setDiffData(differential4WData); + break; + } + } +} + void PhysicsBackend::SetVehicleGearbox(void* vehicle, const void* value) { auto drive = (PxVehicleDrive*)vehicle; auto& gearbox = *(const WheeledVehicle::GearboxSettings*)value; drive->mDriveDynData.setUseAutoGears(gearbox.AutoGear); drive->mDriveDynData.setAutoBoxSwitchTime(Math::Max(gearbox.SwitchTime, 0.0f)); + switch (drive->getVehicleType()) + { + case PxVehicleTypes::eDRIVE4W: + { + auto drive4W = (PxVehicleDrive4W*)drive; + PxVehicleDriveSimData4W& driveSimData = drive4W->mDriveSimData; + + // Gears + PxVehicleGearsData gears; + gears.mSwitchTime = Math::Max(gearbox.SwitchTime, 0.0f); + driveSimData.setGearsData(gears); + + // Auto Box + PxVehicleAutoBoxData autoBox; + driveSimData.setAutoBoxData(autoBox); + + // Clutch + PxVehicleClutchData clutch; + clutch.mStrength = M2ToCm2(gearbox.ClutchStrength); + driveSimData.setClutchData(clutch); + break; + } + case PxVehicleTypes::eDRIVENW: + { + auto drive4W = (PxVehicleDriveNW*)drive; + PxVehicleDriveSimDataNW& driveSimData = drive4W->mDriveSimData; + + // Gears + PxVehicleGearsData gears; + gears.mSwitchTime = Math::Max(gearbox.SwitchTime, 0.0f); + driveSimData.setGearsData(gears); + + // Auto Box + PxVehicleAutoBoxData autoBox; + driveSimData.setAutoBoxData(autoBox); + + // Clutch + PxVehicleClutchData clutch; + clutch.mStrength = M2ToCm2(gearbox.ClutchStrength); + driveSimData.setClutchData(clutch); + break; + } + } } int32 PhysicsBackend::GetVehicleTargetGear(void* vehicle) diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index ba452cda7..6c761a776 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -246,6 +246,8 @@ public: static void* CreateVehicle(class WheeledVehicle* actor); static void DestroyVehicle(void* vehicle, int32 driveType); static void UpdateVehicleWheels(WheeledVehicle* actor); + static void SetVehicleEngine(void* vehicle, const void* value); + static void SetVehicleDifferential(void* vehicle, const void* value); static void SetVehicleGearbox(void* vehicle, const void* value); static int32 GetVehicleTargetGear(void* vehicle); static void SetVehicleTargetGear(void* vehicle, int32 value); diff --git a/Source/Engine/Physics/PhysicsBackendEmpty.cpp b/Source/Engine/Physics/PhysicsBackendEmpty.cpp index 28d849a32..1a418af0c 100644 --- a/Source/Engine/Physics/PhysicsBackendEmpty.cpp +++ b/Source/Engine/Physics/PhysicsBackendEmpty.cpp @@ -736,6 +736,14 @@ void PhysicsBackend::UpdateVehicleWheels(WheeledVehicle* actor) { } +void PhysicsBackend::SetVehicleEngine(void* vehicle, const void* value) +{ +} + +void PhysicsBackend::SetVehicleDifferential(void* vehicle, const void* value) +{ +} + void PhysicsBackend::SetVehicleGearbox(void* vehicle, const void* value) { } From 36dca16991ddcf3504834402d495c53a1d85dfc1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 1 Sep 2023 13:43:36 +0200 Subject: [PATCH 76/76] Fix `WheeledVehicle` driving in `Drive4W` mode when wheels are in custom order #1352 --- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index df427ab85..d4429bfcb 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -29,6 +29,7 @@ #include #include #if WITH_VEHICLE +#include "Engine/Core/Collections/Sorting.h" #include "Engine/Physics/Actors/WheeledVehicle.h" #include #include @@ -2630,8 +2631,20 @@ int32 PhysicsBackend::MoveController(void* controller, void* shape, const Vector #if WITH_VEHICLE +bool SortWheels(WheeledVehicle::Wheel const& a, WheeledVehicle::Wheel const& b) +{ + return (int32)a.Type < (int32)b.Type; +} + void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor) { + // TODO: handle PxVehicleDrive4WWheelOrder internally rather than sorting wheels directly on the vehicle + if (actor->_driveType == WheeledVehicle::DriveTypes::Drive4W) + { + // Drive4W requires wheels to match order from PxVehicleDrive4WWheelOrder enum + Sorting::QuickSort(actor->_wheels.Get(), actor->_wheels.Count(), SortWheels); + } + // Get wheels Array> wheels; for (auto& wheel : actor->_wheels) @@ -2676,10 +2689,7 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor) // Initialize wheels simulation data PxVec3 offsets[PX_MAX_NB_WHEELS]; for (int32 i = 0; i < wheels.Count(); i++) - { - auto& wheel = *wheels[i]; - offsets[i] = C2P(wheel.Collider->GetLocalPosition()); - } + offsets[i] = C2P(wheels[i]->Collider->GetLocalPosition()); PxF32 sprungMasses[PX_MAX_NB_WHEELS]; const float mass = actorPhysX->getMass(); // TODO: get gravityDirection from scenePhysX->Scene->getGravity() @@ -3043,7 +3053,7 @@ void PhysicsBackend::SetVehicleGearbox(void* vehicle, const void* value) { auto drive4W = (PxVehicleDrive4W*)drive; PxVehicleDriveSimData4W& driveSimData = drive4W->mDriveSimData; - + // Gears PxVehicleGearsData gears; gears.mSwitchTime = Math::Max(gearbox.SwitchTime, 0.0f);