diff --git a/Development/Scripts/Windows/CallBuildTool.bat b/Development/Scripts/Windows/CallBuildTool.bat index 9f60368c5..22c2d1dd7 100644 --- a/Development/Scripts/Windows/CallBuildTool.bat +++ b/Development/Scripts/Windows/CallBuildTool.bat @@ -11,16 +11,6 @@ for %%I in (Source\Logo.png) do if %%~zI LSS 2000 ( call "Development\Scripts\Windows\GetMSBuildPath.bat" if errorlevel 1 goto Error_NoVisualStudioEnvironment -if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto Compile -for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( - for %%j in (15.0, Current) do ( - if exist "%%i\MSBuild\%%j\Bin\MSBuild.exe" ( - set MSBUILD_PATH="%%i\MSBuild\%%j\Bin\MSBuild.exe" - goto Compile - ) - ) -) - :Compile md Cache\Intermediate >nul 2>nul dir /s /b Source\Tools\Flax.Build\*.cs >Cache\Intermediate\Flax.Build.Files.txt @@ -44,7 +34,7 @@ goto Exit echo CallBuildTool ERROR: The script is in invalid directory. goto Exit :Error_NoVisualStudioEnvironment -echo CallBuildTool ERROR: Missing Visual Studio 2015 or newer. +echo CallBuildTool ERROR: Missing Visual Studio 2022 or newer. goto Exit :Error_CompilationFailed echo CallBuildTool ERROR: Failed to compile Flax.Build project. diff --git a/Development/Scripts/Windows/GetMSBuildPath.bat b/Development/Scripts/Windows/GetMSBuildPath.bat index b20230118..f44155272 100644 --- a/Development/Scripts/Windows/GetMSBuildPath.bat +++ b/Development/Scripts/Windows/GetMSBuildPath.bat @@ -4,66 +4,26 @@ rem Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. set MSBUILD_PATH= +rem Look for MSBuild version 17.0 or later if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto VsWhereNotFound -for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( - if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" ( - set MSBUILD_PATH="%%i\MSBuild\15.0\Bin\MSBuild.exe" - goto End - ) -) -for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( - if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" ( - set MSBUILD_PATH="%%i\MSBuild\15.0\Bin\MSBuild.exe" - goto End - ) +for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -version 17.0 -latest -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" ( set MSBUILD_PATH="%%i\MSBuild\Current\Bin\MSBuild.exe" goto End ) ) -:VsWhereNotFound -if exist "%ProgramFiles(x86)%\MSBuild\14.0\bin\MSBuild.exe" ( - set MSBUILD_PATH="%ProgramFiles(x86)%\MSBuild\14.0\bin\MSBuild.exe" - goto End +rem Look for MSBuild version 17.0 or later in pre-release versions +for /f "delims=" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -version 17.0 -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath') do ( + if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" ( + set MSBUILD_PATH="%%i\MSBuild\Current\Bin\MSBuild.exe" + goto End + ) ) - -call :GetInstallPath Microsoft\VisualStudio\SxS\VS7 15.0 MSBuild\15.0\bin\MSBuild.exe -if not errorlevel 1 goto End -call :GetInstallPath Microsoft\MSBuild\ToolsVersions\14.0 MSBuildToolsPath MSBuild.exe -if not errorlevel 1 goto End -call :GetInstallPath Microsoft\MSBuild\ToolsVersions\12.0 MSBuildToolsPath MSBuild.exe -if not errorlevel 1 goto End -call :GetInstallPath Microsoft\MSBuild\ToolsVersions\4.0 MSBuildToolsPath MSBuild.exe -if not errorlevel 1 goto End - +echo GetMSBuildPath ERROR: Could not find MSBuild version 17.0 or later. +exit /B 1 +:VsWhereNotFound +echo GetMSBuildPath ERROR: vswhere.exe was not found. exit /B 1 :End -exit /B 0 - -:GetInstallPath -for /f "tokens=2,*" %%A in ('REG.exe query HKCU\SOFTWARE\%1 /v %2 2^>Nul') do ( - if exist "%%B%%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -for /f "tokens=2,*" %%A in ('REG.exe query HKLM\SOFTWARE\%1 /v %2 2^>Nul') do ( - if exist "%%B%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -for /f "tokens=2,*" %%A in ('REG.exe query HKCU\SOFTWARE\Wow6432Node\%1 /v %2 2^>Nul') do ( - if exist "%%B%%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -for /f "tokens=2,*" %%A in ('REG.exe query HKLM\SOFTWARE\Wow6432Node\%1 /v %2 2^>Nul') do ( - if exist "%%B%3" ( - set MSBUILD_PATH="%%B%3" - exit /B 0 - ) -) -exit /B 1 +exit /B 0 \ No newline at end of file diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index e8af1461c..6a22f211b 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -291,6 +291,8 @@ True True True + True + True True True True diff --git a/GenerateProjectFiles.bat b/GenerateProjectFiles.bat index 1b766457f..622939c34 100644 --- a/GenerateProjectFiles.bat +++ b/GenerateProjectFiles.bat @@ -1,15 +1,22 @@ @echo off - -rem Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +:: Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. setlocal pushd + echo Generating Flax Engine project files... -rem Run Flax.Build to generate Visual Studio solution and project files (also pass the arguments) -call "Development\Scripts\Windows\CallBuildTool.bat" -genproject %* +:: Change the path to the script root +cd /D "%~dp0" + +:: Run Flax.Build to generate Visual Studio solution and project files (also pass the arguments) +call "Development\Scripts\Windows\CallBuildTool.bat" -genproject %* if errorlevel 1 goto BuildToolFailed +:: Build bindings for all editor configurations +echo Building C# bindings... +Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor,FlaxGame + popd echo Done! exit /B 0 diff --git a/GenerateProjectFiles.command b/GenerateProjectFiles.command index 6554886bc..5ee5c0783 100755 --- a/GenerateProjectFiles.command +++ b/GenerateProjectFiles.command @@ -10,3 +10,8 @@ cd "`dirname "$0"`" # Run Flax.Build to generate project files (also pass the arguments) bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@" + +# Build bindings for all editor configurations +echo Building C# bindings... +# TODO: Detect the correct architecture here +Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor,FlaxGame diff --git a/GenerateProjectFiles.sh b/GenerateProjectFiles.sh index fcda1acc1..dceb8abe8 100755 --- a/GenerateProjectFiles.sh +++ b/GenerateProjectFiles.sh @@ -10,3 +10,8 @@ cd "`dirname "$0"`" # Run Flax.Build to generate project files (also pass the arguments) bash ./Development/Scripts/Linux/CallBuildTool.sh --genproject "$@" + +# Build bindings for all editor configurations +echo Building C# bindings... +# TODO: Detect the correct architecture here +Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor,FlaxGame diff --git a/README.md b/README.md index dc5abb84f..fac631a6a 100644 --- a/README.md +++ b/README.md @@ -31,19 +31,20 @@ Follow the instructions below to compile and run the engine from source. * Install Visual Studio 2022 or newer * Install Windows 8.1 SDK or newer (via Visual Studio Installer) * Install Microsoft Visual C++ 2015 v140 toolset or newer (via Visual Studio Installer) -* Install .Net 7 SDK (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) +* Install .NET 7 SDK for **Windows x64** (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) * Install Git with LFS * Clone repo (with LFS) * Run **GenerateProjectFiles.bat** * Open `Flax.sln` and set solution configuration to **Editor.Development** and solution platform to **Win64** * Set Flax (C++) or FlaxEngine (C#) as startup project * Compile Flax project (hit F7 or CTRL+Shift+B) +* Optionally set Debug Type to **Managed Only (.NET Core)** to debug C#-only, or **Mixed (.NET Core)** to debug both C++ and C# * Run Flax (hit F5 key) ## Linux * Install Visual Studio Code -* Install .Net 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) +* Install .NET 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) * Ubuntu: `sudo apt install dotnet-sdk-7.0` * Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)) * Ubuntu: `sudo apt install vulkan-sdk` @@ -66,7 +67,7 @@ Follow the instructions below to compile and run the engine from source. ## Mac * Install XCode -* Install .Net 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) +* Install .NET 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) * Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)) * Clone repo (with LFS) * Run `GenerateProjectFiles.command` diff --git a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs new file mode 100644 index 000000000..03d5ddd55 --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs @@ -0,0 +1,26 @@ +using FlaxEditor.CustomEditors.Editors; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Dedicated; + +/// +/// The missing script editor. +/// +[CustomEditor(typeof(MissingScript)), DefaultEditor] +public class MissingScriptEditor : GenericEditor +{ + /// + public override void Initialize(LayoutElementsContainer layout) + { + if (layout.ContainerControl is not DropPanel dropPanel) + { + base.Initialize(layout); + return; + } + + dropPanel.HeaderTextColor = Color.OrangeRed; + + base.Initialize(layout); + } +} diff --git a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs index ec71348cf..5b2cfc144 100644 --- a/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ParticleEffectEditor.cs @@ -25,9 +25,20 @@ namespace FlaxEditor.CustomEditors.Dedicated get { // All selected particle effects use the same system - var effect = (ParticleEffect)Values[0]; - var system = effect.ParticleSystem; - return system != null && Values.TrueForAll(x => (x as ParticleEffect)?.ParticleSystem == system); + var effect = Values[0] as ParticleEffect; + var system = effect ? effect.ParticleSystem : null; + if (system && Values.TrueForAll(x => x is ParticleEffect fx && fx && fx.ParticleSystem == system)) + { + // All parameters can be accessed + var parameters = effect.Parameters; + foreach (var parameter in parameters) + { + if (!parameter) + return false; + } + return true; + } + return false; } } diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index bf82e19da..aaf45bc52 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -25,6 +25,7 @@ namespace FlaxEditor.CustomEditors.Dedicated private DragHandlers _dragHandlers; private DragScriptItems _dragScripts; private DragAssets _dragAssets; + private Button _addScriptsButton; /// /// The parent scripts editor. @@ -40,16 +41,19 @@ namespace FlaxEditor.CustomEditors.Dedicated AutoFocus = false; // Add script button - float addScriptButtonWidth = 60.0f; - var addScriptButton = new Button + var buttonText = "Add script"; + var textSize = Style.Current.FontMedium.MeasureText(buttonText); + float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; + var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4; + _addScriptsButton = new Button { TooltipText = "Add new scripts to the actor", AnchorPreset = AnchorPresets.MiddleCenter, - Text = "Add script", + Text = buttonText, Parent = this, - Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, 18), + Bounds = new Rectangle((Width - addScriptButtonWidth) / 2, 1, addScriptButtonWidth, buttonHeight), }; - addScriptButton.ButtonClicked += OnAddScriptButtonClicked; + _addScriptsButton.ButtonClicked += OnAddScriptButtonClicked; } private void OnAddScriptButtonClicked(Button button) @@ -82,7 +86,7 @@ namespace FlaxEditor.CustomEditors.Dedicated var size = Size; // Info - Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, 22, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(style.FontSmall, "Drag scripts here", new Rectangle(2, _addScriptsButton.Height + 4, size.X - 4, size.Y - 4 - 20), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); // Check if drag is over if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag) @@ -576,8 +580,8 @@ namespace FlaxEditor.CustomEditors.Dedicated return; for (int j = 0; j < e.Length; j++) { - var t1 = scripts[j]?.TypeName; - var t2 = e[j]?.TypeName; + var t1 = scripts[j] != null ? scripts[j].TypeName : null; + var t2 = e[j] != null ? e[j].TypeName : null; if (t1 != t2) return; } diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index fde4967e8..5215e23a4 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -422,12 +422,14 @@ namespace FlaxEditor.CustomEditors.Dedicated // Set control type button var space = layout.Space(20); - float setTypeButtonWidth = 60.0f; + var buttonText = "Set Type"; + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + float setTypeButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var setTypeButton = new Button { TooltipText = "Sets the control to the given type", AnchorPreset = AnchorPresets.MiddleCenter, - Text = "Set Type", + Text = buttonText, Parent = space.Spacer, Bounds = new Rectangle((space.Spacer.Width - setTypeButtonWidth) / 2, 1, setTypeButtonWidth, 18), }; diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index cb4e7b9c1..4aa02ac78 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -88,20 +88,20 @@ namespace FlaxEditor.CustomEditors.Editors LinkValues = Editor.Instance.Windows.PropertiesWin.ScaleLinked; // Add button with the link icon + _linkButton = new Button { BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Link32), Parent = LinkedLabel, Width = 18, Height = 18, - AnchorPreset = AnchorPresets.TopLeft, + AnchorPreset = AnchorPresets.MiddleLeft, }; _linkButton.Clicked += ToggleLink; ToggleEnabled(); SetLinkStyle(); - var x = LinkedLabel.Text.Value.Length * 7 + 5; - _linkButton.LocalX += x; - _linkButton.LocalY += 1; + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value); + _linkButton.LocalX += textSize.X + 10; LinkedLabel.SetupContextMenu += (label, menu, editor) => { menu.AddSeparator(); diff --git a/Source/Editor/CustomEditors/Editors/BooleanEditor.cs b/Source/Editor/CustomEditors/Editors/BooleanEditor.cs index f5edafed8..803b7b4dd 100644 --- a/Source/Editor/CustomEditors/Editors/BooleanEditor.cs +++ b/Source/Editor/CustomEditors/Editors/BooleanEditor.cs @@ -34,7 +34,9 @@ namespace FlaxEditor.CustomEditors.Editors } else { - element.CheckBox.Checked = (bool)Values[0]; + var value = (bool?)Values[0]; + if (value != null) + element.CheckBox.Checked = value.Value; } } } diff --git a/Source/Editor/CustomEditors/Editors/InputEditor.cs b/Source/Editor/CustomEditors/Editors/InputEditor.cs index 416626e81..7b2682a84 100644 --- a/Source/Editor/CustomEditors/Editors/InputEditor.cs +++ b/Source/Editor/CustomEditors/Editors/InputEditor.cs @@ -1,8 +1,10 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using FlaxEditor.CustomEditors.GUI; +using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; -using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors.Editors { @@ -12,7 +14,7 @@ namespace FlaxEditor.CustomEditors.Editors [CustomEditor(typeof(InputEvent)), DefaultEditor] public class InputEventEditor : CustomEditor { - private Dropdown _dropdown; + private ComboBox _comboBox; /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -20,23 +22,30 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var dropdownElement = layout.Custom(); - _dropdown = dropdownElement.CustomControl; - var names = new List(); + LinkedLabel.SetupContextMenu += OnSetupContextMenu; + var comboBoxElement = layout.ComboBox(); + _comboBox = comboBoxElement.ComboBox; + var names = new List(); foreach (var mapping in Input.ActionMappings) { if (!names.Contains(mapping.Name)) names.Add(mapping.Name); } - _dropdown.Items = names; + _comboBox.Items = names; if (Values[0] is InputEvent inputEvent && names.Contains(inputEvent.Name)) - _dropdown.SelectedItem = inputEvent.Name; - _dropdown.SelectedIndexChanged += OnSelectedIndexChanged; + _comboBox.SelectedItem = inputEvent.Name; + _comboBox.SelectedIndexChanged += OnSelectedIndexChanged; } - private void OnSelectedIndexChanged(Dropdown dropdown) + private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor) { - SetValue(new InputEvent(dropdown.SelectedItem)); + var button = menu.AddButton("Set to null"); + button.Clicked += () => _comboBox.SelectedItem = null; + } + + private void OnSelectedIndexChanged(ComboBox comboBox) + { + SetValue(comboBox.SelectedItem == null ? null : new InputEvent(comboBox.SelectedItem)); } /// @@ -49,17 +58,21 @@ namespace FlaxEditor.CustomEditors.Editors } else { - if (Values[0] is InputEvent inputEvent && _dropdown.Items.Contains(inputEvent.Name)) - _dropdown.SelectedItem = inputEvent.Name; + if (Values[0] is InputEvent inputEvent && _comboBox.Items.Contains(inputEvent.Name)) + _comboBox.SelectedItem = inputEvent.Name; + else + _comboBox.SelectedItem = null; } } /// protected override void Deinitialize() { - if (_dropdown != null) - _dropdown.SelectedIndexChanged -= OnSelectedIndexChanged; - _dropdown = null; + if (LinkedLabel != null) + LinkedLabel.SetupContextMenu -= OnSetupContextMenu; + if (_comboBox != null) + _comboBox.SelectedIndexChanged -= OnSelectedIndexChanged; + _comboBox = null; } } @@ -69,7 +82,7 @@ namespace FlaxEditor.CustomEditors.Editors [CustomEditor(typeof(InputAxis)), DefaultEditor] public class InputAxisEditor : CustomEditor { - private Dropdown _dropdown; + private ComboBox _comboBox; /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -77,23 +90,30 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var dropdownElement = layout.Custom(); - _dropdown = dropdownElement.CustomControl; - var names = new List(); + LinkedLabel.SetupContextMenu += OnSetupContextMenu; + var comboBoxElement = layout.ComboBox(); + _comboBox = comboBoxElement.ComboBox; + var names = new List(); foreach (var mapping in Input.AxisMappings) { if (!names.Contains(mapping.Name)) names.Add(mapping.Name); } - _dropdown.Items = names; + _comboBox.Items = names; if (Values[0] is InputAxis inputAxis && names.Contains(inputAxis.Name)) - _dropdown.SelectedItem = inputAxis.Name; - _dropdown.SelectedIndexChanged += OnSelectedIndexChanged; + _comboBox.SelectedItem = inputAxis.Name; + _comboBox.SelectedIndexChanged += OnSelectedIndexChanged; } - private void OnSelectedIndexChanged(Dropdown dropdown) + private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor) { - SetValue(new InputAxis(dropdown.SelectedItem)); + var button = menu.AddButton("Set to null"); + button.Clicked += () => _comboBox.SelectedItem = null; + } + + private void OnSelectedIndexChanged(ComboBox comboBox) + { + SetValue(comboBox.SelectedItem == null ? null : new InputAxis(comboBox.SelectedItem)); } /// @@ -106,17 +126,21 @@ namespace FlaxEditor.CustomEditors.Editors } else { - if (Values[0] is InputAxis inputAxis && _dropdown.Items.Contains(inputAxis.Name)) - _dropdown.SelectedItem = inputAxis.Name; + if (Values[0] is InputAxis inputAxis && _comboBox.Items.Contains(inputAxis.Name)) + _comboBox.SelectedItem = inputAxis.Name; + else + _comboBox.SelectedItem = null; } } /// protected override void Deinitialize() { - if (_dropdown != null) - _dropdown.SelectedIndexChanged -= OnSelectedIndexChanged; - _dropdown = null; + if (LinkedLabel != null) + LinkedLabel.SetupContextMenu -= OnSetupContextMenu; + if (_comboBox != null) + _comboBox.SelectedIndexChanged -= OnSelectedIndexChanged; + _comboBox = null; } } } diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index 3d2dd86aa..911397785 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -623,13 +623,18 @@ namespace FlaxEditor.CustomEditors.Editors { _label = layout.ClickableLabel(GetText(out _)).CustomControl; _label.RightClick += ShowPicker; + var buttonText = "..."; var button = new Button { Size = new Float2(16.0f), - Text = "...", + Text = buttonText, TooltipText = "Edit...", Parent = _label, }; + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + if (textSize.Y > button.Width) + button.Width = textSize.Y + 2; + button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true); button.Clicked += ShowPicker; } @@ -674,9 +679,9 @@ namespace FlaxEditor.CustomEditors.Editors } set { - if (Values[0] is Tag[]) + if (Values[0] is Tag[] || Values.Type.Type == typeof(Tag[])) SetValue(value); - if (Values[0] is List) + else if (Values[0] is List || Values.Type.Type == typeof(List)) SetValue(new List(value)); } } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 7f0359331..06a6f0979 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -13,6 +14,7 @@ using FlaxEditor.Content.Thumbnails; using FlaxEditor.Modules; using FlaxEditor.Modules.SourceCodeEditing; using FlaxEditor.Options; +using FlaxEditor.SceneGraph.Actors; using FlaxEditor.States; using FlaxEditor.Windows; using FlaxEditor.Windows.Assets; @@ -1274,6 +1276,69 @@ namespace FlaxEditor Scene.MarkSceneEdited(scenes); } + /// + /// Bakes all environmental probes in the scene. + /// + public void BakeAllEnvProbes() + { + Scene.ExecuteOnGraph(node => + { + if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive) + { + ((EnvironmentProbe)envProbeNode.Actor).Bake(); + node.ParentScene.IsEdited = true; + } + else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene) + { + skyLight.Bake(); + node.ParentScene.IsEdited = true; + } + + return node.IsActive; + }); + } + + /// + /// Builds CSG for all open scenes. + /// + public void BuildCSG() + { + var scenes = Level.Scenes; + scenes.ToList().ForEach(x => x.BuildCSG(0)); + Scene.MarkSceneEdited(scenes); + } + + /// + /// Builds Nav mesh for all open scenes. + /// + public void BuildNavMesh() + { + var scenes = Level.Scenes; + scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0)); + Scene.MarkSceneEdited(scenes); + } + + /// + /// Builds SDF for all static models in the scene. + /// + public void BuildAllMeshesSDF() + { + // TODO: async maybe with progress reporting? + Scene.ExecuteOnGraph(node => + { + if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel) + { + if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null) + { + Log("Generating SDF for " + staticModel.Model); + if (!staticModel.Model.GenerateSDF()) + staticModel.Model.Save(); + } + } + return true; + }); + } + #endregion #region Internal Calls diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs index 7fa920ca6..eb2f21356 100644 --- a/Source/Editor/EditorAssets.cs +++ b/Source/Editor/EditorAssets.cs @@ -36,7 +36,9 @@ namespace FlaxEditor public static void OnEditorOptionsChanged(Options.EditorOptions options) { - var param = _highlightMaterial?.GetParameter("Color"); + if (!_highlightMaterial) + return; + var param = _highlightMaterial.GetParameter("Color"); if (param != null) param.Value = options.Visual.HighlightColor; } diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs index f2e5d30e3..25f45a1f8 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.GUI; @@ -269,6 +270,24 @@ namespace FlaxEditor.GUI.ContextMenu return item; } + /// + /// Adds the button. + /// + /// The text. + /// The input binding. + /// On button clicked event. + /// Created context menu item control. + public ContextMenuButton AddButton(string text, InputBinding binding, Action clicked) + { + var item = new ContextMenuButton(this, text, binding.ToString()) + { + Parent = _panel + }; + item.Clicked += clicked; + SortButtons(); + return item; + } + /// /// Gets the child menu (with that name). /// diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs index 8910cfe49..07fc3ff0d 100644 --- a/Source/Editor/GUI/Dialogs/Dialog.cs +++ b/Source/Editor/GUI/Dialogs/Dialog.cs @@ -290,7 +290,11 @@ namespace FlaxEditor.GUI.Dialogs OnCancel(); return true; case KeyboardKeys.Tab: - Root?.Navigate(NavDirection.Next); + if (Root != null) + { + bool shiftDown = Root.GetKey(KeyboardKeys.Shift); + Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next); + } return true; } return false; diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs index 6dc700731..52c5dcd3c 100644 --- a/Source/Editor/GUI/Docking/DockHintWindow.cs +++ b/Source/Editor/GUI/Docking/DockHintWindow.cs @@ -476,6 +476,7 @@ namespace FlaxEditor.GUI.Docking settings.ShowInTaskbar = false; settings.ActivateWhenFirstShown = false; settings.IsTopmost = true; + settings.ShowAfterFirstPaint = false; win = Platform.CreateWindow(ref settings); diff --git a/Source/Editor/GUI/MainMenu.cs b/Source/Editor/GUI/MainMenu.cs index 370ad056b..b313fed9f 100644 --- a/Source/Editor/GUI/MainMenu.cs +++ b/Source/Editor/GUI/MainMenu.cs @@ -292,7 +292,8 @@ namespace FlaxEditor.GUI return true; #if PLATFORM_WINDOWS - if (_useCustomWindowSystem) + var child = GetChildAtRecursive(location); + if (_useCustomWindowSystem && child is not Button && child is not MainMenuButton) { if (_window.IsMaximized) _window.Restore(); diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index 8dad8b20d..b07d693e5 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -37,6 +37,9 @@ namespace FlaxEditor.GUI : base(0, 0, 100, height) { Depth = -1; + + if (Height < Style.Current.FontMedium.Height) + Height = Style.Current.FontMedium.Height + 4; } /// diff --git a/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs b/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs index c5989cca5..76b6ca246 100644 --- a/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs +++ b/Source/Editor/GUI/Timeline/SceneAnimationTimeline.cs @@ -33,7 +33,7 @@ namespace FlaxEditor.GUI.Timeline /// public SceneAnimationPlayer Player { - get => _player; + get => _player != null ? _player : null; set { if (_player == value) @@ -268,7 +268,7 @@ namespace FlaxEditor.GUI.Timeline /// public override void OnSeek(int frame) { - if (_player?.Animation) + if (_player != null && _player.Animation) { _player.Animation.WaitForLoaded(); _player.Time = frame / _player.Animation.FramesPerSecond; diff --git a/Source/Editor/Modules/ProgressReportingModule.cs b/Source/Editor/Modules/ProgressReportingModule.cs index 16acd7f8b..22ae5ea98 100644 --- a/Source/Editor/Modules/ProgressReportingModule.cs +++ b/Source/Editor/Modules/ProgressReportingModule.cs @@ -129,6 +129,7 @@ namespace FlaxEditor.Modules else { Editor.UI.UpdateProgress(string.Empty, 0); + Editor.UI.UpdateStatusBar(); } } diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 08ec09de8..299865dea 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -42,8 +42,10 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuFileSaveScenes; private ContextMenuButton _menuFileCloseScenes; + private ContextMenuButton _menuFileOpenScriptsProject; private ContextMenuButton _menuFileGenerateScriptsProjectFiles; - private ContextMenuButton _menuSaveAll; + private ContextMenuButton _menuFileRecompileScripts; + private ContextMenuButton _menuFileSaveAll; private ContextMenuButton _menuEditUndo; private ContextMenuButton _menuEditRedo; private ContextMenuButton _menuEditCut; @@ -62,15 +64,19 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuGamePlayCurrentScenes; private ContextMenuButton _menuGameStop; private ContextMenuButton _menuGamePause; + private ContextMenuButton _menuGameCookAndRun; + private ContextMenuButton _menuGameRunCookedGame; private ContextMenuButton _menuToolsBuildScenes; private ContextMenuButton _menuToolsBakeLightmaps; private ContextMenuButton _menuToolsClearLightmaps; private ContextMenuButton _menuToolsBakeAllEnvProbes; private ContextMenuButton _menuToolsBuildCSGMesh; private ContextMenuButton _menuToolsBuildNavMesh; - private ContextMenuButton _menuToolsBuildAllMesgesSDF; + private ContextMenuButton _menuToolsBuildAllMeshesSDF; private ContextMenuButton _menuToolsCancelBuilding; + private ContextMenuButton _menuToolsProfilerWindow; private ContextMenuButton _menuToolsSetTheCurrentSceneViewAsDefault; + private ContextMenuButton _menuToolsTakeScreenshot; private ContextMenuChildMenu _menuWindowApplyWindowLayout; private ToolStripButton _toolStripSaveAll; @@ -279,7 +285,7 @@ namespace FlaxEditor.Modules Color color; if (Editor.StateMachine.IsPlayMode) - color = Color.OrangeRed; + color = Style.Current.Statusbar.PlayMode; else color = Style.Current.BackgroundSelected; @@ -293,6 +299,11 @@ namespace FlaxEditor.Modules else text = "Ready"; + if(ProgressVisible) + { + color = Style.Current.Statusbar.Loading; + } + StatusBar.Text = text; StatusBar.StatusColor = color; _contentStats = contentStats; @@ -338,7 +349,7 @@ namespace FlaxEditor.Modules internal void ProgressFailed(string message) { _progressFailed = true; - StatusBar.StatusColor = Color.Red; + StatusBar.StatusColor = Style.Current.Statusbar.Failed; StatusBar.Text = message; _outputLogButton.Visible = true; } @@ -391,6 +402,10 @@ namespace FlaxEditor.Modules { UpdateStatusBar(); } + else if(ProgressVisible) + { + UpdateStatusBar(); + } } private class CustomWindowBorderControl : Control @@ -510,13 +525,13 @@ namespace FlaxEditor.Modules MenuFile = MainMenu.AddButton("File"); var cm = MenuFile.ContextMenu; cm.VisibleChanged += OnMenuFileShowHide; - _menuSaveAll = cm.AddButton("Save All", inputOptions.Save.ToString(), Editor.SaveAll); - _menuFileSaveScenes = cm.AddButton("Save scenes", Editor.Scene.SaveScenes); - _menuFileCloseScenes = cm.AddButton("Close scenes", Editor.Scene.CloseAllScenes); + _menuFileSaveAll = cm.AddButton("Save All", inputOptions.Save, Editor.SaveAll); + _menuFileSaveScenes = cm.AddButton("Save scenes", inputOptions.SaveScenes, Editor.Scene.SaveScenes); + _menuFileCloseScenes = cm.AddButton("Close scenes", inputOptions.CloseScenes, Editor.Scene.CloseAllScenes); cm.AddSeparator(); - cm.AddButton("Open scripts project", Editor.CodeEditing.OpenSolution); - _menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync); - cm.AddButton("Recompile scripts", ScriptsBuilder.Compile); + _menuFileOpenScriptsProject = cm.AddButton("Open scripts project", inputOptions.OpenScriptsProject, Editor.CodeEditing.OpenSolution); + _menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", inputOptions.GenerateScriptsProject, Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync); + _menuFileRecompileScripts = cm.AddButton("Recompile scripts", inputOptions.RecompileScripts, ScriptsBuilder.Compile); cm.AddSeparator(); cm.AddButton("Open project...", OpenProject); cm.AddSeparator(); @@ -526,27 +541,27 @@ namespace FlaxEditor.Modules MenuEdit = MainMenu.AddButton("Edit"); cm = MenuEdit.ContextMenu; cm.VisibleChanged += OnMenuEditShowHide; - _menuEditUndo = cm.AddButton(string.Empty, inputOptions.Undo.ToString(), Editor.PerformUndo); - _menuEditRedo = cm.AddButton(string.Empty, inputOptions.Redo.ToString(), Editor.PerformRedo); + _menuEditUndo = cm.AddButton(string.Empty, inputOptions.Undo, Editor.PerformUndo); + _menuEditRedo = cm.AddButton(string.Empty, inputOptions.Redo, Editor.PerformRedo); cm.AddSeparator(); - _menuEditCut = cm.AddButton("Cut", inputOptions.Cut.ToString(), Editor.SceneEditing.Cut); - _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy.ToString(), Editor.SceneEditing.Copy); - _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste.ToString(), Editor.SceneEditing.Paste); + _menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); + _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); + _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); cm.AddSeparator(); - _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete.ToString(), Editor.SceneEditing.Delete); - _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate.ToString(), Editor.SceneEditing.Duplicate); + _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); + _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); cm.AddSeparator(); - _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll.ToString(), Editor.SceneEditing.SelectAllScenes); - _menuEditFind = cm.AddButton("Find", inputOptions.Search.ToString(), Editor.Windows.SceneWin.Search); + _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes); + _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search); // Scene MenuScene = MainMenu.AddButton("Scene"); cm = MenuScene.ContextMenu; cm.VisibleChanged += OnMenuSceneShowHide; - _menuSceneMoveActorToViewport = cm.AddButton("Move actor to viewport", MoveActorToViewport); - _menuSceneAlignActorWithViewport = cm.AddButton("Align actor with viewport", AlignActorWithViewport); - _menuSceneAlignViewportWithActor = cm.AddButton("Align viewport with actor", AlignViewportWithActor); - _menuScenePilotActor = cm.AddButton("Pilot actor", PilotActor); + _menuSceneMoveActorToViewport = cm.AddButton("Move actor to viewport", inputOptions.MoveActorToViewport, MoveActorToViewport); + _menuSceneAlignActorWithViewport = cm.AddButton("Align actor with viewport", inputOptions.AlignActorWithViewport, AlignActorWithViewport); + _menuSceneAlignViewportWithActor = cm.AddButton("Align viewport with actor", inputOptions.AlignViewportWithActor, AlignViewportWithActor); + _menuScenePilotActor = cm.AddButton("Pilot actor", inputOptions.PilotActor, PilotActor); cm.AddSeparator(); _menuSceneCreateTerrain = cm.AddButton("Create terrain", CreateTerrain); @@ -555,39 +570,41 @@ namespace FlaxEditor.Modules cm = MenuGame.ContextMenu; cm.VisibleChanged += OnMenuGameShowHide; - _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); + _menuGamePlayGame = cm.AddButton("Play Game", inputOptions.Play, Editor.Simulation.RequestPlayGameOrStopPlay); + _menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", inputOptions.PlayCurrentScenes, Editor.Simulation.RequestPlayScenesOrStopPlay); + _menuGameStop = cm.AddButton("Stop Game", inputOptions.Play, Editor.Simulation.RequestStopPlay); + _menuGamePause = cm.AddButton("Pause", inputOptions.Pause, Editor.Simulation.RequestPausePlay); cm.AddSeparator(); var numberOfClientsMenu = cm.AddChildMenu("Number of game clients"); _numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu); cm.AddSeparator(); - 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."); + _menuGameCookAndRun = cm.AddButton("Cook & Run", inputOptions.CookAndRun, Editor.Windows.GameCookerWin.BuildAndRun); + _menuGameCookAndRun.LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after."); + _menuGameRunCookedGame = cm.AddButton("Run cooked game", inputOptions.RunCookedGame, Editor.Windows.GameCookerWin.RunCooked); + _menuGameRunCookedGame.LinkTooltip("Runs the game build from the last cooking output. Use 'Cook & Run' or Game Cooker first."); // Tools MenuTools = MainMenu.AddButton("Tools"); cm = MenuTools.ContextMenu; cm.VisibleChanged += OnMenuToolsShowHide; - _menuToolsBuildScenes = cm.AddButton("Build scenes data", "Ctrl+F10", Editor.BuildScenesOrCancel); + _menuToolsBuildScenes = cm.AddButton("Build scenes data", inputOptions.BuildScenesData, Editor.BuildScenesOrCancel); cm.AddSeparator(); - _menuToolsBakeLightmaps = cm.AddButton("Bake lightmaps", Editor.BakeLightmapsOrCancel); - _menuToolsClearLightmaps = cm.AddButton("Clear lightmaps data", Editor.ClearLightmaps); - _menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", BakeAllEnvProbes); - _menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", BuildCSG); - _menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", BuildNavMesh); - _menuToolsBuildAllMesgesSDF = cm.AddButton("Build all meshes SDF", BuildAllMeshesSDF); + _menuToolsBakeLightmaps = cm.AddButton("Bake lightmaps", inputOptions.BakeLightmaps, Editor.BakeLightmapsOrCancel); + _menuToolsClearLightmaps = cm.AddButton("Clear lightmaps data", inputOptions.ClearLightmaps, Editor.ClearLightmaps); + _menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", inputOptions.BakeEnvProbes, Editor.BakeAllEnvProbes); + _menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", inputOptions.BuildCSG, Editor.BuildCSG); + _menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", inputOptions.BuildNav, Editor.BuildNavMesh); + _menuToolsBuildAllMeshesSDF = cm.AddButton("Build all meshes SDF", inputOptions.BuildSDF, Editor.BuildAllMeshesSDF); cm.AddSeparator(); cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow); _menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel()); cm.AddSeparator(); - cm.AddButton("Profiler", Editor.Windows.ProfilerWin.FocusOrShow); + _menuToolsProfilerWindow = cm.AddButton("Profiler", inputOptions.ProfilerWindow, () => Editor.Windows.ProfilerWin.FocusOrShow()); cm.AddSeparator(); _menuToolsSetTheCurrentSceneViewAsDefault = cm.AddButton("Set current scene view as project default", SetTheCurrentSceneViewAsDefault); - cm.AddButton("Take screenshot", "F12", Editor.Windows.TakeScreenshot); + _menuToolsTakeScreenshot = cm.AddButton("Take screenshot", inputOptions.TakeScreenshot, Editor.Windows.TakeScreenshot); cm.AddSeparator(); cm.AddButton("Plugins", () => Editor.Windows.PluginsWin.Show()); cm.AddButton("Options", () => Editor.Windows.EditorOptionsWin.Show()); @@ -606,7 +623,7 @@ namespace FlaxEditor.Modules cm.AddButton("Output Log", Editor.Windows.OutputLogWin.FocusOrShow); cm.AddButton("Graphics Quality", Editor.Windows.GraphicsQualityWin.FocusOrShow); cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow); - cm.AddButton("Profiler", Editor.Windows.ProfilerWin.FocusOrShow); + cm.AddButton("Profiler", inputOptions.ProfilerWindow, Editor.Windows.ProfilerWin.FocusOrShow); cm.AddButton("Content Search", Editor.ContentFinding.ShowSearch); cm.AddButton("Visual Script Debugger", Editor.Windows.VisualScriptDebuggerWin.FocusOrShow); cm.AddSeparator(); @@ -633,7 +650,12 @@ namespace FlaxEditor.Modules { var inputOptions = options.Input; - _menuSaveAll.ShortKeys = inputOptions.Save.ToString(); + _menuFileSaveAll.ShortKeys = inputOptions.Save.ToString(); + _menuFileSaveScenes.ShortKeys = inputOptions.SaveScenes.ToString(); + _menuFileCloseScenes.ShortKeys = inputOptions.CloseScenes.ToString(); + _menuFileOpenScriptsProject.ShortKeys = inputOptions.OpenScriptsProject.ToString(); + _menuFileGenerateScriptsProjectFiles.ShortKeys = inputOptions.GenerateScriptsProject.ToString(); + _menuFileRecompileScripts.ShortKeys = inputOptions.RecompileScripts.ToString(); _menuEditUndo.ShortKeys = inputOptions.Undo.ToString(); _menuEditRedo.ShortKeys = inputOptions.Redo.ToString(); _menuEditCut.ShortKeys = inputOptions.Cut.ToString(); @@ -642,8 +664,21 @@ namespace FlaxEditor.Modules _menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString(); _menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString(); _menuEditFind.ShortKeys = inputOptions.Search.ToString(); - _menuGamePlayCurrentScenes.ShortKeys = inputOptions.Play.ToString(); + _menuGamePlayGame.ShortKeys = inputOptions.Play.ToString(); + _menuGamePlayCurrentScenes.ShortKeys = inputOptions.PlayCurrentScenes.ToString(); _menuGamePause.ShortKeys = inputOptions.Pause.ToString(); + _menuGameStop.ShortKeys = inputOptions.Play.ToString(); + _menuGameCookAndRun.ShortKeys = inputOptions.CookAndRun.ToString(); + _menuGameRunCookedGame.ShortKeys = inputOptions.RunCookedGame.ToString(); + _menuToolsBuildScenes.ShortKeys = inputOptions.BuildScenesData.ToString(); + _menuToolsBakeLightmaps.ShortKeys = inputOptions.BakeLightmaps.ToString(); + _menuToolsClearLightmaps.ShortKeys = inputOptions.ClearLightmaps.ToString(); + _menuToolsBakeAllEnvProbes.ShortKeys = inputOptions.BakeEnvProbes.ToString(); + _menuToolsBuildCSGMesh.ShortKeys = inputOptions.BuildCSG.ToString(); + _menuToolsBuildNavMesh.ShortKeys = inputOptions.BuildNav.ToString(); + _menuToolsBuildAllMeshesSDF.ShortKeys = inputOptions.BuildSDF.ToString(); + _menuToolsProfilerWindow.ShortKeys = inputOptions.ProfilerWindow.ToString(); + _menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString(); MainMenuShortcutKeysUpdated?.Invoke(); } @@ -668,10 +703,10 @@ namespace FlaxEditor.Modules 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)"); + _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 ({inputOptions.BuildScenesData})"); // Cook and run - _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 = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.Play})"); _toolStripCook.ContextMenu = new ContextMenu(); _toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked); _toolStripCook.ContextMenu.AddSeparator(); @@ -692,7 +727,7 @@ namespace FlaxEditor.Modules playActionGroup.SelectedChanged = SetPlayAction; Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; }; - _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game({inputOptions.Pause})"); + _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})"); _toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game"); UpdateToolstrip(); @@ -752,7 +787,7 @@ namespace FlaxEditor.Modules HorizontalAlignment = TextAlignment.Far, AnchorPreset = AnchorPresets.HorizontalStretchMiddle, Parent = progressPanel, - Offsets = new Margin(progressBarRightMargin, progressBarWidth + progressBarLeftMargin + progressBarRightMargin, 0, 0), + Offsets = new Margin(progressBarRightMargin, progressBarWidth + progressBarLeftMargin + progressBarRightMargin, 0, 0) }; UpdateStatusBar(); @@ -872,7 +907,7 @@ namespace FlaxEditor.Modules _menuToolsBakeLightmaps.Text = isBakingLightmaps ? "Cancel baking lightmaps" : "Bake lightmaps"; _menuToolsClearLightmaps.Enabled = canEdit; _menuToolsBakeAllEnvProbes.Enabled = canEdit; - _menuToolsBuildAllMesgesSDF.Enabled = canEdit && !isBakingLightmaps; + _menuToolsBuildAllMeshesSDF.Enabled = canEdit && !isBakingLightmaps; _menuToolsBuildCSGMesh.Enabled = canEdit; _menuToolsBuildNavMesh.Enabled = canEdit; _menuToolsCancelBuilding.Enabled = GameCooker.IsRunning; @@ -911,7 +946,7 @@ namespace FlaxEditor.Modules Editor.Windows.LoadLayout((string)button.Tag); } - private void AlignViewportWithActor() + internal void AlignViewportWithActor() { var selection = Editor.SceneEditing; if (selection.HasSthSelected && selection.Selection[0] is ActorNode node) @@ -922,7 +957,7 @@ namespace FlaxEditor.Modules } } - private void MoveActorToViewport() + internal void MoveActorToViewport() { var selection = Editor.SceneEditing; if (selection.HasSthSelected && selection.Selection[0] is ActorNode node) @@ -936,7 +971,7 @@ namespace FlaxEditor.Modules } } - private void AlignActorWithViewport() + internal void AlignActorWithViewport() { var selection = Editor.SceneEditing; if (selection.HasSthSelected && selection.Selection[0] is ActorNode node) @@ -972,57 +1007,6 @@ namespace FlaxEditor.Modules new Tools.Terrain.CreateTerrainDialog().Show(Editor.Windows.MainWindow); } - private void BakeAllEnvProbes() - { - Editor.Scene.ExecuteOnGraph(node => - { - if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive) - { - ((EnvironmentProbe)envProbeNode.Actor).Bake(); - node.ParentScene.IsEdited = true; - } - else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene) - { - skyLight.Bake(); - node.ParentScene.IsEdited = true; - } - - return node.IsActive; - }); - } - - private void BuildCSG() - { - var scenes = Level.Scenes; - scenes.ToList().ForEach(x => x.BuildCSG(0)); - Editor.Scene.MarkSceneEdited(scenes); - } - - private void BuildNavMesh() - { - var scenes = Level.Scenes; - scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0)); - Editor.Scene.MarkSceneEdited(scenes); - } - - private void BuildAllMeshesSDF() - { - // TODO: async maybe with progress reporting? - Editor.Scene.ExecuteOnGraph(node => - { - if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel) - { - if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null) - { - Editor.Log("Generating SDF for " + staticModel.Model); - if (!staticModel.Model.GenerateSDF()) - staticModel.Model.Save(); - } - } - return true; - }); - } - private void SetTheCurrentSceneViewAsDefault() { var projectInfo = Editor.GameProject; diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 61220f7d5..d0c8b9747 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -721,7 +721,7 @@ namespace FlaxEditor.Modules // Create main window var settings = CreateWindowSettings.Default; settings.Title = "Flax Editor"; - settings.Size = Platform.DesktopSize; + settings.Size = Platform.DesktopSize * 0.75f; settings.StartPosition = WindowStartPosition.CenterScreen; settings.ShowAfterFirstPaint = true; diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs index 2e18dab9e..eb61c0f68 100644 --- a/Source/Editor/Options/InputBinding.cs +++ b/Source/Editor/Options/InputBinding.cs @@ -134,6 +134,66 @@ namespace FlaxEditor.Options return false; } + private bool ProcessModifiers(Control control) + { + return ProcessModifiers(control.Root.GetKey); + } + + private bool ProcessModifiers(Window window) + { + return ProcessModifiers(window.GetKey); + } + + private bool ProcessModifiers(Func getKeyFunc) + { + bool ctrlPressed = getKeyFunc(KeyboardKeys.Control); + bool shiftPressed = getKeyFunc(KeyboardKeys.Shift); + bool altPressed = getKeyFunc(KeyboardKeys.Alt); + + bool mod1 = false; + if (Modifier1 == KeyboardKeys.None) + mod1 = true; + else if (Modifier1 == KeyboardKeys.Control) + { + mod1 = ctrlPressed; + ctrlPressed = false; + } + else if (Modifier1 == KeyboardKeys.Shift) + { + mod1 = shiftPressed; + shiftPressed = false; + } + else if (Modifier1 == KeyboardKeys.Alt) + { + mod1 = altPressed; + altPressed = false; + } + + bool mod2 = false; + if (Modifier2 == KeyboardKeys.None) + mod2 = true; + else if (Modifier2 == KeyboardKeys.Control) + { + mod2 = ctrlPressed; + ctrlPressed = false; + } + else if (Modifier2 == KeyboardKeys.Shift) + { + mod2 = shiftPressed; + shiftPressed = false; + } + else if (Modifier2 == KeyboardKeys.Alt) + { + mod2 = altPressed; + altPressed = false; + } + + // Check if any unhandled modifiers are not pressed + if (mod1 && mod2) + return !ctrlPressed && !shiftPressed && !altPressed; + return false; + } + /// /// Processes this input binding to check if state matches. /// @@ -142,19 +202,7 @@ namespace FlaxEditor.Options public bool Process(Control control) { var root = control.Root; - - if (root.GetKey(Key)) - { - if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1)) - { - if (Modifier2 == KeyboardKeys.None || root.GetKey(Modifier2)) - { - return true; - } - } - } - - return false; + return root.GetKey(Key) && ProcessModifiers(control); } /// @@ -165,20 +213,20 @@ namespace FlaxEditor.Options /// True if input has been processed, otherwise false. public bool Process(Control control, KeyboardKeys key) { - var root = control.Root; + if (key != Key) + return false; - if (key == Key) - { - if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1)) - { - if (Modifier2 == KeyboardKeys.None || root.GetKey(Modifier2)) - { - return true; - } - } - } + return ProcessModifiers(control); + } - return false; + /// + /// Processes this input binding to check if state matches. + /// + /// The input providing window. + /// True if input has been processed, otherwise false. + public bool Process(Window window) + { + return window.GetKey(Key) && ProcessModifiers(window); } /// diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs index e0a51622b..90d098bb6 100644 --- a/Source/Editor/Options/InputOptions.cs +++ b/Source/Editor/Options/InputOptions.cs @@ -78,6 +78,30 @@ namespace FlaxEditor.Options #endregion + #region File + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(300)] + public InputBinding SaveScenes = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(310)] + public InputBinding CloseScenes = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(320)] + public InputBinding OpenScriptsProject = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(330)] + public InputBinding GenerateScriptsProject = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("File"), EditorOrder(340)] + public InputBinding RecompileScripts = new InputBinding(KeyboardKeys.None); + + #endregion + #region Scene [DefaultValue(typeof(InputBinding), "End")] @@ -85,35 +109,115 @@ namespace FlaxEditor.Options public InputBinding SnapToGround = new InputBinding(KeyboardKeys.End); [DefaultValue(typeof(InputBinding), "F5")] - [EditorDisplay("Scene"), EditorOrder(510)] + [EditorDisplay("Scene", "Play/Stop"), EditorOrder(510)] public InputBinding Play = new InputBinding(KeyboardKeys.F5); + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Play Current Scenes/Stop"), EditorOrder(520)] + public InputBinding PlayCurrentScenes = new InputBinding(KeyboardKeys.None); + [DefaultValue(typeof(InputBinding), "F6")] - [EditorDisplay("Scene"), EditorOrder(520)] + [EditorDisplay("Scene"), EditorOrder(530)] public InputBinding Pause = new InputBinding(KeyboardKeys.F6); [DefaultValue(typeof(InputBinding), "F11")] - [EditorDisplay("Scene"), EditorOrder(530)] + [EditorDisplay("Scene"), EditorOrder(540)] public InputBinding StepFrame = new InputBinding(KeyboardKeys.F11); + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Cook & Run"), EditorOrder(550)] + public InputBinding CookAndRun = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Run cooked game"), EditorOrder(560)] + public InputBinding RunCookedGame = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Move actor to viewport"), EditorOrder(570)] + public InputBinding MoveActorToViewport = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Align actor with viewport"), EditorOrder(571)] + public InputBinding AlignActorWithViewport = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene", "Align viewport with actor"), EditorOrder(572)] + public InputBinding AlignViewportWithActor = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Scene"), EditorOrder(573)] + public InputBinding PilotActor = new InputBinding(KeyboardKeys.None); + + #endregion + + #region Tools + + [DefaultValue(typeof(InputBinding), "Ctrl+F10")] + [EditorDisplay("Tools", "Build scenes data"), EditorOrder(600)] + public InputBinding BuildScenesData = new InputBinding(KeyboardKeys.F10, KeyboardKeys.Control); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Bake lightmaps"), EditorOrder(601)] + public InputBinding BakeLightmaps = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Clear lightmaps data"), EditorOrder(602)] + public InputBinding ClearLightmaps = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Bake all env probes"), EditorOrder(603)] + public InputBinding BakeEnvProbes = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Build CSG mesh"), EditorOrder(604)] + public InputBinding BuildCSG = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Build Nav Mesh"), EditorOrder(605)] + public InputBinding BuildNav = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Tools", "Build all meshes SDF"), EditorOrder(606)] + public InputBinding BuildSDF = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "F12")] + [EditorDisplay("Tools", "Take screenshot"), EditorOrder(607)] + public InputBinding TakeScreenshot = new InputBinding(KeyboardKeys.F12); + + #endregion + + #region Profiler + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Profiler", "Open Profiler Window"), EditorOrder(630)] + public InputBinding ProfilerWindow = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Profiler", "Start/Stop Profiler"), EditorOrder(631)] + public InputBinding ProfilerStartStop = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Profiler", "Clear Profiler data"), EditorOrder(632)] + public InputBinding ProfilerClear = new InputBinding(KeyboardKeys.None); + #endregion #region Debugger [DefaultValue(typeof(InputBinding), "F5")] - [EditorDisplay("Debugger", "Continue"), EditorOrder(610)] + [EditorDisplay("Debugger", "Continue"), EditorOrder(810)] public InputBinding DebuggerContinue = new InputBinding(KeyboardKeys.F5); [DefaultValue(typeof(InputBinding), "F10")] - [EditorDisplay("Debugger", "Step Over"), EditorOrder(620)] + [EditorDisplay("Debugger", "Step Over"), EditorOrder(820)] public InputBinding DebuggerStepOver = new InputBinding(KeyboardKeys.F10); [DefaultValue(typeof(InputBinding), "F11")] - [EditorDisplay("Debugger", "Step Into"), EditorOrder(630)] + [EditorDisplay("Debugger", "Step Into"), EditorOrder(830)] public InputBinding DebuggerStepInto = new InputBinding(KeyboardKeys.F11); [DefaultValue(typeof(InputBinding), "Shift+F11")] - [EditorDisplay("Debugger", "Step Out"), EditorOrder(640)] + [EditorDisplay("Debugger", "Step Out"), EditorOrder(840)] public InputBinding DebuggerStepOut = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift); #endregion @@ -132,6 +236,10 @@ namespace FlaxEditor.Options [EditorDisplay("Gizmo"), EditorOrder(1020)] public InputBinding ScaleMode = new InputBinding(KeyboardKeys.Alpha3); + [DefaultValue(typeof(InputBinding), "Alpha4")] + [EditorDisplay("Gizmo"), EditorOrder(1030)] + public InputBinding ToggleTransformSpace = new InputBinding(KeyboardKeys.Alpha4); + #endregion #region Viewport @@ -160,28 +268,40 @@ namespace FlaxEditor.Options [EditorDisplay("Viewport"), EditorOrder(1550)] public InputBinding Down = new InputBinding(KeyboardKeys.Q); + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Viewport", "Toggle Camera Rotation"), EditorOrder(1560)] + public InputBinding CameraToggleRotation = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Viewport", "Increase Camera Move Speed"), EditorOrder(1570)] + public InputBinding CameraIncreaseMoveSpeed = new InputBinding(KeyboardKeys.None); + + [DefaultValue(typeof(InputBinding), "None")] + [EditorDisplay("Viewport", "Decrease Camera Move Speed"), EditorOrder(1571)] + public InputBinding CameraDecreaseMoveSpeed = new InputBinding(KeyboardKeys.None); + [DefaultValue(typeof(InputBinding), "Numpad0")] - [EditorDisplay("Viewport"), EditorOrder(1600)] + [EditorDisplay("Viewport"), EditorOrder(1700)] public InputBinding ViewpointFront = new InputBinding(KeyboardKeys.Numpad0); [DefaultValue(typeof(InputBinding), "Numpad5")] - [EditorDisplay("Viewport"), EditorOrder(1610)] + [EditorDisplay("Viewport"), EditorOrder(1710)] public InputBinding ViewpointBack = new InputBinding(KeyboardKeys.Numpad5); [DefaultValue(typeof(InputBinding), "Numpad4")] - [EditorDisplay("Viewport"), EditorOrder(1620)] + [EditorDisplay("Viewport"), EditorOrder(1720)] public InputBinding ViewpointLeft = new InputBinding(KeyboardKeys.Numpad4); [DefaultValue(typeof(InputBinding), "Numpad6")] - [EditorDisplay("Viewport"), EditorOrder(1630)] + [EditorDisplay("Viewport"), EditorOrder(1730)] public InputBinding ViewpointRight = new InputBinding(KeyboardKeys.Numpad6); [DefaultValue(typeof(InputBinding), "Numpad8")] - [EditorDisplay("Viewport"), EditorOrder(1640)] + [EditorDisplay("Viewport"), EditorOrder(1740)] public InputBinding ViewpointTop = new InputBinding(KeyboardKeys.Numpad8); [DefaultValue(typeof(InputBinding), "Numpad2")] - [EditorDisplay("Viewport"), EditorOrder(1650)] + [EditorDisplay("Viewport"), EditorOrder(1750)] public InputBinding ViewpointBottom = new InputBinding(KeyboardKeys.Numpad2); #endregion diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 68aa11626..c2d744239 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -244,6 +244,13 @@ namespace FlaxEditor.Options CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), ProgressNormal = Color.FromBgra(0xFF0ad328), + Statusbar = new Style.StatusbarStyle() + { + PlayMode = Color.FromBgra(0xFF2F9135), + Failed = Color.FromBgra(0xFF9C2424), + Loading = Color.FromBgra(0xFF2D2D30) + }, + // Fonts FontTitle = options.Interface.TitleFont.GetFont(), FontLarge = options.Interface.LargeFont.GetFont(), diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp index 05608d71e..7f5ca6f17 100644 --- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp @@ -268,8 +268,16 @@ void RiderCodeEditor::OpenFile(const String& path, int32 line) // Open file line = line > 0 ? line : 1; CreateProcessSettings procSettings; + +#if !PLATFORM_MAC procSettings.FileName = _execPath; procSettings.Arguments = String::Format(TEXT("\"{0}\" --line {2} \"{1}\""), _solutionPath, path, line); +#else + // This follows pretty much how all the other engines open rider which deals with cross architecture issues + procSettings.FileName = "/usr/bin/open"; + procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" --args \"{1}\" --line {3} \"{2}\""), _execPath, _solutionPath, path, line); +#endif + procSettings.HiddenWindow = false; procSettings.WaitForEnd = false; procSettings.LogOutput = false; @@ -287,8 +295,14 @@ void RiderCodeEditor::OpenSolution() // Open solution CreateProcessSettings procSettings; +#if !PLATFORM_MAC procSettings.FileName = _execPath; procSettings.Arguments = String::Format(TEXT("\"{0}\""), _solutionPath); +#else + // This follows pretty much how all the other engines open rider which deals with cross architecture issues + procSettings.FileName = "/usr/bin/open"; + procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" \"{1}\""), _execPath, _solutionPath); +#endif procSettings.HiddenWindow = false; procSettings.WaitForEnd = false; procSettings.LogOutput = false; diff --git a/Source/Editor/Surface/Elements/OutputBox.cs b/Source/Editor/Surface/Elements/OutputBox.cs index 67b6e8409..cf78b559c 100644 --- a/Source/Editor/Surface/Elements/OutputBox.cs +++ b/Source/Editor/Surface/Elements/OutputBox.cs @@ -34,10 +34,8 @@ namespace FlaxEditor.Surface.Elements public static void DrawConnection(ref Float2 start, ref Float2 end, ref Color color, float thickness = 1) { // Calculate control points - var dst = (end - start) * new Float2(0.5f, 0.05f); - var control1 = new Float2(start.X + dst.X, start.Y + dst.Y); - var control2 = new Float2(end.X - dst.X, end.Y + dst.Y); - + CalculateBezierControlPoints(start, end, out var control1, out var control2); + // Draw line Render2D.DrawBezier(start, control1, control2, end, color, thickness); @@ -49,6 +47,23 @@ namespace FlaxEditor.Surface.Elements */ } + private static void CalculateBezierControlPoints(Float2 start, Float2 end, out Float2 control1, out Float2 control2) + { + // Control points parameters + const float minControlLength = 100f; + const float maxControlLength = 150f; + var dst = (end - start).Length; + var yDst = Mathf.Abs(start.Y - end.Y); + + // Calculate control points + var minControlDst = dst * 0.5f; + var maxControlDst = Mathf.Max(Mathf.Min(maxControlLength, dst), minControlLength); + var controlDst = Mathf.Lerp(minControlDst, maxControlDst, Mathf.Clamp(yDst / minControlLength, 0f, 1f)); + + control1 = new Float2(start.X + controlDst, start.Y); + control2 = new Float2(end.X - controlDst, end.Y); + } + /// /// Checks if a point intersects a connection /// @@ -75,13 +90,9 @@ namespace FlaxEditor.Surface.Elements float offset = Mathf.Sign(end.Y - start.Y) * distance; if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0) return false; - - // Taken from the Render2D.DrawBezier code + float squaredDistance = distance; - - var dst = (end - start) * new Float2(0.5f, 0.05f); - var control1 = new Float2(start.X + dst.X, start.Y + dst.Y); - var control2 = new Float2(end.X - dst.X, end.Y + dst.Y); + CalculateBezierControlPoints(start, end, out var control1, out var control2); var d1 = control1 - start; var d2 = control2 - control1; diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 7264321c3..5e30ff110 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -213,6 +213,23 @@ namespace FlaxEditor.Surface return; } + if (_middleMouseDown) + { + // Calculate delta + var delta = location - _middleMouseDownPos; + if (delta.LengthSquared > 0.01f) + { + // Move view + _mouseMoveAmount += delta.Length; + _rootControl.Location += delta; + _middleMouseDownPos = location; + Cursor = CursorType.SizeAll; + } + + // Handled + return; + } + // Check if user is selecting or moving node(s) if (_leftMouseDown) { @@ -269,6 +286,11 @@ namespace FlaxEditor.Surface _rightMouseDown = false; Cursor = CursorType.Default; } + if (_middleMouseDown) + { + _middleMouseDown = false; + Cursor = CursorType.Default; + } _isMovingSelection = false; ConnectingEnd(null); @@ -291,7 +313,7 @@ namespace FlaxEditor.Surface if (IsMouseOver && !_leftMouseDown && !IsPrimaryMenuOpened) { var nextViewScale = ViewScale + delta * 0.1f; - + if (delta > 0 && !_rightMouseDown) { // Scale towards mouse when zooming in @@ -306,7 +328,7 @@ namespace FlaxEditor.Surface ViewScale = nextViewScale; ViewCenterPosition = viewCenter; } - + return true; } @@ -380,6 +402,7 @@ namespace FlaxEditor.Surface _isMovingSelection = false; _rightMouseDown = false; _leftMouseDown = false; + _middleMouseDown = false; return true; } @@ -399,6 +422,11 @@ namespace FlaxEditor.Surface _rightMouseDown = true; _rightMouseDownPos = location; } + if (button == MouseButton.Middle) + { + _middleMouseDown = true; + _middleMouseDownPos = location; + } // Check if any node is under the mouse SurfaceControl controlUnderMouse = GetControlUnderMouse(); @@ -444,7 +472,7 @@ namespace FlaxEditor.Surface Focus(); return true; } - if (_rightMouseDown) + if (_rightMouseDown || _middleMouseDown) { // Start navigating StartMouseCapture(); @@ -513,6 +541,13 @@ namespace FlaxEditor.Surface } _mouseMoveAmount = 0; } + if (_middleMouseDown && button == MouseButton.Middle) + { + _middleMouseDown = false; + EndMouseCapture(); + Cursor = CursorType.Default; + _mouseMoveAmount = 0; + } // Base bool handled = base.OnMouseUp(location, button); @@ -523,6 +558,7 @@ namespace FlaxEditor.Surface // Clear flags _rightMouseDown = false; _leftMouseDown = false; + _middleMouseDown = false; return true; } @@ -706,6 +742,8 @@ namespace FlaxEditor.Surface { if (_inputBrackets.Count == 0) { + if (currentInputText.StartsWith(' ')) + currentInputText = ""; ResetInput(); ShowPrimaryMenu(_mousePos, false, currentInputText); } diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 22aba4855..6aed7cf68 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -59,6 +59,11 @@ namespace FlaxEditor.Surface /// protected bool _rightMouseDown; + /// + /// The middle mouse down flag. + /// + protected bool _middleMouseDown; + /// /// The left mouse down position. /// @@ -69,6 +74,11 @@ namespace FlaxEditor.Surface /// protected Float2 _rightMouseDownPos = Float2.Minimum; + /// + /// The middle mouse down position. + /// + protected Float2 _middleMouseDownPos = Float2.Minimum; + /// /// The mouse position. /// @@ -902,7 +912,7 @@ namespace FlaxEditor.Surface { return _context.FindNode(id); } - + /// /// Adds the undo action to be batched (eg. if multiple undo actions is performed in a sequence during single update). /// diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index 814376c75..10358a8c0 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -137,14 +137,23 @@ namespace FlaxEditor.Tools.Foliage Offsets = Margin.Zero, Parent = _noFoliagePanel }; + + var buttonText = "Create new foliage"; _createNewFoliage = new Button { - Text = "Create new foliage", + Text = buttonText, AnchorPreset = AnchorPresets.MiddleCenter, Offsets = new Margin(-60, 120, -12, 24), Parent = _noFoliagePanel, Enabled = false }; + var textSize = Style.Current.FontMedium.MeasureText(buttonText); + if (_createNewFoliage.Width < textSize.X) + { + _createNewFoliage.LocalX -= (textSize.X - _createNewFoliage.Width) / 2; + _createNewFoliage.Width = textSize.X + 6; + } + _createNewFoliage.Clicked += OnCreateNewFoliageClicked; } diff --git a/Source/Editor/Tools/Foliage/FoliageTypesTab.cs b/Source/Editor/Tools/Foliage/FoliageTypesTab.cs index 2a51d972c..8934f5058 100644 --- a/Source/Editor/Tools/Foliage/FoliageTypesTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTypesTab.cs @@ -375,7 +375,7 @@ namespace FlaxEditor.Tools.Foliage private void OnModified() { - Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage?.Scene); + Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage != null ? _proxy.Foliage.Scene : null); } private void OnSelectedFoliageChanged() diff --git a/Source/Editor/Tools/Foliage/PaintTab.cs b/Source/Editor/Tools/Foliage/PaintTab.cs index e4f01661b..ba31ca18b 100644 --- a/Source/Editor/Tools/Foliage/PaintTab.cs +++ b/Source/Editor/Tools/Foliage/PaintTab.cs @@ -218,7 +218,7 @@ namespace FlaxEditor.Tools.Foliage private void OnModified() { - Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage?.Scene); + Editor.Instance.Scene.MarkSceneEdited(_proxy.Foliage != null ? _proxy.Foliage.Scene : null); } private void OnSelectedFoliageChanged() diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index 6bee0bd92..000f90817 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -95,14 +95,23 @@ namespace FlaxEditor.Tools.Terrain Offsets = Margin.Zero, Parent = _noTerrainPanel }; + + var buttonText = "Create new terrain"; _createTerrainButton = new Button { - Text = "Create new terrain", + Text = buttonText, AnchorPreset = AnchorPresets.MiddleCenter, Offsets = new Margin(-60, 120, -12, 24), Parent = _noTerrainPanel, Enabled = false }; + var textSize = Style.Current.FontMedium.MeasureText(buttonText); + if (_createTerrainButton.Width < textSize.X) + { + _createTerrainButton.LocalX -= (textSize.X - _createTerrainButton.Width) / 2; + _createTerrainButton.Width = textSize.X + 6; + } + _createTerrainButton.Clicked += OnCreateNewTerrainClicked; } diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index 0863569c2..6934a754b 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -544,7 +544,7 @@ namespace FlaxEditor.Tools public override bool IsControllingMouse => IsPainting; /// - public override BoundingSphere FocusBounds => _selectedModel?.Sphere ?? base.FocusBounds; + public override BoundingSphere FocusBounds => _selectedModel != null ? _selectedModel.Sphere : base.FocusBounds; /// public override void Update(float dt) diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs index 8b0d040d8..7aee40878 100644 --- a/Source/Editor/Undo/Actions/PasteActorsAction.cs +++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Actions { var node = nodeParents[i]; var actor = node.Actor; - var parent = actor?.Parent; + var parent = actor != null ? actor.Parent : null; if (parent != null) { bool IsNameValid(string name) diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index fe801bbaf..863bdbedb 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -18,10 +18,10 @@ using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Tree; using FlaxEditor.SceneGraph; -using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; +using FlaxEditor.Windows; namespace FlaxEngine { @@ -1235,5 +1235,60 @@ namespace FlaxEditor.Utilities } return s; } + + /// + /// Binds global input actions for the window. + /// + /// The editor window. + public static void SetupCommonInputActions(EditorWindow window) + { + var inputActions = window.InputActions; + + // Setup input actions + inputActions.Add(options => options.Save, Editor.Instance.SaveAll); + inputActions.Add(options => options.Undo, () => + { + Editor.Instance.PerformUndo(); + window.Focus(); + }); + inputActions.Add(options => options.Redo, () => + { + Editor.Instance.PerformRedo(); + window.Focus(); + }); + inputActions.Add(options => options.Cut, Editor.Instance.SceneEditing.Cut); + inputActions.Add(options => options.Copy, Editor.Instance.SceneEditing.Copy); + inputActions.Add(options => options.Paste, Editor.Instance.SceneEditing.Paste); + inputActions.Add(options => options.Duplicate, Editor.Instance.SceneEditing.Duplicate); + inputActions.Add(options => options.SelectAll, Editor.Instance.SceneEditing.SelectAllScenes); + inputActions.Add(options => options.Delete, Editor.Instance.SceneEditing.Delete); + inputActions.Add(options => options.Search, () => Editor.Instance.Windows.SceneWin.Search()); + inputActions.Add(options => options.MoveActorToViewport, Editor.Instance.UI.MoveActorToViewport); + inputActions.Add(options => options.AlignActorWithViewport, Editor.Instance.UI.AlignActorWithViewport); + inputActions.Add(options => options.AlignViewportWithActor, Editor.Instance.UI.AlignViewportWithActor); + inputActions.Add(options => options.PilotActor, Editor.Instance.UI.PilotActor); + inputActions.Add(options => options.Play, Editor.Instance.Simulation.DelegatePlayOrStopPlayInEditor); + inputActions.Add(options => options.PlayCurrentScenes, Editor.Instance.Simulation.RequestPlayScenesOrStopPlay); + inputActions.Add(options => options.Pause, Editor.Instance.Simulation.RequestResumeOrPause); + inputActions.Add(options => options.StepFrame, Editor.Instance.Simulation.RequestPlayOneFrame); + inputActions.Add(options => options.CookAndRun, () => Editor.Instance.Windows.GameCookerWin.BuildAndRun()); + inputActions.Add(options => options.RunCookedGame, () => Editor.Instance.Windows.GameCookerWin.RunCooked()); + inputActions.Add(options => options.BuildScenesData, Editor.Instance.BuildScenesOrCancel); + inputActions.Add(options => options.BakeLightmaps, Editor.Instance.BakeLightmapsOrCancel); + inputActions.Add(options => options.ClearLightmaps, Editor.Instance.ClearLightmaps); + inputActions.Add(options => options.BakeEnvProbes, Editor.Instance.BakeAllEnvProbes); + inputActions.Add(options => options.BuildCSG, Editor.Instance.BuildCSG); + inputActions.Add(options => options.BuildNav, Editor.Instance.BuildNavMesh); + inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF); + inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot); + inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow()); + inputActions.Add(options => options.ProfilerStartStop, () => { Editor.Instance.Windows.ProfilerWin.LiveRecording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; Editor.Instance.UI.AddStatusMessage($"Profiling {(Editor.Instance.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); }); + inputActions.Add(options => options.ProfilerClear, () => { Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); }); + inputActions.Add(options => options.SaveScenes, () => Editor.Instance.Scene.SaveScenes()); + inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes()); + inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution()); + inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync()); + inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile); + } } } diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index ad7e06ba5..8c71a8b02 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -137,7 +137,7 @@ namespace FlaxEditor.Viewport // Input - private bool _isControllingMouse, _isViewportControllingMouse; + private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown; private int _deltaFilteringStep; private Float2 _startPos; private Float2 _mouseDeltaLast; @@ -441,6 +441,9 @@ namespace FlaxEditor.Viewport if (useWidgets) { + var largestText = "Invert Panning"; + var textSize = Style.Current.FontMedium.MeasureText(largestText); + var xLocationForExtras = textSize.X + 5; // Camera speed widget var camSpeed = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); var camSpeedCM = new ContextMenu(); @@ -541,7 +544,7 @@ namespace FlaxEditor.Viewport { var ortho = ViewWidgetButtonMenu.AddButton("Orthographic"); ortho.CloseMenuOnClick = false; - var orthoValue = new CheckBox(90, 2, _isOrtho) + var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho) { Parent = ortho }; @@ -581,7 +584,7 @@ namespace FlaxEditor.Viewport { var fov = ViewWidgetButtonMenu.AddButton("Field Of View"); fov.CloseMenuOnClick = false; - var fovValue = new FloatValueBox(1, 90, 2, 70.0f, 35.0f, 160.0f, 0.1f) + var fovValue = new FloatValueBox(1, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f) { Parent = fov }; @@ -598,7 +601,7 @@ namespace FlaxEditor.Viewport { var orthoSize = ViewWidgetButtonMenu.AddButton("Ortho Scale"); orthoSize.CloseMenuOnClick = false; - var orthoSizeValue = new FloatValueBox(_orthoSize, 90, 2, 70.0f, 0.001f, 100000.0f, 0.01f) + var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f) { Parent = orthoSize }; @@ -615,7 +618,7 @@ namespace FlaxEditor.Viewport { var nearPlane = ViewWidgetButtonMenu.AddButton("Near Plane"); nearPlane.CloseMenuOnClick = false; - var nearPlaneValue = new FloatValueBox(2.0f, 90, 2, 70.0f, 0.001f, 1000.0f) + var nearPlaneValue = new FloatValueBox(2.0f, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f) { Parent = nearPlane }; @@ -627,7 +630,7 @@ namespace FlaxEditor.Viewport { var farPlane = ViewWidgetButtonMenu.AddButton("Far Plane"); farPlane.CloseMenuOnClick = false; - var farPlaneValue = new FloatValueBox(1000, 90, 2, 70.0f, 10.0f) + var farPlaneValue = new FloatValueBox(1000, xLocationForExtras, 2, 70.0f, 10.0f) { Parent = farPlane }; @@ -639,7 +642,7 @@ namespace FlaxEditor.Viewport { var brightness = ViewWidgetButtonMenu.AddButton("Brightness"); brightness.CloseMenuOnClick = false; - var brightnessValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.001f, 10.0f, 0.001f) + var brightnessValue = new FloatValueBox(1.0f, xLocationForExtras, 2, 70.0f, 0.001f, 10.0f, 0.001f) { Parent = brightness }; @@ -651,7 +654,7 @@ namespace FlaxEditor.Viewport { var resolution = ViewWidgetButtonMenu.AddButton("Resolution"); resolution.CloseMenuOnClick = false; - var resolutionValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.1f, 4.0f, 0.001f) + var resolutionValue = new FloatValueBox(1.0f, xLocationForExtras, 2, 70.0f, 0.1f, 4.0f, 0.001f) { Parent = resolution }; @@ -663,7 +666,7 @@ namespace FlaxEditor.Viewport { var invert = ViewWidgetButtonMenu.AddButton("Invert Panning"); invert.CloseMenuOnClick = false; - var invertValue = new CheckBox(90, 2, _invertPanning) + var invertValue = new CheckBox(xLocationForExtras, 2, _invertPanning) { Parent = invert }; @@ -685,6 +688,9 @@ namespace FlaxEditor.Viewport InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation))); InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); + InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown); + InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1)); + InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1)); // Link for task event task.Begin += OnRenderBegin; @@ -722,6 +728,30 @@ namespace FlaxEditor.Viewport } } + /// + /// Increases or decreases the camera movement speed. + /// + /// The stepping direction for speed adjustment. + protected void AdjustCameraMoveSpeed(int step) + { + int camValueIndex = -1; + for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++) + { + if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed)) + { + camValueIndex = i; + break; + } + } + if (camValueIndex == -1) + return; + + if (step > 0) + MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)]; + else if (step < 0) + MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)]; + } + private void OnEditorOptionsChanged(EditorOptions options) { _mouseSensitivity = options.Viewport.MouseSensitivity; @@ -1048,6 +1078,15 @@ namespace FlaxEditor.Viewport // Track controlling mouse state change bool wasControllingMouse = _prevInput.IsControllingMouse; _isControllingMouse = _input.IsControllingMouse; + + // Simulate holding mouse right down for trackpad users + if ((_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) || win.GetKeyDown(KeyboardKeys.Escape)) + _isVirtualMouseRightDown = false; // Cancel when mouse right or escape is pressed + if (_wasVirtualMouseRightDown) + wasControllingMouse = true; + if (_isVirtualMouseRightDown) + _isControllingMouse = _isVirtualMouseRightDown; + if (wasControllingMouse != _isControllingMouse) { if (_isControllingMouse) @@ -1061,16 +1100,18 @@ namespace FlaxEditor.Viewport OnLeftMouseButtonDown(); else if (_prevInput.IsMouseLeftDown && !_input.IsMouseLeftDown) OnLeftMouseButtonUp(); - // - if (!_prevInput.IsMouseRightDown && _input.IsMouseRightDown) + + if ((!_prevInput.IsMouseRightDown && _input.IsMouseRightDown) || (!_wasVirtualMouseRightDown && _isVirtualMouseRightDown)) OnRightMouseButtonDown(); - else if (_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) + else if ((_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) || (_wasVirtualMouseRightDown && !_isVirtualMouseRightDown)) OnRightMouseButtonUp(); - // + if (!_prevInput.IsMouseMiddleDown && _input.IsMouseMiddleDown) OnMiddleMouseButtonDown(); else if (_prevInput.IsMouseMiddleDown && !_input.IsMouseMiddleDown) OnMiddleMouseButtonUp(); + + _wasVirtualMouseRightDown = _isVirtualMouseRightDown; } // Get clamped delta time (more stable during lags) @@ -1088,7 +1129,7 @@ namespace FlaxEditor.Viewport bool isAltDown = _input.IsAltDown; bool lbDown = _input.IsMouseLeftDown; bool mbDown = _input.IsMouseMiddleDown; - bool rbDown = _input.IsMouseRightDown; + bool rbDown = _input.IsMouseRightDown || _isVirtualMouseRightDown; bool wheelInUse = Math.Abs(_input.MouseWheelDelta) > Mathf.Epsilon; _input.IsPanning = !isAltDown && mbDown && !rbDown; @@ -1098,32 +1139,20 @@ namespace FlaxEditor.Viewport _input.IsOrbiting = isAltDown && lbDown && !mbDown && !rbDown; // Control move speed with RMB+Wheel - rmbWheel = useMovementSpeed && _input.IsMouseRightDown && wheelInUse; + rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse; if (rmbWheel) { - float step = 4.0f; + const float step = 4.0f; _wheelMovementChangeDeltaSum += _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity; - int camValueIndex = -1; - for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++) + if (_wheelMovementChangeDeltaSum >= step) { - if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed)) - { - camValueIndex = i; - break; - } + _wheelMovementChangeDeltaSum -= step; + AdjustCameraMoveSpeed(1); } - if (camValueIndex != -1) + else if (_wheelMovementChangeDeltaSum <= -step) { - if (_wheelMovementChangeDeltaSum >= step) - { - _wheelMovementChangeDeltaSum -= step; - MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)]; - } - else if (_wheelMovementChangeDeltaSum <= -step) - { - _wheelMovementChangeDeltaSum += step; - MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)]; - } + _wheelMovementChangeDeltaSum += step; + AdjustCameraMoveSpeed(-1); } } } @@ -1165,7 +1194,7 @@ namespace FlaxEditor.Viewport // Calculate smooth mouse delta not dependant on viewport size var offset = _viewMousePos - _startPos; - if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel) + if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel && !_isVirtualMouseRightDown) { offset = Float2.Zero; } @@ -1213,7 +1242,7 @@ namespace FlaxEditor.Viewport UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse); // Move mouse back to the root position - if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown)) + if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown || _isVirtualMouseRightDown)) { var center = PointToWindow(_startPos); win.MousePosition = center; @@ -1229,7 +1258,7 @@ namespace FlaxEditor.Viewport } else { - if (_input.IsMouseLeftDown || _input.IsMouseRightDown) + if (_input.IsMouseLeftDown || _input.IsMouseRightDown || _isVirtualMouseRightDown) { // Calculate smooth mouse delta not dependant on viewport size var offset = _viewMousePos - _startPos; @@ -1359,6 +1388,7 @@ namespace FlaxEditor.Viewport { OnControlMouseEnd(RootWindow.Window); _isControllingMouse = false; + _isVirtualMouseRightDown = false; } } diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 9cbfce562..6c52ce46b 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -194,6 +194,7 @@ namespace FlaxEditor.Viewport { _editor = editor; _dragAssets = new DragAssets(ValidateDragItem); + var inputOptions = editor.Options.Options.Input; // Prepare rendering task Task.ActorsSource = ActorsSources.Scenes; @@ -250,7 +251,7 @@ namespace FlaxEditor.Viewport var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true) { Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, - TooltipText = "Gizmo transform space (world or local)", + TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", Parent = transformSpaceWidget }; transformSpaceToggle.Toggled += OnTransformSpaceToggle; @@ -347,7 +348,7 @@ namespace FlaxEditor.Viewport _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true) { Tag = TransformGizmoBase.Mode.Translate, - TooltipText = "Translate gizmo mode", + TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", Checked = true, Parent = gizmoMode }; @@ -355,14 +356,14 @@ namespace FlaxEditor.Viewport _gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true) { Tag = TransformGizmoBase.Mode.Rotate, - TooltipText = "Rotate gizmo mode", + TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", Parent = gizmoMode }; _gizmoModeRotate.Toggled += OnGizmoModeToggle; _gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true) { Tag = TransformGizmoBase.Mode.Scale, - TooltipText = "Scale gizmo mode", + TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", Parent = gizmoMode }; _gizmoModeScale.Toggled += OnGizmoModeToggle; @@ -390,6 +391,7 @@ 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.ToggleTransformSpace, () => { OnTransformSpaceToggle(transformSpaceToggle); transformSpaceToggle.Checked = !transformSpaceToggle.Checked; }); InputActions.Add(options => options.LockFocusSelection, LockFocusSelection); InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.RotateSelection, RotateSelection); diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index ec6a983ce..c02d6b6e8 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -84,6 +84,7 @@ namespace FlaxEditor.Viewport _dragAssets = new DragAssets(ValidateDragItem); ShowDebugDraw = true; ShowEditorPrimitives = true; + var inputOptions = window.Editor.Options.Options.Input; // Prepare rendering task Task.ActorsSource = ActorsSources.CustomActors; @@ -113,7 +114,7 @@ namespace FlaxEditor.Viewport var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true) { Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, - TooltipText = "Gizmo transform space (world or local)", + TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", Parent = transformSpaceWidget }; transformSpaceToggle.Toggled += OnTransformSpaceToggle; @@ -205,7 +206,7 @@ namespace FlaxEditor.Viewport _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true) { Tag = TransformGizmoBase.Mode.Translate, - TooltipText = "Translate gizmo mode", + TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", Checked = true, Parent = gizmoMode }; @@ -213,14 +214,14 @@ namespace FlaxEditor.Viewport _gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true) { Tag = TransformGizmoBase.Mode.Rotate, - TooltipText = "Rotate gizmo mode", + TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", Parent = gizmoMode }; _gizmoModeRotate.Toggled += OnGizmoModeToggle; _gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true) { Tag = TransformGizmoBase.Mode.Scale, - TooltipText = "Scale gizmo mode", + TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", Parent = gizmoMode }; _gizmoModeScale.Toggled += OnGizmoModeToggle; @@ -233,6 +234,7 @@ 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.ToggleTransformSpace, () => { OnTransformSpaceToggle(transformSpaceToggle); transformSpaceToggle.Checked = !transformSpaceToggle.Checked; }); InputActions.Add(options => options.FocusSelection, ShowSelectedActors); SetUpdate(ref _update, OnUpdate); diff --git a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs index fc7fcdbe1..c6ce5a7b5 100644 --- a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs +++ b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs @@ -68,7 +68,7 @@ namespace FlaxEditor.Viewport.Previews /// public bool ShowBounds { - get => _boundsModel?.IsActive ?? false; + get => _boundsModel != null ? _boundsModel.IsActive : false; set { if (value == ShowBounds) @@ -110,7 +110,7 @@ namespace FlaxEditor.Viewport.Previews /// public bool ShowOrigin { - get => _originModel?.IsActive ?? false; + get => _originModel != null ? _originModel.IsActive : false; set { if (value == ShowOrigin) diff --git a/Source/Editor/Viewport/Previews/TexturePreview.cs b/Source/Editor/Viewport/Previews/TexturePreview.cs index a33a61f86..ec10bb019 100644 --- a/Source/Editor/Viewport/Previews/TexturePreview.cs +++ b/Source/Editor/Viewport/Previews/TexturePreview.cs @@ -500,7 +500,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// @@ -549,7 +549,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// @@ -604,7 +604,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// @@ -659,7 +659,7 @@ namespace FlaxEditor.Viewport.Previews /// protected override void CalculateTextureRect(out Rectangle rect) { - CalculateTextureRect(_asset?.Size ?? new Float2(100), Size, out rect); + CalculateTextureRect(_asset != null ? _asset.Size : new Float2(100), Size, out rect); } /// diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 4b9595445..b059dadcb 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -52,9 +52,11 @@ namespace FlaxEditor.Windows VerticalAlignment = TextAlignment.Near, Parent = this }; - var copyVersionButton = new Button(Width - 104, 6, 100, 20) + var buttonText = "Copy version info"; + var fontSize = Style.Current.FontMedium.MeasureText(buttonText); + var copyVersionButton = new Button(Width - fontSize.X - 8, 6, fontSize.X + 4, 20) { - Text = "Copy version info", + Text = buttonText, TooltipText = "Copies the current engine version information to system clipboard.", Parent = this }; diff --git a/Source/Editor/Windows/Assets/CollisionDataWindow.cs b/Source/Editor/Windows/Assets/CollisionDataWindow.cs index cc1daa45b..1b6ed914c 100644 --- a/Source/Editor/Windows/Assets/CollisionDataWindow.cs +++ b/Source/Editor/Windows/Assets/CollisionDataWindow.cs @@ -200,6 +200,7 @@ namespace FlaxEditor.Windows.Assets ViewportCamera = new FPSCamera(), Parent = _split.Panel1 }; + _preview.Task.ViewFlags &= ~ViewFlags.Sky & ~ViewFlags.Bloom; // Asset properties _propertiesPresenter = new CustomEditorPresenter(null); diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs index d5ab2ae8a..5f1273999 100644 --- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs @@ -69,7 +69,7 @@ namespace FlaxEditor.Windows.Assets [EditorDisplay("General"), Tooltip("The base material used to override it's properties")] public MaterialBase BaseMaterial { - get => Window?.Asset?.BaseMaterial; + get => Window?.Asset != null ? Window?.Asset.BaseMaterial : null; set { var asset = Window?.Asset; @@ -101,10 +101,12 @@ namespace FlaxEditor.Windows.Assets [HideInEditor] public object[] Values { - get => Window?.Asset?.Parameters.Select(x => x.Value).ToArray(); + get => Window?.Asset != null ? Window?.Asset.Parameters.Select(x => x.Value).ToArray() : null; set { - var parameters = Window?.Asset?.Parameters; + if (Window?.Asset == null) + return; + var parameters = Window?.Asset.Parameters; if (value != null && parameters != null) { if (value.Length != parameters.Length) @@ -131,9 +133,11 @@ namespace FlaxEditor.Windows.Assets [HideInEditor] public FlaxEngine.Object[] ValuesRef { - get => Window?.Asset?.Parameters.Select(x => x.Value as FlaxEngine.Object).ToArray(); + get => Window?.Asset != null ? Window?.Asset.Parameters.Select(x => x.Value as FlaxEngine.Object).ToArray() : null; set { + if (Window?.Asset == null) + return; var parameters = Window?.Asset?.Parameters; if (value != null && parameters != null) { @@ -293,7 +297,7 @@ namespace FlaxEditor.Windows.Assets var p = (MaterialParameter)e.Tag; // Try to get default value (from the base material) - var pBase = baseMaterial?.GetParameter(p.Name); + var pBase = baseMaterial != null ? baseMaterial.GetParameter(p.Name) : null; if (pBase != null && pBase.ParameterType == p.ParameterType) { valueContainer.SetDefaultValue(pBase.Value); diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index f70f6a3b5..c2764a5a6 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -134,7 +134,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._isolateIndex = mesh?.MaterialSlotIndex ?? -1; + Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._highlightIndex = mesh?.MaterialSlotIndex ?? -1; + Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -326,7 +326,7 @@ namespace FlaxEditor.Windows.Assets [EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)] public MaterialSlot[] MaterialSlots { - get => Asset?.MaterialSlots; + get => Asset != null ? Asset.MaterialSlots : null; set { if (Asset != null) diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs index 328369680..17eda1358 100644 --- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs @@ -187,7 +187,7 @@ namespace FlaxEditor.Windows.Assets base.Initialize(layout); var emitterTrack = Values[0] as EmitterTrackProxy; - if (emitterTrack?._effect?.Parameters == null) + if (emitterTrack?._effect == null || emitterTrack?._effect.Parameters == null) return; var group = layout.Group("Parameters"); diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs index 824928743..162944144 100644 --- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs +++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs @@ -792,7 +792,7 @@ namespace FlaxEditor.Windows.Assets { if (_previewButton.Checked) return; - _previewPlayerPicker.Value = _timeline.Player; + _previewPlayerPicker.Value = _timeline.Player != null ? _timeline.Player : null; _cachedPlayerId = _timeline.Player?.ID ?? Guid.Empty; } @@ -821,7 +821,7 @@ namespace FlaxEditor.Windows.Assets if (_timeline.IsModified) { var time = _timeline.CurrentTime; - var isPlaying = _previewPlayer?.IsPlaying ?? false; + var isPlaying = _previewPlayer != null ? _previewPlayer.IsPlaying : false; _timeline.Save(_asset); if (_previewButton.Checked && _previewPlayer != null) { diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index a7ee6e767..d360e5570 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -151,7 +151,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._isolateIndex = mesh?.MaterialSlotIndex ?? -1; + Window._isolateIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -165,7 +165,7 @@ namespace FlaxEditor.Windows.Assets if (Window._skipEffectsGuiEvents) return; - Window._highlightIndex = mesh?.MaterialSlotIndex ?? -1; + Window._highlightIndex = mesh != null ? mesh.MaterialSlotIndex : -1; Window.UpdateEffectsOnAsset(); UpdateEffectsOnUI(); } @@ -415,7 +415,7 @@ namespace FlaxEditor.Windows.Assets [EditorOrder(10), EditorDisplay("Materials", EditorDisplayAttribute.InlineStyle)] public MaterialSlot[] MaterialSlots { - get => Asset?.MaterialSlots; + get => Asset != null ? Asset.MaterialSlots : null; set { if (Asset != null) diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index 283133688..6923d634d 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -145,10 +145,9 @@ namespace FlaxEditor.Windows cm.AddButton("Refresh all thumbnails", RefreshViewItemsThumbnails); } - cm.AddSeparator(); - - if (!isRootFolder) + if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode)) { + cm.AddSeparator(); cm.AddButton("New folder", NewFolder); } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 2628735eb..c25316e9a 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -46,7 +46,7 @@ namespace FlaxEditor.Windows private TextBox _itemsSearchBox; private ViewDropdown _viewDropdown; private SortType _sortType; - private bool _showEngineFiles = true, _showPluginsFiles = true, _showAllFiles = true; + private bool _showEngineFiles = true, _showPluginsFiles = true, _showAllFiles = true, _showGeneratedFiles = false; private RootContentTreeNode _root; @@ -106,6 +106,19 @@ namespace FlaxEditor.Windows } } + internal bool ShowGeneratedFiles + { + get => _showGeneratedFiles; + set + { + if (_showGeneratedFiles != value) + { + _showGeneratedFiles = value; + RefreshView(); + } + } + } + internal bool ShowAllFiles { get => _showAllFiles; @@ -129,6 +142,8 @@ namespace FlaxEditor.Windows Title = "Content"; Icon = editor.Icons.Folder32; + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); + // Content database events editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true; editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved; @@ -314,6 +329,12 @@ namespace FlaxEditor.Windows b.Checked = ShowPluginsFiles; b.CloseMenuOnClick = false; b.AutoCheck = true; + + b = show.ContextMenu.AddButton("Generated files", () => ShowGeneratedFiles = !ShowGeneratedFiles); + b.TooltipText = "Shows generated files"; + b.Checked = ShowGeneratedFiles; + b.CloseMenuOnClick = false; + b.AutoCheck = true; b = show.ContextMenu.AddButton("All files", () => ShowAllFiles = !ShowAllFiles); b.TooltipText = "Shows all files including other than assets and source code"; @@ -722,7 +743,12 @@ namespace FlaxEditor.Windows { var item = Editor.ContentDatabase.Find(sourcePath); if (item != null) - Editor.ContentDatabase.Copy(item, Path.Combine(CurrentViewFolder.Path, item.FileName)); + { + var newPath = StringUtils.NormalizePath(Path.Combine(CurrentViewFolder.Path, item.FileName)); + if (sourcePath.Equals(newPath)) + newPath = GetClonedAssetPath(item); + Editor.ContentDatabase.Copy(item, newPath); + } else importFiles.Add(sourcePath); } @@ -969,6 +995,8 @@ namespace FlaxEditor.Windows var items = target.Folder.Children; if (!_showAllFiles) items = items.Where(x => !(x is FileItem)).ToList(); + if (!_showGeneratedFiles) + items = items.Where(x => !(x.Path.EndsWith(".Gen.cs", StringComparison.Ordinal) || x.Path.EndsWith(".Gen.h", StringComparison.Ordinal) || x.Path.EndsWith(".Gen.cpp", StringComparison.Ordinal) || x.Path.EndsWith(".csproj", StringComparison.Ordinal) || x.Path.Contains(".CSharp"))).ToList(); _view.ShowItems(items, _sortType, false, true); } } @@ -1145,6 +1173,7 @@ namespace FlaxEditor.Windows writer.WriteAttributeString("ShowEngineFiles", ShowEngineFiles.ToString()); writer.WriteAttributeString("ShowPluginsFiles", ShowPluginsFiles.ToString()); writer.WriteAttributeString("ShowAllFiles", ShowAllFiles.ToString()); + writer.WriteAttributeString("ShowGeneratedFiles", ShowGeneratedFiles.ToString()); writer.WriteAttributeString("ViewType", _view.ViewType.ToString()); } @@ -1162,6 +1191,8 @@ namespace FlaxEditor.Windows ShowPluginsFiles = value2; if (bool.TryParse(node.GetAttribute("ShowAllFiles"), out value2)) ShowAllFiles = value2; + if (bool.TryParse(node.GetAttribute("ShowGeneratedFiles"), out value2)) + ShowGeneratedFiles = value2; if (Enum.TryParse(node.GetAttribute("ViewType"), out ContentViewType viewType)) _view.ViewType = viewType; } diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs index 49b03ede1..ab0c07ec5 100644 --- a/Source/Editor/Windows/DebugLogWindow.cs +++ b/Source/Editor/Windows/DebugLogWindow.cs @@ -317,6 +317,7 @@ namespace FlaxEditor.Windows Title = "Debug Log"; Icon = IconInfo; OnEditorOptionsChanged(Editor.Options.Options); + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); // Toolstrip var toolstrip = new ToolStrip(22.0f) @@ -544,7 +545,7 @@ namespace FlaxEditor.Windows if (noLocation) { desc.LocationFile = match.Groups[2].Value; - int.TryParse(match.Groups[5].Value, out desc.LocationLine); + int.TryParse(match.Groups[4].Value, out desc.LocationLine); noLocation = false; } fineStackTrace.AppendLine(match.Groups[0].Value); @@ -573,7 +574,7 @@ namespace FlaxEditor.Windows if (match.Success) { desc.LocationFile = match.Groups[2].Value; - int.TryParse(match.Groups[3].Value, out desc.LocationLine); + int.TryParse(match.Groups[4].Value, out desc.LocationLine); } } diff --git a/Source/Editor/Windows/EditorOptionsWindow.cs b/Source/Editor/Windows/EditorOptionsWindow.cs index 2942f98c0..d53d4f632 100644 --- a/Source/Editor/Windows/EditorOptionsWindow.cs +++ b/Source/Editor/Windows/EditorOptionsWindow.cs @@ -216,5 +216,36 @@ namespace FlaxEditor.Windows base.OnDestroy(); } + + /// + protected override bool OnClosing(ClosingReason reason) + { + // Block closing only on user events + if (reason == ClosingReason.User) + { + // Check if asset has been edited and not saved (and still has linked item) + if (_isDataDirty && _options != null) + { + // Ask user for further action + var result = MessageBox.Show( + "Editor options have been edited. Save before closing?", + "Save before closing?", + MessageBoxButtons.YesNoCancel + ); + if (result == DialogResult.OK || result == DialogResult.Yes) + { + // Save and close + SaveData(); + } + else if (result == DialogResult.Cancel || result == DialogResult.Abort) + { + // Cancel closing + return true; + } + } + } + + return base.OnClosing(reason); + } } } diff --git a/Source/Editor/Windows/EditorWindow.cs b/Source/Editor/Windows/EditorWindow.cs index 1ff0363f1..ff0673c11 100644 --- a/Source/Editor/Windows/EditorWindow.cs +++ b/Source/Editor/Windows/EditorWindow.cs @@ -192,6 +192,13 @@ namespace FlaxEditor.Windows /// public override bool OnKeyDown(KeyboardKeys key) { + // Prevent closing the editor window when using RMB + Ctrl + W to slow down the camera flight + if (Editor.Options.Options.Input.CloseTab.Process(this, key)) + { + if (Root.GetMouseButton(MouseButton.Right)) + return true; + } + if (base.OnKeyDown(key)) return true; @@ -207,7 +214,8 @@ namespace FlaxEditor.Windows case KeyboardKeys.Tab: if (CanUseNavigation && Root != null) { - Root.Navigate(NavDirection.Next); + bool shiftDown = Root.GetKey(KeyboardKeys.Shift); + Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next); return true; } break; diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 513542fdc..bbdf763fb 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -271,6 +271,8 @@ namespace FlaxEditor.Windows Title = "Game"; AutoFocus = true; + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); + var task = MainRenderTask.Instance; // Setup viewport @@ -302,10 +304,6 @@ namespace FlaxEditor.Windows // Link editor options Editor.Options.OptionsChanged += OnOptionsChanged; OnOptionsChanged(Editor.Options.Options); - - InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor); - InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause); - InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); } private void ChangeViewportRatio(ViewportScaleOptions v) diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 0b2249a33..1d188a32c 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -150,6 +150,7 @@ namespace FlaxEditor.Windows { Title = "Output Log"; ClipChildren = false; + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); // Setup UI _viewDropdown = new Button(2, 2, 40.0f, TextBoxBase.DefaultHeight) @@ -159,7 +160,7 @@ namespace FlaxEditor.Windows Parent = this, }; _viewDropdown.Clicked += OnViewButtonClicked; - _searchBox = new SearchBox(false, _viewDropdown.Right + 2, 2, Width - _viewDropdown.Right - 2 - _scrollSize) + _searchBox = new SearchBox(false, _viewDropdown.Right + 2, 2, Width - _viewDropdown.Right - 4) { Parent = this, }; @@ -170,11 +171,12 @@ namespace FlaxEditor.Windows Maximum = 0, }; _hScroll.ValueChanged += OnHScrollValueChanged; - _vScroll = new VScrollBar(this, Width - _scrollSize, Height, _scrollSize) + _vScroll = new VScrollBar(this, Width - _scrollSize, Height - _viewDropdown.Height - 2, _scrollSize) { ThumbThickness = 10, Maximum = 0, }; + _vScroll.Y += _viewDropdown.Height + 2; _vScroll.ValueChanged += OnVScrollValueChanged; _output = new OutputTextBox { @@ -408,7 +410,7 @@ namespace FlaxEditor.Windows if (_output != null) { - _searchBox.Width = Width - _viewDropdown.Right - 2 - _scrollSize; + _searchBox.Width = Width - _viewDropdown.Right - 4; _output.Size = new Float2(_vScroll.X - 2, _hScroll.Y - 4 - _viewDropdown.Bottom); } } diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index 36fbf21f4..fd4061276 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -429,21 +429,8 @@ namespace FlaxEditor.Windows.Profiler private void UpdateTable(ref ViewRange viewRange) { _table.IsLayoutLocked = true; - int idx = 0; - while (_table.Children.Count > idx) - { - var child = _table.Children[idx]; - if (child is Row row) - { - _tableRowsCache.Add(row); - child.Parent = null; - } - else - { - idx++; - } - } + RecycleTableRows(_table, _tableRowsCache); UpdateTableInner(ref viewRange); _table.UnlockChildrenRecursive(); diff --git a/Source/Editor/Windows/Profiler/GPU.cs b/Source/Editor/Windows/Profiler/GPU.cs index 2cf75a9aa..4ed18691a 100644 --- a/Source/Editor/Windows/Profiler/GPU.cs +++ b/Source/Editor/Windows/Profiler/GPU.cs @@ -298,21 +298,7 @@ namespace FlaxEditor.Windows.Profiler private void UpdateTable() { _table.IsLayoutLocked = true; - int idx = 0; - while (_table.Children.Count > idx) - { - var child = _table.Children[idx]; - if (child is Row row) - { - _tableRowsCache.Add(row); - child.Parent = null; - } - else - { - idx++; - } - } - _table.LockChildrenRecursive(); + RecycleTableRows(_table, _tableRowsCache); UpdateTableInner(); diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs index f0b93a03c..dbee0e8e7 100644 --- a/Source/Editor/Windows/Profiler/Network.cs +++ b/Source/Editor/Windows/Profiler/Network.cs @@ -1,8 +1,32 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System; +using System.Collections.Generic; +using FlaxEditor.GUI; using FlaxEngine; using FlaxEngine.GUI; +namespace FlaxEngine +{ + partial class ProfilingTools + { + partial struct NetworkEventStat + { + /// + /// Gets the event name. + /// + public unsafe string Name + { + get + { + fixed (byte* name = Name0) + return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new IntPtr(name)); + } + } + } + } +} + namespace FlaxEditor.Windows.Profiler { /// @@ -13,6 +37,10 @@ namespace FlaxEditor.Windows.Profiler { private readonly SingleChart _dataSentChart; private readonly SingleChart _dataReceivedChart; + private readonly Table _tableRpc; + private readonly Table _tableRep; + private SamplesBuffer _events; + private List _tableRowsCache; private FlaxEngine.Networking.NetworkDriverStats _prevStats; public Network() @@ -48,11 +76,10 @@ namespace FlaxEditor.Windows.Profiler Parent = layout, }; _dataReceivedChart.SelectedSampleChanged += OnSelectedSampleChanged; - } - private static string FormatSampleBytes(float v) - { - return Utilities.Utils.FormatBytesCount((ulong)v); + // Tables + _tableRpc = InitTable(layout, "RPC Name"); + _tableRep = InitTable(layout, "Replication Name"); } /// @@ -60,21 +87,30 @@ namespace FlaxEditor.Windows.Profiler { _dataSentChart.Clear(); _dataReceivedChart.Clear(); + _events?.Clear(); } /// public override void Update(ref SharedUpdateData sharedData) { - var peer = FlaxEngine.Networking.NetworkManager.Peer; - if (peer == null) + // Gather peer stats + var peers = FlaxEngine.Networking.NetworkPeer.Peers; + var stats = new FlaxEngine.Networking.NetworkDriverStats(); + foreach (var peer in peers) { - _prevStats = new FlaxEngine.Networking.NetworkDriverStats(); - return; + var peerStats = peer.NetworkDriver.GetStats(); + stats.TotalDataSent += peerStats.TotalDataSent; + stats.TotalDataReceived += peerStats.TotalDataReceived; } - var stats = peer.NetworkDriver.GetStats(); _dataSentChart.AddSample(Mathf.Max((long)stats.TotalDataSent - (long)_prevStats.TotalDataSent, 0)); _dataReceivedChart.AddSample(Mathf.Max((long)stats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0)); _prevStats = stats; + + // Gather network events + var events = ProfilingTools.EventsNetwork; + if (_events == null) + _events = new SamplesBuffer(); + _events.Add(events); } /// @@ -82,6 +118,159 @@ namespace FlaxEditor.Windows.Profiler { _dataSentChart.SelectedSampleIndex = selectedFrame; _dataReceivedChart.SelectedSampleIndex = selectedFrame; + + // Update events tables + if (_events != null) + { + if (_tableRowsCache == null) + _tableRowsCache = new List(); + _tableRpc.IsLayoutLocked = true; + _tableRep.IsLayoutLocked = true; + RecycleTableRows(_tableRpc, _tableRowsCache); + RecycleTableRows(_tableRep, _tableRowsCache); + + var events = _events.Get(selectedFrame); + var rowCount = Int2.Zero; + if (events != null && events.Length != 0) + { + var rowColor2 = Style.Current.Background * 1.4f; + for (int i = 0; i < events.Length; i++) + { + var e = events[i]; + var name = e.Name; + var isRpc = name.Contains("::", StringComparison.Ordinal); + + Row row; + if (_tableRowsCache.Count != 0) + { + var last = _tableRowsCache.Count - 1; + row = _tableRowsCache[last]; + _tableRowsCache.RemoveAt(last); + } + else + { + row = new Row + { + Values = new object[5], + }; + } + { + // Name + row.Values[0] = name; + + // Count + row.Values[1] = (int)e.Count; + + // Data Size + row.Values[2] = (int)e.DataSize; + + // Message Size + row.Values[3] = (int)e.MessageSize; + + // Receivers + row.Values[4] = (float)e.Receivers / (float)e.Count; + } + + var table = isRpc ? _tableRpc : _tableRep; + row.Width = table.Width; + row.BackgroundColor = rowCount[isRpc ? 0 : 1] % 2 == 0 ? rowColor2 : Color.Transparent; + row.Parent = table; + if (isRpc) + rowCount.X++; + else + rowCount.Y++; + } + } + + _tableRpc.Visible = rowCount.X != 0; + _tableRep.Visible = rowCount.Y != 0; + _tableRpc.Children.Sort(SortRows); + _tableRep.Children.Sort(SortRows); + + _tableRpc.UnlockChildrenRecursive(); + _tableRpc.PerformLayout(); + _tableRep.UnlockChildrenRecursive(); + _tableRep.PerformLayout(); + } + } + + /// + public override void OnDestroy() + { + _tableRowsCache?.Clear(); + + base.OnDestroy(); + } + + private static Table InitTable(ContainerControl parent, string name) + { + var headerColor = Style.Current.LightBackground; + var table = new Table + { + Columns = new[] + { + new ColumnDefinition + { + UseExpandCollapseMode = true, + CellAlignment = TextAlignment.Near, + Title = name, + TitleBackgroundColor = headerColor, + }, + new ColumnDefinition + { + Title = "Count", + TitleBackgroundColor = headerColor, + }, + new ColumnDefinition + { + Title = "Data Size", + TitleBackgroundColor = headerColor, + FormatValue = FormatCellBytes, + }, + new ColumnDefinition + { + Title = "Message Size", + TitleBackgroundColor = headerColor, + FormatValue = FormatCellBytes, + }, + new ColumnDefinition + { + Title = "Receivers", + TitleBackgroundColor = headerColor, + }, + }, + Splits = new[] + { + 0.40f, + 0.15f, + 0.15f, + 0.15f, + 0.15f, + }, + Parent = parent, + }; + return table; + } + + private static string FormatSampleBytes(float v) + { + return Utilities.Utils.FormatBytesCount((ulong)v); + } + + private static string FormatCellBytes(object x) + { + return Utilities.Utils.FormatBytesCount((int)x); + } + + private static int SortRows(Control x, Control y) + { + if (x is Row xRow && y is Row yRow) + { + var xDataSize = (int)xRow.Values[2]; + var yDataSize = (int)yRow.Values[2]; + return yDataSize - xDataSize; + } + return 0; } } } diff --git a/Source/Editor/Windows/Profiler/ProfilerMode.cs b/Source/Editor/Windows/Profiler/ProfilerMode.cs index 0cc1a39ee..b8d2f0c4c 100644 --- a/Source/Editor/Windows/Profiler/ProfilerMode.cs +++ b/Source/Editor/Windows/Profiler/ProfilerMode.cs @@ -1,6 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; +using FlaxEditor.GUI; using FlaxEditor.GUI.Tabs; using FlaxEngine; @@ -135,5 +137,28 @@ namespace FlaxEditor.Windows.Profiler { SelectedSampleChanged?.Invoke(frameIndex); } + + /// + /// Recycles all table rows to be reused. + /// + /// The table. + /// The output cache. + protected static void RecycleTableRows(Table table, List rowsCache) + { + int idx = 0; + while (table.Children.Count > idx) + { + var child = table.Children[idx]; + if (child is Row row) + { + rowsCache.Add(row); + child.Parent = null; + } + else + { + idx++; + } + } + } } } diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs index f5a5c6f86..f97e943ad 100644 --- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs +++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs @@ -93,7 +93,7 @@ namespace FlaxEditor.Windows.Profiler _liveRecordingButton = toolstrip.AddButton(editor.Icons.Play64); _liveRecordingButton.LinkTooltip("Live profiling events recording"); _liveRecordingButton.AutoCheck = true; - _liveRecordingButton.Clicked += () => _liveRecordingButton.Icon = LiveRecording ? editor.Icons.Stop64 : editor.Icons.Play64; + _liveRecordingButton.Clicked += OnLiveRecordingChanged; _clearButton = toolstrip.AddButton(editor.Icons.Rotate32, Clear); _clearButton.LinkTooltip("Clear data"); toolstrip.AddSeparator(); @@ -116,6 +116,16 @@ namespace FlaxEditor.Windows.Profiler Parent = this }; _tabs.SelectedTabChanged += OnSelectedTabChanged; + + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); + InputActions.Bindings.RemoveAll(x => x.Callback == this.FocusOrShow); + InputActions.Add(options => options.ProfilerWindow, Hide); + } + + private void OnLiveRecordingChanged() + { + _liveRecordingButton.Icon = LiveRecording ? Editor.Icons.Stop64 : Editor.Icons.Play64; + ProfilingTools.Enabled = LiveRecording; } /// diff --git a/Source/Editor/Windows/Profiler/SamplesBuffer.cs b/Source/Editor/Windows/Profiler/SamplesBuffer.cs index abc99cfd5..999156dca 100644 --- a/Source/Editor/Windows/Profiler/SamplesBuffer.cs +++ b/Source/Editor/Windows/Profiler/SamplesBuffer.cs @@ -49,6 +49,8 @@ namespace FlaxEditor.Windows.Profiler /// The sample value public T Get(int index) { + if (index >= _data.Length || _data.Length == 0) + return default; return index == -1 ? _data[_count - 1] : _data[index]; } diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs index 72ff3be33..04cce47e8 100644 --- a/Source/Editor/Windows/SceneEditorWindow.cs +++ b/Source/Editor/Windows/SceneEditorWindow.cs @@ -19,28 +19,7 @@ namespace FlaxEditor.Windows protected SceneEditorWindow(Editor editor, bool hideOnClose, ScrollBars scrollBars) : base(editor, hideOnClose, scrollBars) { - // Setup input actions - InputActions.Add(options => options.Save, Editor.SaveAll); - InputActions.Add(options => options.Undo, () => - { - Editor.PerformUndo(); - Focus(); - }); - InputActions.Add(options => options.Redo, () => - { - Editor.PerformRedo(); - Focus(); - }); - InputActions.Add(options => options.Cut, Editor.SceneEditing.Cut); - InputActions.Add(options => options.Copy, Editor.SceneEditing.Copy); - InputActions.Add(options => options.Paste, Editor.SceneEditing.Paste); - InputActions.Add(options => options.Duplicate, Editor.SceneEditing.Duplicate); - 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.DelegatePlayOrStopPlayInEditor); - InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause); - InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame); + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); } } } diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index e48e59f80..63d110368 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -26,6 +26,7 @@ namespace FlaxEditor.Windows bool hasSthSelected = Editor.SceneEditing.HasSthSelected; bool isSingleActorSelected = Editor.SceneEditing.SelectionCount == 1 && Editor.SceneEditing.Selection[0] is ActorNode; bool canEditScene = Editor.StateMachine.CurrentState.CanEditScene && Level.IsAnySceneLoaded; + var inputOptions = Editor.Options.Options.Input; // Create popup @@ -44,17 +45,17 @@ namespace FlaxEditor.Windows if (hasSthSelected) { - contextMenu.AddButton(Editor.Windows.EditWin.IsPilotActorActive ? "Stop piloting actor" : "Pilot actor", Editor.UI.PilotActor); + contextMenu.AddButton(Editor.Windows.EditWin.IsPilotActorActive ? "Stop piloting actor" : "Pilot actor", inputOptions.PilotActor, Editor.UI.PilotActor); } contextMenu.AddSeparator(); // Basic editing options - b = contextMenu.AddButton("Rename", Rename); + b = contextMenu.AddButton("Rename", inputOptions.Rename, Rename); b.Enabled = isSingleActorSelected; - b = contextMenu.AddButton("Duplicate", Editor.SceneEditing.Duplicate); + b = contextMenu.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); b.Enabled = hasSthSelected; if (isSingleActorSelected) @@ -116,17 +117,17 @@ namespace FlaxEditor.Windows } } } - b = contextMenu.AddButton("Delete", Editor.SceneEditing.Delete); + b = contextMenu.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); b.Enabled = hasSthSelected; contextMenu.AddSeparator(); - b = contextMenu.AddButton("Copy", Editor.SceneEditing.Copy); + b = contextMenu.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); b.Enabled = hasSthSelected; - contextMenu.AddButton("Paste", Editor.SceneEditing.Paste); + contextMenu.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); - b = contextMenu.AddButton("Cut", Editor.SceneEditing.Cut); + b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); b.Enabled = canEditScene; // Prefab options diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 609f98f83..fba052269 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -173,6 +173,9 @@ namespace FlaxEditor.Windows // Spawn it Editor.SceneEditing.Spawn(actor, parentActor); + + Editor.SceneEditing.Select(actor); + Rename(); } /// diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 7cf7a7f78..d2371a540 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -18,21 +18,21 @@ const Char* SplashScreenQuotes[] = TEXT("Loading"), TEXT("Unloading"), TEXT("Reloading"), - TEXT("Reloading gun"), + TEXT("Downloading more RAM"), TEXT("Consuming your RAM"), TEXT("Burning your CPU"), - TEXT("#BetterThanUnity"), TEXT("Rendering buttons"), TEXT("Collecting crash data"), - TEXT("Downloading porn"), #if PLATFORM_WINDOWS - TEXT("Removing 'C:\\Windows\\'"), + TEXT("We're getting everything ready for you."), #elif PLATFORM_LINUX - TEXT("Time to switch to Windows?"), - TEXT("Installing Windows 10"), + TEXT("Try it on a Raspberry"), + TEXT("Trying to exit vim"), + TEXT("Sudo flax --loadproject"), #elif PLATFORM_MAC - TEXT("Hacking your iPhone"), + TEXT("don't compare Macbooks to oranges."), TEXT("Why does macbook heat up?\nBecause it doesn't have windows"), + TEXT("Starting Direc... um, Vulkan renderer."), #endif TEXT("Kappa!"), TEXT("How you doin'?"), @@ -40,12 +40,10 @@ const Char* SplashScreenQuotes[] = TEXT("Bond. James Bond."), TEXT("To infinity and beyond!"), TEXT("Houston, we have a problem"), - TEXT("NotImplementedEngineException"), TEXT("Made in Poland"), TEXT("We like you"), TEXT("Compiling the compiler"), TEXT("Flax it up!"), - TEXT("Fun fact: Fortnite runs on Flax"), TEXT("Toss a coin to your Witcher!!!"), TEXT("Holy Moly!"), TEXT("Just Read the Instructions"), @@ -60,7 +58,6 @@ const Char* SplashScreenQuotes[] = TEXT("They see me loadin'"), TEXT("Loadin' loadin' and loadin' loadin'"), TEXT("Procedurally generating buttons"), - TEXT("Out of Memory Exception!"), TEXT("Running Big Bang simulation"), TEXT("Calculating infinity"), TEXT("Dividing infinity by zero"), @@ -70,10 +67,7 @@ const Char* SplashScreenQuotes[] = TEXT("Whatever you do, do it well.\n~Walt Disney"), TEXT("Here's Johnny!"), TEXT("Did you see that? No... I don't think so"), - TEXT("Collecting unreal power"), TEXT("Stay safe, friend"), - TEXT("trolololololololololololo"), - TEXT("xD"), TEXT("Come to the dark side"), TEXT("Flax Facts: This is a loading screen"), TEXT("Don't Stop Me Now"), @@ -81,61 +75,58 @@ const Char* SplashScreenQuotes[] = TEXT("Made with Flax"), TEXT("This is the way"), TEXT("The quick brown fox jumps over the lazy dog"), - TEXT("Hit The Road Jack"), TEXT("You have 7 lives left"), TEXT("May the Force be with you"), TEXT("A martini. Shaken, not stirred"), TEXT("Hasta la vista, baby"), TEXT("Winter is coming"), - TEXT("You know nothing, Jon Snow"), TEXT("Create something awesome!"), TEXT("Well Polished Engine"), TEXT("Error 404: Joke Not Found"), TEXT("Rushing B"), TEXT("Putting pineapple on pizza"), - TEXT("Loading Simulation"), + TEXT("Entering the Matrix"), TEXT("Get ready for a surprise!"), TEXT("Coffee is my fuel"), - TEXT("Installing a free copy of Cyberpunk 2077"), TEXT("With great power comes great electricity bill"), TEXT("Flax was made in the same city as Witcher 3"), TEXT("So JavaScript is a scripting version of Java"), TEXT("Good things take time.\n~Someone"), - TEXT("Get shit done"), TEXT("Hold Tight! Loading Flax"), TEXT("That's one small step for a man,\none giant leap for mankind"), TEXT("Remember to save your work frequently"), TEXT("In case of fire:\ngit commit, git push, leave building"), TEXT("Keep calm and make games"), TEXT("You're breathtaking!!!"), - TEXT("Do regular dogs see police dogs & think,\nOh no it's a cop?"), - TEXT("Dear Santa,\nDefine naughty."), TEXT("Blah, blah"), TEXT("My PRECIOUS!!!!"), TEXT("YOU SHALL NOT PASS!"), TEXT("You have my bow.\nAnd my axe!"), TEXT("To the bridge of Khazad-dum."), TEXT("One ring to rule them all.\nOne ring to find them."), - TEXT("Ladies and gentelman, we got him"), - TEXT("Cyberpunk of game engines"), TEXT("That's what she said"), - TEXT("Compiling Shaders (93,788)"), + TEXT("We could be compiling shaders here"), TEXT("Hello There"), TEXT("BAGUETTE"), - TEXT("All we had to do was follow the damn train, CJ"), TEXT("Here we go again"), TEXT("@everyone"), TEXT("Potato"), TEXT("Python is a programming snek"), TEXT("Flax will start when pigs will fly"), - TEXT("I'm the android sent by CyberLife"), - TEXT("Fancy-ass ray tracing, rtx on, lighting"), TEXT("ZOINKS"), TEXT("Scooby dooby doo"), TEXT("You shall not load!"), TEXT("The roof, the roof, the roof is on fire!"), - TEXT("I've seen better documentation...\nFrom ransomware gangs!"), TEXT("Slava Ukraini!"), + TEXT("RTX off... for now!"), + TEXT("Increasing Fiber count"), + TEXT("Now this is podracing!"), + TEXT("Weird flax, but ok"), + TEXT("Reticulating Splines"), + TEXT("Discombobulating"), + TEXT("Who is signing all these integers?!"), + TEXT("Flax fact: Flax was called Celelej once."), + TEXT("Changing text overflow setti-"), }; SplashScreen::~SplashScreen() diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 3df4d3527..b747149c5 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -353,6 +353,8 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.None) { Title = "Toolbox"; + + FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); } /// diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp index fa7999ce0..8fef8e6d3 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp @@ -905,7 +905,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3 MException ex(exception); ex.Log(LogType::Error, TEXT("Property")); } - else if (!MCore::Type::IsPointer(valueType)) + else if (!MCore::Type::IsPointer(valueType) && !MCore::Type::IsReference(valueType)) { if (boxed) Platform::MemoryCopy(value, MCore::Object::Unbox(boxed), valueSize); diff --git a/Source/Engine/ContentImporters/ImportShader.cpp b/Source/Engine/ContentImporters/ImportShader.cpp index 9cfb1242c..bf5b5581d 100644 --- a/Source/Engine/ContentImporters/ImportShader.cpp +++ b/Source/Engine/ContentImporters/ImportShader.cpp @@ -32,14 +32,21 @@ CreateAssetResult ImportShader::Import(CreateAssetContext& context) LOG(Warning, "Empty shader source file."); return CreateAssetResult::Error; } + + // Ensure the source code has an empty line at the end (expected by glslang) + auto sourceCodeChunkSize = sourceCodeSize + 1; + if (sourceCodeText[sourceCodeSize - 1] != '\n') + sourceCodeChunkSize++; + const auto& sourceCodeChunk = context.Data.Header.Chunks[SourceCodeChunk]; - sourceCodeChunk->Data.Allocate(sourceCodeSize + 1); + sourceCodeChunk->Data.Allocate(sourceCodeChunkSize); const auto sourceCode = sourceCodeChunk->Get(); Platform::MemoryCopy(sourceCode, sourceCodeText.Get(), sourceCodeSize); + sourceCode[sourceCodeChunkSize - 2] = '\n'; // Encrypt source code - Encryption::EncryptBytes(sourceCode, sourceCodeSize); - sourceCode[sourceCodeSize] = 0; + Encryption::EncryptBytes(sourceCode, sourceCodeChunkSize - 1); + sourceCode[sourceCodeChunkSize - 1] = 0; // Set Custom Data with Header ShaderStorage::Header20 shaderHeader; diff --git a/Source/Engine/Core/Collections/ArrayExtensions.h b/Source/Engine/Core/Collections/ArrayExtensions.h index 788b31edb..2ae6da9c8 100644 --- a/Source/Engine/Core/Collections/ArrayExtensions.h +++ b/Source/Engine/Core/Collections/ArrayExtensions.h @@ -4,7 +4,7 @@ #include "../Collections/Array.h" #include "../Collections/Dictionary.h" -#include +#include "../Delegate.h" class ArrayExtensions; @@ -23,7 +23,6 @@ public: /// /// Gets the common key. /// - /// The key. FORCE_INLINE const TKey& GetKey() const { return _key; @@ -32,7 +31,6 @@ public: /// /// Gets the common key. /// - /// The key. FORCE_INLINE TKey GetKey() { return _key; @@ -52,7 +50,7 @@ public: /// The prediction function. Should return true for the target element to find. /// The index of the element or -1 if nothing found. template - static int32 IndexOf(const Array& obj, const std::function& predicate) + static int32 IndexOf(const Array& obj, const Function& predicate) { for (int32 i = 0; i < obj.Count(); i++) { @@ -71,7 +69,7 @@ public: /// The prediction function. /// True if any element in the collection matches the prediction, otherwise false. template - static bool Any(const Array& obj, const std::function& predicate) + static bool Any(const Array& obj, const Function& predicate) { for (int32 i = 0; i < obj.Count(); i++) { @@ -90,7 +88,7 @@ public: /// The prediction function. /// True if all elements in the collection matches the prediction, otherwise false. template - static int32 All(const Array& obj, const std::function& predicate) + static int32 All(const Array& obj, const Function& predicate) { for (int32 i = 0; i < obj.Count(); i++) { @@ -109,7 +107,7 @@ public: /// A function to extract the key for each element. /// The result collection with groups. template - static void GroupBy(const Array& obj, const std::function& keySelector, Array, AllocationType>& result) + static void GroupBy(const Array& obj, const Function& keySelector, Array, AllocationType>& result) { Dictionary> data(static_cast(obj.Count() * 3.0f)); for (int32 i = 0; i < obj.Count(); i++) diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index 1147c4ff1..e3d97e149 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -251,19 +251,13 @@ void Log::Logger::Write(LogType type, const StringView& msg) OnError(type, msg); } - // Check if need to show message box with that log message - if (type == LogType::Fatal) - { + // Ensure the error gets written to the disk + if (type == LogType::Fatal || type == LogType::Error) Flush(); - // Process message further - if (type == LogType::Fatal) - Platform::Fatal(msg); - else if (type == LogType::Error) - Platform::Error(msg); - else - Platform::Info(msg); - } + // Check if need to show message box with that log message + if (type == LogType::Fatal) + Platform::Fatal(msg); } const Char* ToString(LogType e) diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index 2af365638..568c51764 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -58,6 +58,7 @@ using Mathr = FlaxEngine.Mathf; */ using System; using System.Globalization; +using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -953,6 +954,91 @@ namespace FlaxEngine return result; } + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime, float maxSpeed) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, maxSpeed, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, float.PositiveInfinity, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + /// Delta Time, represents the time elapsed since last frame. + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime, [DefaultValue("float.PositiveInfinity")] float maxSpeed, [DefaultValue("Time.DeltaTime")] float deltaTime) + { + smoothTime = Mathf.Max(0.0001f, smoothTime); + Real a = 2f / smoothTime; + Real b = a * deltaTime; + Real e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); + + Real change_x = current.X - target.X; + Real change_y = current.Y - target.Y; + Vector2 originalTo = target; + + Real maxChangeSpeed = maxSpeed * smoothTime; + Real changeSq = maxChangeSpeed * maxChangeSpeed; + Real sqrDist = change_x * change_x + change_y * change_y; + if (sqrDist > changeSq) + { + var dist = (Real)Math.Sqrt(sqrDist); + change_x = change_x / dist * maxChangeSpeed; + change_y = change_y / dist * maxChangeSpeed; + } + + target.X = current.X - change_x; + target.Y = current.Y - change_y; + + Real temp_x = (currentVelocity.X + a * change_x) * deltaTime; + Real temp_y = (currentVelocity.Y + a * change_y) * deltaTime; + + currentVelocity.X = (currentVelocity.X - a * temp_x) * e; + currentVelocity.Y = (currentVelocity.Y - a * temp_y) * e; + + Real output_x = target.X + (change_x + temp_x) * e; + Real output_y = target.Y + (change_y + temp_y) * e; + + Real x1 = originalTo.X - current.X; + Real y1 = originalTo.Y - current.Y; + + Real x2 = output_x - originalTo.X; + Real y2 = output_y - originalTo.Y; + + if (x1 * x2 + y1 * y2 > 0) + { + output_x = originalTo.X; + output_y = originalTo.Y; + + currentVelocity.X = (output_x - originalTo.X) / deltaTime; + currentVelocity.Y = (output_y - originalTo.Y) / deltaTime; + } + + return new Vector2(output_x, output_y); + } + /// /// Performs a cubic interpolation between two vectors. /// diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs index 4442d3334..59d1f63db 100644 --- a/Source/Engine/Core/Math/Vector3.cs +++ b/Source/Engine/Core/Math/Vector3.cs @@ -58,6 +58,7 @@ using Mathr = FlaxEngine.Mathf; */ using System; using System.Globalization; +using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -1042,6 +1043,103 @@ namespace FlaxEngine return result; } + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, float maxSpeed) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, maxSpeed, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, float.PositiveInfinity, Time.DeltaTime); + } + + /// + /// Performs a gradual change of a vector towards a specified target over time + /// + /// Current vector. + /// Target vector. + /// Used to store the current velocity. + /// Determines the approximate time it should take to reach the target vector. + /// Defines the upper limit on the speed of the Smooth Damp. + /// Delta Time, represents the time elapsed since last frame. + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, [DefaultValue("float.PositiveInfinity")] float maxSpeed, [DefaultValue("Time.DeltaTime")] float deltaTime) + { + smoothTime = Mathf.Max(0.0001f, smoothTime); + Real a = 2f / smoothTime; + Real b = a * deltaTime; + Real e = 1f / (1f + b + 0.48f * b * b + 0.235f * b * b * b); + + Real change_x = current.X - target.X; + Real change_y = current.Y - target.Y; + Real change_z = current.Z - target.Z; + + Vector3 originalTo = target; + + Real maxChangeSpeed = maxSpeed * smoothTime; + Real changeSq = maxChangeSpeed * maxChangeSpeed; + Real sqrLen = change_x * change_x + change_y * change_y + change_z * change_z; + if (sqrLen > changeSq) + { + var len = (Real)Math.Sqrt(sqrLen); + change_x = change_x / len * maxChangeSpeed; + change_y = change_y / len * maxChangeSpeed; + change_z = change_z / len * maxChangeSpeed; + } + + target.X = current.X - change_x; + target.Y = current.Y - change_y; + target.Z = current.Z - change_z; + + Real temp_x = (currentVelocity.X + a * change_x) * deltaTime; + Real temp_y = (currentVelocity.Y + a * change_y) * deltaTime; + Real temp_z = (currentVelocity.Z + a * change_z) * deltaTime; + + currentVelocity.X = (currentVelocity.X - a * temp_x) * e; + currentVelocity.Y = (currentVelocity.Y - a * temp_y) * e; + currentVelocity.Z = (currentVelocity.Z - a * temp_z) * e; + + Real output_x = target.X + (change_x + temp_x) * e; + Real output_y = target.Y + (change_y + temp_y) * e; + Real output_z = target.Z + (change_z + temp_z) * e; + + Real x1 = originalTo.X - current.X; + Real y1 = originalTo.Y - current.Y; + Real z1 = originalTo.Z - current.Z; + + Real x2 = output_x - originalTo.X; + Real y2 = output_y - originalTo.Y; + Real z2 = output_z - originalTo.Z; + + if (x1 * x2 + y1 * y2 + z1 * z2 > 0) + { + output_x = originalTo.X; + output_y = originalTo.Y; + output_z = originalTo.Z; + + currentVelocity.X = (output_x - originalTo.X) / deltaTime; + currentVelocity.Y = (output_y - originalTo.Y) / deltaTime; + currentVelocity.Z = (output_z - originalTo.Z) / deltaTime; + } + + return new Vector3(output_x, output_y, output_z); + } + + /// /// Performs a cubic interpolation between two vectors. /// diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 228a47856..5b93ab588 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -102,9 +102,6 @@ int32 Engine::Main(const Char* cmdLine) Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue()); Time::StartupTime = DateTime::Now(); -#if COMPILE_WITH_PROFILER - ProfilerCPU::Enabled = true; -#endif Globals::StartupFolder = Globals::BinariesFolder = Platform::GetMainDirectory(); #if USE_EDITOR Globals::StartupFolder /= TEXT("../../../.."); diff --git a/Source/Engine/Engine/NativeInterop.Invoker.cs b/Source/Engine/Engine/NativeInterop.Invoker.cs index 381e6dac0..36d7f2e03 100644 --- a/Source/Engine/Engine/NativeInterop.Invoker.cs +++ b/Source/Engine/Engine/NativeInterop.Invoker.cs @@ -84,7 +84,7 @@ namespace FlaxEngine.Interop internal static IntPtr MarshalReturnValueType(ref Type returnValue) { - return returnValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(returnValue)) : IntPtr.Zero; + return returnValue != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(returnValue)) : IntPtr.Zero; } internal static IntPtr MarshalReturnValueArray(ref TRet returnValue) @@ -162,8 +162,8 @@ namespace FlaxEngine.Interop return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject); if (returnType == typeof(bool)) return (bool)returnObject ? boolTruePtr : boolFalsePtr; - if (returnType == typeof(Type)) - return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnObject))); + if (returnType == typeof(Type) || returnType == typeof(TypeHolder)) + return ManagedHandle.ToIntPtr(GetTypeManagedHandle(Unsafe.As(returnObject))); if (returnType.IsArray && ArrayFactory.GetMarshalledType(returnType.GetElementType()) == returnType.GetElementType()) return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnObject)), GCHandleType.Weak); if (returnType.IsArray) @@ -186,8 +186,8 @@ namespace FlaxEngine.Interop return (IntPtr)(object)returnObject; if (returnType == typeof(ManagedHandle)) return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject); - if (returnType == typeof(Type)) - return returnObject != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnObject))) : IntPtr.Zero; + if (returnType == typeof(Type) || returnType == typeof(TypeHolder)) + return returnObject != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(Unsafe.As(returnObject))) : IntPtr.Zero; if (returnType.IsArray) { var elementType = returnType.GetElementType(); diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs index a74299670..58aa95a2f 100644 --- a/Source/Engine/Engine/NativeInterop.Marshallers.cs +++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs @@ -119,13 +119,13 @@ namespace FlaxEngine.Interop [CustomMarshaller(typeof(Type), MarshalMode.Default, typeof(SystemTypeMarshaller))] public static class SystemTypeMarshaller { - public static Type ConvertToManaged(IntPtr unmanaged) => Unsafe.As(ManagedHandleMarshaller.ConvertToManaged(unmanaged)); + public static Type ConvertToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? Unsafe.As(ManagedHandleMarshaller.ConvertToManaged(unmanaged)).type : null; public static IntPtr ConvertToUnmanaged(Type managed) { if (managed == null) return IntPtr.Zero; - ManagedHandle handle = NativeInterop.GetTypeGCHandle(managed); + ManagedHandle handle = NativeInterop.GetTypeManagedHandle(managed); return ManagedHandle.ToIntPtr(handle); } diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 423aae6a5..b40fa740a 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -19,6 +19,7 @@ namespace FlaxEngine.Interop internal struct NativeClassDefinitions { internal ManagedHandle typeHandle; + internal IntPtr nativePointer; internal IntPtr name; internal IntPtr fullname; internal IntPtr @namespace; @@ -40,6 +41,7 @@ namespace FlaxEngine.Interop internal IntPtr name; internal ManagedHandle fieldHandle; internal ManagedHandle fieldTypeHandle; + internal int fieldOffset; internal uint fieldAttributes; } @@ -139,6 +141,9 @@ namespace FlaxEngine.Interop unsafe partial class NativeInterop { + [LibraryImport("FlaxEngine", EntryPoint = "NativeInterop_CreateClass", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] + internal static partial void NativeInterop_CreateClass(ref NativeClassDefinitions managedClass, ManagedHandle assemblyHandle); + internal enum MTypes : uint { End = 0x00, @@ -205,46 +210,8 @@ namespace FlaxEngine.Interop NativeMemory.AlignedFree(ptr); } - [UnmanagedCallersOnly] - internal static void GetManagedClasses(ManagedHandle assemblyHandle, NativeClassDefinitions** managedClasses, int* managedClassCount) + private static Assembly GetOwningAssembly(Type type) { - Assembly assembly = Unsafe.As(assemblyHandle.Target); - var assemblyTypes = GetAssemblyTypes(assembly); - - NativeClassDefinitions* arr = (NativeClassDefinitions*)NativeAlloc(assemblyTypes.Length, Unsafe.SizeOf()); - - for (int i = 0; i < assemblyTypes.Length; i++) - { - var type = assemblyTypes[i]; - IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); - var managedClass = new NativeClassDefinitions - { - typeHandle = GetTypeGCHandle(type), - name = NativeAllocStringAnsi(type.Name), - fullname = NativeAllocStringAnsi(type.GetTypeName()), - @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), - typeAttributes = (uint)type.Attributes, - }; - Unsafe.Write(ptr.ToPointer(), managedClass); - } - - *managedClasses = arr; - *managedClassCount = assemblyTypes.Length; - } - - [UnmanagedCallersOnly] - internal static void GetManagedClassFromType(ManagedHandle typeHandle, NativeClassDefinitions* managedClass, ManagedHandle* assemblyHandle) - { - Type type = Unsafe.As(typeHandle.Target); - *managedClass = new NativeClassDefinitions - { - typeHandle = GetTypeGCHandle(type), - name = NativeAllocStringAnsi(type.Name), - fullname = NativeAllocStringAnsi(type.GetTypeName()), - @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), - typeAttributes = (uint)type.Attributes, - }; - Assembly assembly = null; if (type.IsGenericType && !type.Assembly.IsCollectible) { @@ -261,14 +228,87 @@ namespace FlaxEngine.Interop } if (assembly == null) assembly = type.Assembly; + return assembly; + } - *assemblyHandle = GetAssemblyHandle(assembly); + private static NativeClassDefinitions CreateNativeClassDefinitions(Type type, out ManagedHandle assemblyHandle) + { + assemblyHandle = GetAssemblyHandle(GetOwningAssembly(type)); + return CreateNativeClassDefinitions(type); + } + + private static NativeClassDefinitions CreateNativeClassDefinitions(Type type) + { + return new NativeClassDefinitions() + { + typeHandle = RegisterType(type).handle, + name = NativeAllocStringAnsi(type.Name), + fullname = NativeAllocStringAnsi(type.GetTypeName()), + @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), + typeAttributes = (uint)type.Attributes, + }; + } + + private static NativeClassDefinitions CreateNativeClassDefinitions(Type type, ManagedHandle typeHandle, out ManagedHandle assemblyHandle) + { + assemblyHandle = GetAssemblyHandle(GetOwningAssembly(type)); + return new NativeClassDefinitions() + { + typeHandle = typeHandle, + name = NativeAllocStringAnsi(type.Name), + fullname = NativeAllocStringAnsi(type.GetTypeName()), + @namespace = NativeAllocStringAnsi(type.Namespace ?? ""), + typeAttributes = (uint)type.Attributes, + }; + } + + [UnmanagedCallersOnly] + internal static void GetManagedClasses(ManagedHandle assemblyHandle, NativeClassDefinitions** managedClasses, int* managedClassCount) + { + Assembly assembly = Unsafe.As(assemblyHandle.Target); + Type[] assemblyTypes = GetAssemblyTypes(assembly); + + *managedClasses = (NativeClassDefinitions*)NativeAlloc(assemblyTypes.Length, Unsafe.SizeOf()); + *managedClassCount = assemblyTypes.Length; + Span span = new Span(*managedClasses, assemblyTypes.Length); + for (int i = 0; i < assemblyTypes.Length; i++) + { + Type type = assemblyTypes[i]; + ref var managedClass = ref span[i]; + managedClass = CreateNativeClassDefinitions(type); + } + } + + [UnmanagedCallersOnly] + internal static void RegisterManagedClassNativePointers(NativeClassDefinitions** managedClasses, int managedClassCount) + { + Span span = new Span(Unsafe.Read(managedClasses).ToPointer(), managedClassCount); + foreach (ref NativeClassDefinitions managedClass in span) + { + TypeHolder typeHolder = Unsafe.As(managedClass.typeHandle.Target); + typeHolder.managedClassPointer = managedClass.nativePointer; + } + } + + [UnmanagedCallersOnly] + internal static void GetManagedClassFromType(ManagedHandle typeHandle, NativeClassDefinitions* managedClass, ManagedHandle* assemblyHandle) + { + Type type = Unsafe.As(typeHandle.Target); + *managedClass = CreateNativeClassDefinitions(type, out ManagedHandle handle); + *assemblyHandle = handle; + } + + private static void RegisterNativeClassFromType(TypeHolder typeHolder, ManagedHandle typeHandle) + { + NativeClassDefinitions managedClass = CreateNativeClassDefinitions(typeHolder.type, typeHandle, out ManagedHandle assemblyHandle); + NativeInterop_CreateClass(ref managedClass, assemblyHandle); + typeHolder.managedClassPointer = managedClass.nativePointer; } [UnmanagedCallersOnly] internal static void GetClassMethods(ManagedHandle typeHandle, NativeMethodDefinitions** classMethods, int* classMethodsCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); var methods = new List(); var staticMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); @@ -296,7 +336,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassFields(ManagedHandle typeHandle, NativeFieldDefinitions** classFields, int* classFieldsCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); var fields = type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); NativeFieldDefinitions* arr = (NativeFieldDefinitions*)NativeAlloc(fields.Length, Unsafe.SizeOf()); @@ -318,7 +358,8 @@ namespace FlaxEngine.Interop { name = NativeAllocStringAnsi(fieldHolder.field.Name), fieldHandle = fieldHandle, - fieldTypeHandle = GetTypeGCHandle(fieldHolder.field.FieldType), + fieldTypeHandle = GetTypeManagedHandle(fieldHolder.field.FieldType), + fieldOffset = fieldHolder.fieldOffset, fieldAttributes = (uint)fieldHolder.field.Attributes, }; Unsafe.Write(IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i).ToPointer(), classField); @@ -330,7 +371,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassProperties(ManagedHandle typeHandle, NativePropertyDefinitions** classProperties, int* classPropertiesCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); var properties = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); var arr = (NativePropertyDefinitions*)NativeAlloc(properties.Length, Unsafe.SizeOf()); @@ -364,7 +405,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassAttributes(ManagedHandle typeHandle, ManagedHandle** classAttributes, int* classAttributesCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); object[] attributeValues = type.GetCustomAttributes(false); ManagedHandle* arr = (ManagedHandle*)NativeAlloc(attributeValues.Length, Unsafe.SizeOf()); @@ -384,13 +425,13 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle GetCustomAttribute(ManagedHandle typeHandle, ManagedHandle attributeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); var attributes = type.GetCustomAttributes(false); object attrib; if (attributeHandle.IsAllocated) { // Check for certain attribute type - Type attributeType = Unsafe.As(attributeHandle.Target); + Type attributeType = Unsafe.As(attributeHandle.Target); attrib = attributes.FirstOrDefault(x => x.GetType() == attributeType); } else @@ -413,7 +454,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static void GetClassInterfaces(ManagedHandle typeHandle, IntPtr* classInterfaces, int* classInterfacesCount) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); Type[] interfaces = type.GetInterfaces(); // Match mono_class_get_interfaces which doesn't return interfaces from base class @@ -465,7 +506,7 @@ namespace FlaxEngine.Interop IntPtr arr = (IntPtr)NativeAlloc(interfaces.Length, IntPtr.Size); for (int i = 0; i < interfaces.Length; i++) { - ManagedHandle handle = GetTypeGCHandle(interfaces[i]); + ManagedHandle handle = GetTypeManagedHandle(interfaces[i]); Unsafe.Write(IntPtr.Add(arr, IntPtr.Size * i).ToPointer(), handle); } *classInterfaces = arr; @@ -477,7 +518,7 @@ namespace FlaxEngine.Interop { MethodHolder methodHolder = Unsafe.As(methodHandle.Target); Type returnType = methodHolder.returnType; - return GetTypeGCHandle(returnType); + return GetTypeManagedHandle(returnType); } [UnmanagedCallersOnly] @@ -488,7 +529,7 @@ namespace FlaxEngine.Interop IntPtr arr = (IntPtr)NativeAlloc(methodHolder.parameterTypes.Length, IntPtr.Size); for (int i = 0; i < methodHolder.parameterTypes.Length; i++) { - ManagedHandle typeHandle = GetTypeGCHandle(methodHolder.parameterTypes[i]); + ManagedHandle typeHandle = GetTypeManagedHandle(methodHolder.parameterTypes[i]); Unsafe.Write(IntPtr.Add(new IntPtr(arr), IntPtr.Size * i).ToPointer(), typeHandle); } *typeHandles = arr; @@ -509,22 +550,15 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle NewObject(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - if (type.IsAbstract) - { - // Dotnet doesn't allow to instantiate abstract type thus allow to use generated mock class usage (eg. for Script or GPUResource) for generated abstract types - var abstractWrapper = type.GetNestedType("AbstractWrapper", BindingFlags.NonPublic); - if (abstractWrapper != null) - type = abstractWrapper; - } - object value = RuntimeHelpers.GetUninitializedObject(type); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + object value = typeHolder.CreateObject(); return ManagedHandle.Alloc(value); } [UnmanagedCallersOnly] internal static ManagedHandle NewArray(ManagedHandle typeHandle, long size) { - Type elementType = Unsafe.As(typeHandle.Target); + Type elementType = Unsafe.As(typeHandle.Target); Type marshalledType = ArrayFactory.GetMarshalledType(elementType); Type arrayType = ArrayFactory.GetArrayType(elementType); if (marshalledType.IsValueType) @@ -543,9 +577,9 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle GetArrayTypeFromElementType(ManagedHandle elementTypeHandle) { - Type elementType = Unsafe.As(elementTypeHandle.Target); + Type elementType = Unsafe.As(elementTypeHandle.Target); Type classType = ArrayFactory.GetArrayType(elementType); - return GetTypeGCHandle(classType); + return GetTypeManagedHandle(classType); } [UnmanagedCallersOnly] @@ -606,7 +640,7 @@ namespace FlaxEngine.Interop Type classType = obj.GetType(); if (classType == typeof(ManagedArray)) classType = ((ManagedArray)obj).ArrayType; - return GetTypeGCHandle(classType); + return GetTypeManagedHandle(classType); } [UnmanagedCallersOnly] @@ -647,7 +681,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static ManagedHandle BoxValue(ManagedHandle typeHandle, IntPtr valuePtr) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); object value = MarshalToManaged(valuePtr, type); return ManagedHandle.Alloc(value, GCHandleType.Weak); } @@ -690,6 +724,14 @@ namespace FlaxEngine.Interop } } + [UnmanagedCallersOnly] + internal static IntPtr GetObjectClass(ManagedHandle objectHandle) + { + object obj = objectHandle.Target; + TypeHolder typeHolder = GetTypeHolder(obj.GetType()); + return typeHolder.managedClassPointer; + } + [UnmanagedCallersOnly] internal static IntPtr InvokeMethod(ManagedHandle instanceHandle, ManagedHandle methodHandle, IntPtr paramPtr, IntPtr exceptionPtr) { @@ -706,7 +748,7 @@ namespace FlaxEngine.Interop catch (Exception exception) { if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } return returnValue; @@ -721,7 +763,7 @@ namespace FlaxEngine.Interop for (int i = 0; i < numParams; i++) { - IntPtr nativePtr = Marshal.ReadIntPtr(IntPtr.Add(paramPtr, sizeof(IntPtr) * i)); + IntPtr nativePtr = Unsafe.Read((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer()); methodParameters[i] = MarshalToManaged(nativePtr, methodHolder.parameterTypes[i]); } @@ -737,7 +779,7 @@ namespace FlaxEngine.Interop realException = exception.InnerException; if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(realException, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(realException, GCHandleType.Weak)); else throw realException; return IntPtr.Zero; @@ -749,7 +791,7 @@ namespace FlaxEngine.Interop Type parameterType = methodHolder.parameterTypes[i]; if (parameterType.IsByRef) { - IntPtr nativePtr = Marshal.ReadIntPtr(IntPtr.Add(paramPtr, sizeof(IntPtr) * i)); + IntPtr nativePtr = Unsafe.Read((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer()); MarshalToNative(methodParameters[i], nativePtr, parameterType.GetElementType()); } } @@ -803,7 +845,7 @@ namespace FlaxEngine.Interop internal static int FieldGetOffset(ManagedHandle fieldHandle) { FieldHolder field = Unsafe.As(fieldHandle.Target); - return (int)Marshal.OffsetOf(field.field.DeclaringType, field.field.Name); + return field.fieldOffset; } [UnmanagedCallersOnly] @@ -811,7 +853,40 @@ namespace FlaxEngine.Interop { object fieldOwner = fieldOwnerHandle.Target; FieldHolder field = Unsafe.As(fieldHandle.Target); - field.toNativeMarshaller(field.field, fieldOwner, valuePtr, out int fieldOffset); + field.toNativeMarshaller(field.field, field.fieldOffset, fieldOwner, valuePtr, out int fieldSize); + } + + [UnmanagedCallersOnly] + internal static void FieldGetValueReference(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr) + { + object fieldOwner = fieldOwnerHandle.Target; + FieldHolder field = Unsafe.As(fieldHandle.Target); + if (fieldOwner.GetType().IsValueType) + { + ref IntPtr fieldRef = ref FieldHelper.GetValueTypeFieldReference(field.fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } + else + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(field.fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } + } + + [UnmanagedCallersOnly] + internal static void FieldGetValueReferenceWithOffset(ManagedHandle fieldOwnerHandle, int fieldOffset, IntPtr valuePtr) + { + object fieldOwner = fieldOwnerHandle.Target; + if (fieldOwner.GetType().IsValueType) + { + ref IntPtr fieldRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } + else + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(valuePtr.ToPointer(), fieldRef); + } } [UnmanagedCallersOnly] @@ -839,7 +914,15 @@ namespace FlaxEngine.Interop } [UnmanagedCallersOnly] - internal static ManagedHandle LoadAssemblyImage(IntPtr assemblyPathPtr, IntPtr* assemblyName, IntPtr* assemblyFullName) + internal static void GetAssemblyName(ManagedHandle assemblyHandle, IntPtr* assemblyName, IntPtr* assemblyFullName) + { + Assembly assembly = Unsafe.As(assemblyHandle.Target); + *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name); + *assemblyFullName = NativeAllocStringAnsi(assembly.FullName); + } + + [UnmanagedCallersOnly] + internal static ManagedHandle LoadAssemblyImage(IntPtr assemblyPathPtr) { if (!firstAssemblyLoaded) { @@ -847,55 +930,55 @@ namespace FlaxEngine.Interop firstAssemblyLoaded = true; Assembly flaxEngineAssembly = AssemblyLoadContext.Default.Assemblies.First(x => x.GetName().Name == "FlaxEngine.CSharp"); - *assemblyName = NativeAllocStringAnsi(flaxEngineAssembly.GetName().Name); - *assemblyFullName = NativeAllocStringAnsi(flaxEngineAssembly.FullName); return GetAssemblyHandle(flaxEngineAssembly); } + try + { + string assemblyPath = Marshal.PtrToStringUni(assemblyPathPtr); - string assemblyPath = Marshal.PtrToStringAnsi(assemblyPathPtr); - - Assembly assembly; + Assembly assembly; #if FLAX_EDITOR - // Load assembly from loaded bytes to prevent file locking in Editor - var assemblyBytes = File.ReadAllBytes(assemblyPath); - using MemoryStream stream = new MemoryStream(assemblyBytes); - var pdbPath = Path.ChangeExtension(assemblyPath, "pdb"); - if (File.Exists(pdbPath)) - { - // Load including debug symbols - using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open); - assembly = scriptingAssemblyLoadContext.LoadFromStream(stream, pdbStream); - } - else - { - assembly = scriptingAssemblyLoadContext.LoadFromStream(stream); - } + // Load assembly from loaded bytes to prevent file locking in Editor + var assemblyBytes = File.ReadAllBytes(assemblyPath); + using MemoryStream stream = new MemoryStream(assemblyBytes); + var pdbPath = Path.ChangeExtension(assemblyPath, "pdb"); + if (File.Exists(pdbPath)) + { + // Load including debug symbols + using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open); + assembly = scriptingAssemblyLoadContext.LoadFromStream(stream, pdbStream); + } + else + { + assembly = scriptingAssemblyLoadContext.LoadFromStream(stream); + } #else - // Load assembly from file - assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + // Load assembly from file + assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(assemblyPath); #endif - if (assembly == null) - return new ManagedHandle(); - NativeLibrary.SetDllImportResolver(assembly, NativeLibraryImportResolver); + if (assembly == null) + return new ManagedHandle(); + NativeLibrary.SetDllImportResolver(assembly, NativeLibraryImportResolver); - // Assemblies loaded via streams have no Location: https://github.com/dotnet/runtime/issues/12822 - AssemblyLocations.Add(assembly.FullName, assemblyPath); + // Assemblies loaded via streams have no Location: https://github.com/dotnet/runtime/issues/12822 + AssemblyLocations.Add(assembly.FullName, assemblyPath); - *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name); - *assemblyFullName = NativeAllocStringAnsi(assembly.FullName); - return GetAssemblyHandle(assembly); + return GetAssemblyHandle(assembly); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + return new ManagedHandle(); } [UnmanagedCallersOnly] - internal static ManagedHandle GetAssemblyByName(IntPtr namePtr, IntPtr* assemblyName, IntPtr* assemblyFullName) + internal static ManagedHandle GetAssemblyByName(IntPtr namePtr) { string name = Marshal.PtrToStringAnsi(namePtr); Assembly assembly = Utils.GetAssemblies().FirstOrDefault(x => x.GetName().Name == name); if (assembly == null) return new ManagedHandle(); - - *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name); - *assemblyFullName = NativeAllocStringAnsi(assembly.FullName); return GetAssemblyHandle(assembly); } @@ -934,9 +1017,9 @@ namespace FlaxEngine.Interop // Release all references in collectible ALC cachedDelegatesCollectible.Clear(); - foreach (var pair in typeHandleCacheCollectible) - pair.Value.Free(); - typeHandleCacheCollectible.Clear(); + foreach (var pair in managedTypesCollectible) + pair.Value.handle.Free(); + managedTypesCollectible.Clear(); foreach (var handle in methodHandlesCollectible) handle.Free(); methodHandlesCollectible.Clear(); @@ -966,7 +1049,7 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static int NativeSizeOf(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); Type nativeType = GetInternalType(type) ?? type; if (nativeType == typeof(Version)) nativeType = typeof(NativeVersion); @@ -984,8 +1067,8 @@ namespace FlaxEngine.Interop if (typeHandle == otherTypeHandle) return 1; - Type type = Unsafe.As(typeHandle.Target); - Type otherType = Unsafe.As(otherTypeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); + Type otherType = Unsafe.As(otherTypeHandle.Target); if (type == otherType) return 1; @@ -1002,37 +1085,39 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static byte TypeIsAssignableFrom(ManagedHandle typeHandle, ManagedHandle otherTypeHandle) { - Type type = Unsafe.As(typeHandle.Target); - Type otherType = Unsafe.As(otherTypeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); + Type otherType = Unsafe.As(otherTypeHandle.Target); return (byte)(type.IsAssignableFrom(otherType) ? 1 : 0); } [UnmanagedCallersOnly] internal static byte TypeIsValueType(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); return (byte)(type.IsValueType ? 1 : 0); } [UnmanagedCallersOnly] internal static byte TypeIsEnum(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); return (byte)(type.IsEnum ? 1 : 0); } [UnmanagedCallersOnly] - internal static ManagedHandle GetClassParent(ManagedHandle typeHandle) + internal static IntPtr GetClassParent(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - return GetTypeGCHandle(type.BaseType); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + TypeHolder baseTypeHolder = GetTypeHolder(typeHolder.type.BaseType); + return baseTypeHolder.managedClassPointer; } [UnmanagedCallersOnly] - internal static ManagedHandle GetElementClass(ManagedHandle typeHandle) + internal static IntPtr GetElementClass(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - return GetTypeGCHandle(type.GetElementType()); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + TypeHolder elementTypeHolder = GetTypeHolder(typeHolder.type.GetElementType()); + return elementTypeHolder.managedClassPointer; } [UnmanagedCallersOnly] @@ -1123,32 +1208,35 @@ namespace FlaxEngine.Interop } [UnmanagedCallersOnly] - internal static ManagedHandle GetTypeClass(ManagedHandle typeHandle) + internal static IntPtr GetTypeClass(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - if (type.IsByRef) - type = type.GetElementType(); // Drop reference type (&) to get actual value type - return GetTypeGCHandle(type); + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + if (typeHolder.type.IsByRef) + { + // Drop reference type (&) to get actual value type + return GetTypeHolder(typeHolder.type.GetElementType()).managedClassPointer; + } + return typeHolder.managedClassPointer; } [UnmanagedCallersOnly] internal static bool GetTypeIsPointer(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); return type.IsPointer; } [UnmanagedCallersOnly] internal static bool GetTypeIsReference(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); - return type.IsByRef; + Type type = Unsafe.As(typeHandle.Target); + return !type.IsValueType; // Maybe also type.IsByRef? } [UnmanagedCallersOnly] internal static uint GetTypeMTypesEnum(ManagedHandle typeHandle) { - Type type = Unsafe.As(typeHandle.Target); + Type type = Unsafe.As(typeHandle.Target); if (type.IsByRef) type = type.GetElementType(); // Drop reference type (&) to get actual value type MTypes monoType; diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 8a8d543f1..500636290 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -24,7 +24,8 @@ namespace FlaxEngine.Interop /// /// Provides a Mono-like API for native code to access managed runtime. /// - internal static unsafe partial class NativeInterop + [HideInEditor] + public static unsafe partial class NativeInterop { internal static Dictionary AssemblyLocations = new(); @@ -37,12 +38,12 @@ namespace FlaxEngine.Interop private static List methodHandles = new(); private static ConcurrentDictionary cachedDelegates = new(); - private static Dictionary typeHandleCache = new(); + private static Dictionary managedTypes = new(new TypeComparer()); private static List fieldHandleCache = new(); #if FLAX_EDITOR private static List methodHandlesCollectible = new(); private static ConcurrentDictionary cachedDelegatesCollectible = new(); - private static Dictionary typeHandleCacheCollectible = new(); + private static Dictionary managedTypesCollectible = new(new TypeComparer()); private static List fieldHandleCacheCollectible = new(); #endif private static Dictionary classAttributesCacheCollectible = new(); @@ -118,6 +119,38 @@ namespace FlaxEngine.Interop { } + // Cache offsets to frequently accessed fields of FlaxEngine.Object + private static int unmanagedPtrFieldOffset = IntPtr.Size + (Unsafe.Read((typeof(FlaxEngine.Object).GetField("__unmanagedPtr", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); + private static int internalIdFieldOffset = IntPtr.Size + (Unsafe.Read((typeof(FlaxEngine.Object).GetField("__internalId", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); + + [UnmanagedCallersOnly] + internal static void ScriptingObjectSetInternalValues(ManagedHandle objectHandle, IntPtr unmanagedPtr, IntPtr idPtr) + { + object obj = objectHandle.Target; + if (obj is not Object) + return; + + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); + fieldRef = unmanagedPtr; + } + + if (idPtr != IntPtr.Zero) + { + ref Guid nativeId = ref Unsafe.AsRef(idPtr.ToPointer()); + ref Guid fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(internalIdFieldOffset, ref obj); + fieldRef = nativeId; + } + } + + [UnmanagedCallersOnly] + internal static ManagedHandle ScriptingObjectCreate(ManagedHandle typeHandle, IntPtr unmanagedPtr, IntPtr idPtr) + { + TypeHolder typeHolder = Unsafe.As(typeHandle.Target); + object obj = typeHolder.CreateScriptingObject(unmanagedPtr, idPtr); + return ManagedHandle.Alloc(obj); + } + internal static void* NativeAlloc(int byteCount) { return NativeMemory.AlignedAlloc((UIntPtr)byteCount, 16); @@ -147,7 +180,27 @@ namespace FlaxEngine.Interop NativeMemory.AlignedFree(ptr); } - internal static T[] GCHandleArrayToManagedArray(ManagedArray ptrArray) where T : class + /// + /// Converts a delegate into a function pointer that is callable from unmanaged code via but cached delegate to prevent collecting it by GC. + /// + /// The type of delegate to convert. + /// The delegate to be passed to unmanaged code. + /// A value that can be passed to unmanaged code, which, in turn, can use it to call the underlying managed delegate. + public static IntPtr GetFunctionPointerForDelegate(TDelegate d) where TDelegate : notnull + { + // Example use-case: C# script runs actions via JobSystem.Dispatch which causes crash due to GC collecting Delegate object + ManagedHandle.Alloc(d, GCHandleType.Weak); + + return Marshal.GetFunctionPointerForDelegate(d); + } + + /// + /// Converts array of GC Handles from native runtime to managed array. + /// + /// Array element type. + /// Input array. + /// Output array. + public static T[] GCHandleArrayToManagedArray(ManagedArray ptrArray) where T : class { Span span = ptrArray.ToSpan(); T[] managedArray = new T[ptrArray.Length]; @@ -156,7 +209,12 @@ namespace FlaxEngine.Interop return managedArray; } - internal static IntPtr[] ManagedArrayToGCHandleArray(Array array) + /// + /// Converts managed array wrapper into array of GC Handles for native runtime. + /// + /// Input array. + /// Output array. + public static IntPtr[] ManagedArrayToGCHandleArray(Array array) { if (array.Length == 0) return Array.Empty(); @@ -170,13 +228,26 @@ namespace FlaxEngine.Interop return pointerArray; } - internal static ManagedArray ManagedArrayToGCHandleWrappedArray(Array array) + /// + /// Converts managed array wrapper into array of GC Handles for native runtime. + /// + /// Input array. + /// Output array. + public static ManagedArray ManagedArrayToGCHandleWrappedArray(Array array) { IntPtr[] pointerArray = ManagedArrayToGCHandleArray(array); return ManagedArray.WrapNewArray(pointerArray, array.GetType()); } - internal static TDst[] ConvertArray(Span src, Func convertFunc) + /// + /// Converts array with a custom converter function for each element. + /// + /// Input data type. + /// Output data type. + /// The input array. + /// Converter callback. + /// The output array. + public static TDst[] ConvertArray(Span src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) @@ -184,7 +255,15 @@ namespace FlaxEngine.Interop return dst; } - internal static TDst[] ConvertArray(TSrc[] src, Func convertFunc) + /// + /// Converts array with a custom converter function for each element. + /// + /// Input data type. + /// Output data type. + /// The input array. + /// Converter callback. + /// The output array. + public static TDst[] ConvertArray(TSrc[] src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) @@ -279,19 +358,12 @@ namespace FlaxEngine.Interop return FindType(internalAssemblyQualifiedName); } - internal class ReferenceTypePlaceholder - { - } - - internal struct ValueTypePlaceholder - { - } + internal class ReferenceTypePlaceholder { } + internal struct ValueTypePlaceholder { } internal delegate object MarshalToManagedDelegate(IntPtr nativePtr, bool byRef); - internal delegate void MarshalToNativeDelegate(object managedObject, IntPtr nativePtr); - - internal delegate void MarshalToNativeFieldDelegate(FieldInfo field, object fieldOwner, IntPtr nativePtr, out int fieldOffset); + internal delegate void MarshalToNativeFieldDelegate(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize); internal static ConcurrentDictionary toManagedMarshallers = new ConcurrentDictionary(1, 3); internal static ConcurrentDictionary toNativeMarshallers = new ConcurrentDictionary(1, 3); @@ -333,7 +405,7 @@ namespace FlaxEngine.Interop deleg(managedObject, nativePtr); } - internal static MarshalToNativeFieldDelegate GetToNativeFieldMarshallerDelegate(Type type) + internal static MarshalToNativeFieldDelegate GetToNativeFieldMarshallerDelegate(FieldInfo field, Type type) { static MarshalToNativeFieldDelegate Factory(Type type) { @@ -350,9 +422,46 @@ namespace FlaxEngine.Interop return toNativeFieldMarshallers.GetOrAdd(type, Factory); } - internal static void MarshalToNativeField(FieldInfo field, object fieldOwner, IntPtr nativePtr, out int fieldOffset) + internal static class FieldHelper { - GetToNativeFieldMarshallerDelegate(fieldOwner.GetType())(field, fieldOwner, nativePtr, out fieldOffset); + /// + /// Returns the address of the field, relative to field owner. + /// + internal static int GetFieldOffset(FieldInfo field, Type type) + { + // Get the address of the field, source: https://stackoverflow.com/a/56512720 + int fieldOffset = Unsafe.Read((field.FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF; + if (!type.IsValueType) + fieldOffset += IntPtr.Size; + return fieldOffset; + } + + /// + /// Returns a reference to the value of the field. + /// + internal static ref TField GetReferenceTypeFieldReference(int fieldOffset, ref object fieldOwner) + { + byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + fieldOffset; + return ref Unsafe.AsRef(fieldPtr); + } + + /// + /// Returns a reference to the value of the field. + /// + internal static ref TField GetValueTypeFieldReference(int fieldOffset, ref T fieldOwner) //where T : struct + { + byte* fieldPtr = (byte*)Unsafe.AsPointer(ref fieldOwner) + fieldOffset; + return ref Unsafe.AsRef(fieldPtr); + } + + /// + /// Returns a reference to the value of the field. + /// + internal static ref TField GetReferenceTypeFieldReference(int fieldOffset, ref T fieldOwner) //where T : class + { + byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + fieldOffset; + return ref Unsafe.AsRef(fieldPtr); + } } /// @@ -361,12 +470,12 @@ namespace FlaxEngine.Interop internal static class MarshalHelper { private delegate void MarshalToNativeTypedDelegate(ref T managedValue, IntPtr nativePtr); - private delegate void MarshalToManagedTypedDelegate(ref T managedValue, IntPtr nativePtr, bool byRef); - - internal delegate void MarshalFieldTypedDelegate(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset); + internal delegate void MarshalFieldTypedDelegate(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize); + internal delegate void* GetBasePointer(ref T fieldOwner); internal static FieldInfo[] marshallableFields; + internal static int[] marshallableFieldOffsets; internal static MarshalFieldTypedDelegate[] toManagedFieldMarshallers; internal static MarshalFieldTypedDelegate[] toNativeFieldMarshallers; @@ -377,28 +486,9 @@ namespace FlaxEngine.Interop { Type type = typeof(T); - // Setup marshallers for managed and native directions - MethodInfo toManagedMethod; - if (type.IsValueType) - toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToManaged), BindingFlags.Static | BindingFlags.NonPublic); - else if (type.IsArray && type.GetElementType().IsValueType) - toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type.GetElementType()).GetMethod(nameof(MarshalHelperValueType.ToManagedArray), BindingFlags.Static | BindingFlags.NonPublic); - else if (type.IsArray && !type.GetElementType().IsValueType) - toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type.GetElementType()).GetMethod(nameof(MarshalHelperReferenceType.ToManagedArray), BindingFlags.Static | BindingFlags.NonPublic); - else - toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperReferenceType.ToManaged), BindingFlags.Static | BindingFlags.NonPublic); - toManagedTypedMarshaller = toManagedMethod.CreateDelegate(); - - MethodInfo toNativeMethod; - if (type.IsValueType) - toNativeMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNative), BindingFlags.Static | BindingFlags.NonPublic); - else - toNativeMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperReferenceType.ToNative), BindingFlags.Static | BindingFlags.NonPublic); - toNativeTypedMarshaller = toNativeMethod.CreateDelegate(); - + // Setup field-by-field marshallers for reference types or structures containing references if (!type.IsPrimitive && !type.IsPointer && type != typeof(bool)) { - // Setup field-by-field marshallers for reference types or structures containing references marshallableFields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (type.IsValueType && !marshallableFields.Any(x => (x.FieldType.IsClass && !x.FieldType.IsPointer) || x.FieldType.Name == "Boolean")) marshallableFields = null; @@ -409,6 +499,7 @@ namespace FlaxEngine.Interop { toManagedFieldMarshallers = new MarshalFieldTypedDelegate[marshallableFields.Length]; toNativeFieldMarshallers = new MarshalFieldTypedDelegate[marshallableFields.Length]; + marshallableFieldOffsets = new int[marshallableFields.Length]; BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public; for (int i = 0; i < marshallableFields.Length; i++) { @@ -419,8 +510,16 @@ namespace FlaxEngine.Interop if (fieldType.IsPointer) { - toManagedFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToManagedFieldPointer), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToNativeFieldPointer), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToManagedFieldPointerValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToNativeFieldPointerValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToManagedFieldPointerReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToNativeFieldPointerReferenceType), bindingFlags); + } } else if (fieldType.IsValueType) { @@ -431,8 +530,16 @@ namespace FlaxEngine.Interop } else { - toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedField), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToNativeFieldReferenceType), bindingFlags); + } } } else if (fieldType.IsArray) @@ -440,25 +547,124 @@ namespace FlaxEngine.Interop Type arrayElementType = fieldType.GetElementType(); if (arrayElementType.IsValueType) { - toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldArray), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldArrayValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldArrayReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); + } } else { - toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArray), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArrayValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArrayReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); + } } } else { - toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedField), bindingFlags); - toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeField), bindingFlags); + if (type.IsValueType) + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldValueType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); + } + else + { + toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldReferenceType), bindingFlags); + toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); + } } toManagedFieldMarshallers[i] = toManagedFieldMethod.CreateDelegate(); toNativeFieldMarshallers[i] = toNativeFieldMethod.CreateDelegate(); + marshallableFieldOffsets[i] = FieldHelper.GetFieldOffset(field, type); } } } + + // Setup marshallers for managed and native directions + MethodInfo toManagedMethod; + if (type.IsValueType) + { + string methodName; + if (type == typeof(IntPtr)) + methodName = nameof(MarshalHelperValueType.ToManagedPointer); + else if (type == typeof(ManagedHandle)) + methodName = nameof(MarshalHelperValueType.ToManagedHandle); + else if (marshallableFields != null) + methodName = nameof(MarshalHelperValueType.ToManagedWithMarshallableFields); + else + methodName = nameof(MarshalHelperValueType.ToManaged); + toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + else if (type.IsArray) + { + Type elementType = type.GetElementType(); + if (elementType.IsValueType) + { + string methodName; + if (ArrayFactory.GetMarshalledType(elementType) == elementType) + methodName = nameof(MarshalHelperValueType.ToManagedArray); + else + methodName = nameof(MarshalHelperValueType.ToManagedArrayMarshalled); + toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type.GetElementType()).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + else + toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type.GetElementType()).GetMethod(nameof(MarshalHelperReferenceType.ToManagedArray), BindingFlags.Static | BindingFlags.NonPublic); + } + else + { + string methodName; + if (type == typeof(string)) + methodName = nameof(MarshalHelperReferenceType.ToManagedString); + else if (type == typeof(Type)) + methodName = nameof(MarshalHelperReferenceType.ToManagedType); + else if (type.IsClass) + methodName = nameof(MarshalHelperReferenceType.ToManagedClass); + else if (type.IsInterface) // Dictionary + methodName = nameof(MarshalHelperReferenceType.ToManagedInterface); + else + throw new NativeInteropException($"Unsupported type '{type.FullName}'"); + toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + toManagedTypedMarshaller = toManagedMethod.CreateDelegate(); + + MethodInfo toNativeMethod; + if (type.IsValueType) + { + if (type.IsByRef) + throw new NotImplementedException(); // Is this possible? + if (marshallableFields != null) + toNativeMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNativeWithMarshallableFields), BindingFlags.Static | BindingFlags.NonPublic); + else + toNativeMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNative), BindingFlags.Static | BindingFlags.NonPublic); + } + else + { + string methodName; + if (type == typeof(string)) + methodName = nameof(MarshalHelperReferenceType.ToNativeString); + else if (type == typeof(Type)) + methodName = nameof(MarshalHelperReferenceType.ToNativeType); + else if (type.IsPointer) + methodName = nameof(MarshalHelperReferenceType.ToNativePointer); + else if (type.IsArray) + methodName = nameof(MarshalHelperReferenceType.ToNativeArray); + else + methodName = nameof(MarshalHelperReferenceType.ToNative); + toNativeMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); + } + toNativeTypedMarshaller = toNativeMethod.CreateDelegate(); } internal static object ToManagedWrapper(IntPtr nativePtr, bool byRef) @@ -476,16 +682,12 @@ namespace FlaxEngine.Interop [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static T ToManagedUnbox(IntPtr nativePtr) { - T managed = default; - if (nativePtr != IntPtr.Zero) - { - Type type = typeof(T); - if (type.IsArray) - managed = (T)MarshalToManaged(nativePtr, type); // Array might be in internal format of custom structs so unbox if need to - else - managed = (T)ManagedHandle.FromIntPtr(nativePtr).Target; - } - return managed; + T value = default; + if (nativePtr == IntPtr.Zero) + return value; + + MarshalHelper.ToManaged(ref value, nativePtr, false); + return value; } internal static Array ToManagedArray(Span ptrSpan) @@ -513,52 +715,48 @@ namespace FlaxEngine.Interop toNativeTypedMarshaller(ref managedValue, nativePtr); } - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr nativePtr, out int fieldOffset) + internal static void ToNativeField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativePtr, out int fieldSize) { if (marshallableFields != null) { for (int i = 0; i < marshallableFields.Length; i++) { - if (marshallableFields[i] == field) + if (marshallableFieldOffsets[i] == fieldOffset) { - toNativeFieldMarshallers[i](marshallableFields[i], ref fieldOwner, nativePtr, out fieldOffset); + toNativeFieldMarshallers[i](field, fieldOffset, ref fieldOwner, nativePtr, out fieldSize); return; } } } - throw new NativeInteropException($"Invalid field {field.Name} to marshal for type {typeof(T).Name}"); + throw new NativeInteropException($"Invalid field with offset {fieldOffset} to marshal for type {typeof(T).Name}"); } - private static void ToManagedFieldPointer(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + private static void ToManagedFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - ref IntPtr fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - fieldValueRef = Unsafe.Read(fieldPtr.ToPointer()); - fieldOffset = IntPtr.Size; + ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); + fieldSize = IntPtr.Size; } - private static void ToNativeFieldPointer(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + private static void ToManagedFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { - ref IntPtr fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - Unsafe.Write(fieldPtr.ToPointer(), fieldValueRef); - fieldOffset = IntPtr.Size; + ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); + fieldSize = IntPtr.Size; } - /// - /// Returns a reference to the value of the field. - /// - private static ref TField GetFieldReference(FieldInfo field, ref T fieldOwner) + private static void ToNativeFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - // Get the address of the field, source: https://stackoverflow.com/a/56512720 - if (typeof(T).IsValueType) - { - byte* fieldPtr = (byte*)Unsafe.AsPointer(ref fieldOwner) + (Marshal.ReadInt32(field.FieldHandle.Value + 4 + IntPtr.Size) & 0xFFFFFF); - return ref Unsafe.AsRef(fieldPtr); - } - else - { - byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + IntPtr.Size + (Marshal.ReadInt32(field.FieldHandle.Value + 4 + IntPtr.Size) & 0xFFFFFF); - return ref Unsafe.AsRef(fieldPtr); - } + ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef); + fieldSize = IntPtr.Size; + } + + private static void ToNativeFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValueRef); + fieldSize = IntPtr.Size; } private static IntPtr EnsureAlignment(IntPtr ptr, int alignment) @@ -589,48 +787,92 @@ namespace FlaxEngine.Interop fieldAlignment = GetTypeSize(fieldType); } - internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - fieldOffset = Unsafe.SizeOf(); + fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) { - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, fieldAlignment); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); } - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperValueType.ToManaged(ref fieldValueRef, fieldPtr, false); + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false); } - internal static void ToManagedFieldArray(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { - // Follows the same marshalling semantics with reference types - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); - - ref TField[] fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperValueType.ToManagedArray(ref fieldValueRef, Unsafe.Read(fieldPtr.ToPointer()), false); - } - - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) - { - fieldOffset = Unsafe.SizeOf(); + fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) { - IntPtr startPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, fieldAlignment); - fieldOffset += (fieldPtr - startPtr).ToInt32(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + } + + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, nativeFieldPtr, false); + } + + internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + { + // Follows the same marshalling semantics with reference types + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + // Follows the same marshalling semantics with reference types + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + { + fieldSize = Unsafe.SizeOf(); + if (fieldAlignment > 1) + { + IntPtr startPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - startPtr).ToInt32(); } #if USE_AOT TField fieldValueRef = (TField)field.GetValue(fieldOwner); #else - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); #endif - MarshalHelperValueType.ToNative(ref fieldValueRef, fieldPtr); + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); + } + + internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + fieldSize = Unsafe.SizeOf(); + if (fieldAlignment > 1) + { + IntPtr startPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); + fieldSize += (nativeFieldPtr - startPtr).ToInt32(); + } + +#if USE_AOT + TField fieldValueRef = (TField)field.GetValue(fieldOwner); +#else + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); +#endif + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); } } @@ -640,50 +882,83 @@ namespace FlaxEngine.Interop { } - internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) { - fieldOffset = 0; + fieldSize = 0; } - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToNativeField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) { - fieldOffset = 0; + fieldSize = 0; } } private static class ReferenceTypeField where TField : class { - internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperReferenceType.ToManaged(ref fieldValueRef, Unsafe.Read(fieldPtr.ToPointer()), false); + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToManagedFieldArray(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField[] fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperReferenceType.ToManagedArray(ref fieldValueRef, Unsafe.Read(fieldPtr.ToPointer()), false); + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); } - internal static void ToNativeField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset) + internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { - fieldOffset = Unsafe.SizeOf(); - IntPtr fieldStartPtr = fieldPtr; - fieldPtr = EnsureAlignment(fieldPtr, IntPtr.Size); - fieldOffset += (fieldPtr - fieldStartPtr).ToInt32(); + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); - ref TField fieldValueRef = ref GetFieldReference(field, ref fieldOwner); - MarshalHelperReferenceType.ToNative(ref fieldValueRef, fieldPtr); + ref TField[] fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField[] fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToManaged(ref fieldValueRef, Unsafe.Read(nativeFieldPtr.ToPointer()), false); + } + + internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct + { + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); + } + + internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class + { + fieldSize = Unsafe.SizeOf(); + IntPtr fieldStartPtr = nativeFieldPtr; + nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); + fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); + + ref TField fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); + MarshalHelper.ToNative(ref fieldValueRef, nativeFieldPtr); } } } @@ -692,80 +967,92 @@ namespace FlaxEngine.Interop { internal static void ToNativeWrapper(object managedObject, IntPtr nativePtr) { - ToNative(ref Unsafe.Unbox(managedObject), nativePtr); + MarshalHelper.ToNative(ref Unsafe.Unbox(managedObject), nativePtr); } - internal static void ToNativeFieldWrapper(FieldInfo field, object fieldOwner, IntPtr nativePtr, out int fieldOffset) + internal static void ToNativeFieldWrapper(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize) { - MarshalHelper.ToNativeField(field, ref Unsafe.Unbox(fieldOwner), nativePtr, out fieldOffset); + MarshalHelper.ToNativeField(field, fieldOffset, ref Unsafe.Unbox(fieldOwner), nativePtr, out fieldSize); + } + + internal static void ToManagedPointer(ref IntPtr managedValue, IntPtr nativePtr, bool byRef) + { + Type type = typeof(T); + byRef |= type.IsByRef; // Is this needed? + if (type.IsByRef) + Assert.IsTrue(type.GetElementType().IsValueType); + managedValue = byRef ? nativePtr : Unsafe.Read(nativePtr.ToPointer()); + } + + internal static void ToManagedHandle(ref ManagedHandle managedValue, IntPtr nativePtr, bool byRef) + { + managedValue = ManagedHandle.FromIntPtr(nativePtr); + } + + internal static void ToManagedWithMarshallableFields(ref T managedValue, IntPtr nativePtr, bool byRef) + { + IntPtr fieldPtr = nativePtr; + var fields = MarshalHelper.marshallableFields; + var offsets = MarshalHelper.marshallableFieldOffsets; + var marshallers = MarshalHelper.toManagedFieldMarshallers; + for (int i = 0; i < fields.Length; i++) + { + marshallers[i](fields[i], offsets[i], ref managedValue, fieldPtr, out int fieldSize); + fieldPtr += fieldSize; + } + Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf()); } internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef) { - Type type = typeof(T); - byRef |= type.IsByRef; - if (byRef) - { - if (type.IsByRef) - type = type.GetElementType(); - Assert.IsTrue(type.IsValueType); - } - - if (type == typeof(IntPtr) && byRef) - managedValue = (T)(object)nativePtr; - else if (type == typeof(ManagedHandle)) - managedValue = (T)(object)ManagedHandle.FromIntPtr(nativePtr); - else if (MarshalHelper.marshallableFields != null) - { - IntPtr fieldPtr = nativePtr; - for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) - { - MarshalHelper.toManagedFieldMarshallers[i](MarshalHelper.marshallableFields[i], ref managedValue, fieldPtr, out int fieldOffset); - fieldPtr += fieldOffset; - } - Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf()); - } - else - managedValue = Unsafe.Read(nativePtr.ToPointer()); + managedValue = Unsafe.Read(nativePtr.ToPointer()); } internal static void ToManagedArray(ref T[] managedValue, IntPtr nativePtr, bool byRef) { if (byRef) - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); - Type elementType = typeof(T); if (nativePtr != IntPtr.Zero) { ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); - if (ArrayFactory.GetMarshalledType(elementType) == elementType) - managedValue = Unsafe.As(managedArray.ToArray()); - else if (elementType.IsValueType) - managedValue = Unsafe.As(MarshalHelper.ToManagedArray(managedArray)); - else - managedValue = Unsafe.As(MarshalHelper.ToManagedArray(managedArray.ToSpan())); + managedValue = Unsafe.As(managedArray.ToArray()); } else managedValue = null; } - internal static void ToNative(ref T managedValue, IntPtr nativePtr) + internal static void ToManagedArrayMarshalled(ref T[] managedValue, IntPtr nativePtr, bool byRef) { - if (typeof(T).IsByRef) - throw new NotImplementedException(); + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); - if (MarshalHelper.marshallableFields != null) + if (nativePtr != IntPtr.Zero) { - IntPtr fieldPtr = nativePtr; - for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) - { - MarshalHelper.toNativeFieldMarshallers[i](MarshalHelper.marshallableFields[i], ref managedValue, nativePtr, out int fieldOffset); - nativePtr += fieldOffset; - } - Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf()); + ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); + managedValue = Unsafe.As(MarshalHelper.ToManagedArray(managedArray)); } else - Unsafe.AsRef(nativePtr.ToPointer()) = managedValue; + managedValue = null; + } + + internal static void ToNativeWithMarshallableFields(ref T managedValue, IntPtr nativePtr) + { + IntPtr fieldPtr = nativePtr; + var fields = MarshalHelper.marshallableFields; + var offsets = MarshalHelper.marshallableFieldOffsets; + var marshallers = MarshalHelper.toNativeFieldMarshallers; + for (int i = 0; i < MarshalHelper.marshallableFields.Length; i++) + { + marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize); + nativePtr += fieldSize; + } + Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf()); + } + + internal static void ToNative(ref T managedValue, IntPtr nativePtr) + { + Unsafe.AsRef(nativePtr.ToPointer()) = managedValue; } } @@ -774,37 +1061,47 @@ namespace FlaxEngine.Interop internal static void ToNativeWrapper(object managedObject, IntPtr nativePtr) { T managedValue = Unsafe.As(managedObject); - ToNative(ref managedValue, nativePtr); + MarshalHelper.ToNative(ref managedValue, nativePtr); } - internal static void ToNativeFieldWrapper(FieldInfo field, object managedObject, IntPtr nativePtr, out int fieldOffset) + internal static void ToNativeFieldWrapper(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize) { - T managedValue = Unsafe.As(managedObject); - MarshalHelper.ToNativeField(field, ref managedValue, nativePtr, out fieldOffset); + T managedValue = Unsafe.As(fieldOwner); + MarshalHelper.ToNativeField(field, fieldOffset, ref managedValue, nativePtr, out fieldSize); } - internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef) + internal static void ToManagedString(ref string managedValue, IntPtr nativePtr, bool byRef) { - Type type = typeof(T); if (byRef) - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = ManagedString.ToManaged(nativePtr); + } - if (type == typeof(string)) - managedValue = Unsafe.As(ManagedString.ToManaged(nativePtr)); - else if (nativePtr == IntPtr.Zero) - managedValue = null; - else if (type.IsClass) - managedValue = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); - else if (type.IsInterface) // Dictionary - managedValue = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); - else - throw new NotImplementedException(); + internal static void ToManagedType(ref Type managedValue, IntPtr nativePtr, bool byRef) + { + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); + } + + internal static void ToManagedClass(ref T managedValue, IntPtr nativePtr, bool byRef) + { + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); + } + + internal static void ToManagedInterface(ref T managedValue, IntPtr nativePtr, bool byRef) // Dictionary + { + if (byRef) + nativePtr = Unsafe.Read(nativePtr.ToPointer()); + managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); } internal static void ToManagedArray(ref T[] managedValue, IntPtr nativePtr, bool byRef) { if (byRef) - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); if (nativePtr != IntPtr.Zero) { @@ -815,56 +1112,64 @@ namespace FlaxEngine.Interop managedValue = null; } + + internal static void ToNativeString(ref string managedValue, IntPtr nativePtr) + { + Unsafe.Write(nativePtr.ToPointer(), ManagedString.ToNativeWeak(managedValue)); + } + + internal static void ToNativeType(ref Type managedValue, IntPtr nativePtr) + { + Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(managedValue)) : IntPtr.Zero); + } + + internal static void ToNativePointer(ref T managedValue, IntPtr nativePtr) + { + IntPtr managedPtr; + if (Pointer.Unbox(managedValue) == null) + managedPtr = IntPtr.Zero; + else if (managedValue is FlaxEngine.Object obj) + managedPtr = FlaxEngine.Object.GetUnmanagedPtr(obj); + else + managedPtr = ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak); + Unsafe.Write(nativePtr.ToPointer(), managedPtr); + } + + internal static void ToNativeArray(ref T managedValue, IntPtr nativePtr) + { + IntPtr managedPtr; + if (managedValue == null) + managedPtr = IntPtr.Zero; + else + { + Type type = typeof(T); + var elementType = type.GetElementType(); + var arr = Unsafe.As(managedValue); + var marshalledType = ArrayFactory.GetMarshalledType(elementType); + ManagedArray managedArray; + if (marshalledType == elementType) + managedArray = ManagedArray.WrapNewArray(arr, type); + else if (elementType.IsValueType) + { + // Convert array of custom structures into internal native layout + managedArray = ManagedArray.AllocateNewArray(arr.Length, type, marshalledType); + IntPtr managedArrayPtr = managedArray.Pointer; + for (int i = 0; i < arr.Length; i++) + { + MarshalToNative(arr.GetValue(i), managedArrayPtr, elementType); + managedArrayPtr += managedArray.ElementSize; + } + } + else + managedArray = ManagedArrayToGCHandleWrappedArray(arr); + managedPtr = ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak); + } + Unsafe.Write(nativePtr.ToPointer(), managedPtr); + } + internal static void ToNative(ref T managedValue, IntPtr nativePtr) { - Type type = typeof(T); - - IntPtr managedPtr; - if (type == typeof(string)) - managedPtr = ManagedString.ToNativeWeak(managedValue as string); - else if (type.IsPointer) - { - if (Pointer.Unbox(managedValue) == null) - managedPtr = IntPtr.Zero; - else if (managedValue is FlaxEngine.Object flaxObj) - managedPtr = FlaxEngine.Object.GetUnmanagedPtr(flaxObj); - else - managedPtr = ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak); - } - else if (type == typeof(Type)) - managedPtr = managedValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle((Type)(object)managedValue)) : IntPtr.Zero; - else if (type.IsArray) - { - if (managedValue == null) - managedPtr = IntPtr.Zero; - else - { - var elementType = type.GetElementType(); - var arr = Unsafe.As(managedValue); - var marshalledType = ArrayFactory.GetMarshalledType(elementType); - ManagedArray managedArray; - if (marshalledType == elementType) - managedArray = ManagedArray.WrapNewArray(arr, type); - else if (elementType.IsValueType) - { - // Convert array of custom structures into internal native layout - managedArray = ManagedArray.AllocateNewArray(arr.Length, type, marshalledType); - IntPtr managedArrayPtr = managedArray.Pointer; - for (int i = 0; i < arr.Length; i++) - { - MarshalToNative(arr.GetValue(i), managedArrayPtr, elementType); - managedArrayPtr += managedArray.ElementSize; - } - } - else - managedArray = ManagedArrayToGCHandleWrappedArray(arr); - managedPtr = ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak); - } - } - else - managedPtr = managedValue != null ? ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak) : IntPtr.Zero; - - Unsafe.Write(nativePtr.ToPointer(), managedPtr); + Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak) : IntPtr.Zero); } } @@ -955,14 +1260,82 @@ namespace FlaxEngine.Interop { internal FieldInfo field; internal MarshalToNativeFieldDelegate toNativeMarshaller; + internal int fieldOffset; internal FieldHolder(FieldInfo field, Type type) { this.field = field; - toNativeMarshaller = GetToNativeFieldMarshallerDelegate(type); + toNativeMarshaller = GetToNativeFieldMarshallerDelegate(field, type); + fieldOffset = FieldHelper.GetFieldOffset(field, type); } } + internal class TypeComparer : IEqualityComparer + { + public bool Equals(Type x, Type y) => x == y; + public int GetHashCode(Type obj) => obj.GetHashCode(); + } + + internal class TypeHolder + { + internal Type type; + internal Type wrappedType; + internal ConstructorInfo ctor; + internal IntPtr managedClassPointer; // MClass* + + internal TypeHolder(Type type) + { + this.type = type; + wrappedType = type; + + if (type.IsAbstract) + { + // Dotnet doesn't allow to instantiate abstract type thus allow to use generated mock class usage (eg. for Script or GPUResource) for generated abstract types + var abstractWrapper = type.GetNestedType("AbstractWrapper", BindingFlags.NonPublic); + if (abstractWrapper != null) + wrappedType = abstractWrapper; + } + + ctor = wrappedType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); + } + + internal object CreateObject() + { + return RuntimeHelpers.GetUninitializedObject(wrappedType); + } + + internal object CreateScriptingObject(IntPtr unmanagedPtr, IntPtr idPtr) + { + object obj = CreateObject(); + if (obj is Object) + { + { + ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); + fieldRef = unmanagedPtr; + } + + if (idPtr != IntPtr.Zero) + { + ref Guid nativeId = ref Unsafe.AsRef(idPtr.ToPointer()); + ref Guid fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(internalIdFieldOffset, ref obj); + fieldRef = nativeId; + } + } + + if (ctor != null) + ctor.Invoke(obj, null); + else + Debug.LogException(new Exception($"Missing empty constructor in type '{wrappedType}'.")); + + return obj; + } + + public static implicit operator Type(TypeHolder holder) => holder?.type ?? null; + public bool Equals(TypeHolder other) => type == other.type; + public bool Equals(Type other) => type == other; + public override int GetHashCode() => type.GetHashCode(); + } + internal static class ArrayFactory { private delegate Array CreateArrayDelegate(long size); @@ -1024,11 +1397,12 @@ namespace FlaxEngine.Interop 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 (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); @@ -1089,7 +1463,8 @@ namespace FlaxEngine.Interop return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox(value))); } - private static IntPtr UnboxPointerWithConverter(object value, object converter) where T : struct where TInternal : struct + private static IntPtr UnboxPointerWithConverter(object value, object converter) where T : struct + where TInternal : struct { ToNativeDelegate toNative = Unsafe.As>(converter); return PinValue(toNative(Unsafe.Unbox(value))); @@ -1099,12 +1474,12 @@ namespace FlaxEngine.Interop private delegate IntPtr InvokeThunkDelegate(ManagedHandle instanceHandle, IntPtr param1, IntPtr param2, IntPtr param3, IntPtr param4, IntPtr param5, IntPtr param6, IntPtr param7); /// - /// Returns all types that that owned by this assembly. + /// Returns all types owned by this assembly. /// private static Type[] GetAssemblyTypes(Assembly assembly) { var referencedAssemblies = assembly.GetReferencedAssemblies(); - var allAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + var allAssemblies = Utils.GetAssemblies(); var referencedTypes = new List(); foreach (var assemblyName in referencedAssemblies) { @@ -1124,29 +1499,77 @@ namespace FlaxEngine.Interop return types; } - /// - /// Returns a static ManagedHandle for given Type, and caches it if needed. - /// - internal static ManagedHandle GetTypeGCHandle(Type type) + internal static TypeHolder GetTypeHolder(Type type) { - if (typeHandleCache.TryGetValue(type, out ManagedHandle handle)) - return handle; + if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) + return tuple.typeHolder; #if FLAX_EDITOR - if (typeHandleCacheCollectible.TryGetValue(type, out handle)) - return handle; + if (managedTypesCollectible.TryGetValue(type, out tuple)) + return tuple.typeHolder; #endif + return RegisterType(type, true).typeHolder; + } - handle = ManagedHandle.Alloc(type); + internal static (TypeHolder typeHolder, ManagedHandle handle) GetTypeHolderAndManagedHandle(Type type) + { + if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) + return tuple; #if FLAX_EDITOR - if (type.IsCollectible) // check if generic parameters are also collectible? - typeHandleCacheCollectible.Add(type, handle); + if (managedTypesCollectible.TryGetValue(type, out tuple)) + return tuple; +#endif + return RegisterType(type, true); + } + + /// + /// Returns a static ManagedHandle to TypeHolder for given Type, and caches it if needed. + /// + internal static ManagedHandle GetTypeManagedHandle(Type type) + { + if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) + return tuple.handle; +#if FLAX_EDITOR + if (managedTypesCollectible.TryGetValue(type, out tuple)) + return tuple.handle; +#endif + return RegisterType(type, true).handle; + } + + internal static (TypeHolder typeHolder, ManagedHandle handle) RegisterType(Type type, bool registerNativeType = false) + { + // TODO: should this strip by-ref? + + (TypeHolder typeHolder, ManagedHandle handle) tuple; + tuple.typeHolder = new TypeHolder(type); + tuple.handle = ManagedHandle.Alloc(tuple.typeHolder); +#if FLAX_EDITOR + bool isCollectible = type.IsCollectible; + if (!isCollectible && type.IsGenericType && !type.Assembly.IsCollectible) + { + // The owning assembly of a generic type with type arguments referencing + // collectible assemblies must be one of the collectible assemblies. + foreach (var genericType in type.GetGenericArguments()) + { + if (genericType.Assembly.IsCollectible) + { + isCollectible = true; + break; + } + } + } + + if (isCollectible) + managedTypesCollectible.Add(type, tuple); else #endif { - typeHandleCache.Add(type, handle); + managedTypes.Add(type, tuple); } - return handle; + if (registerNativeType) + RegisterNativeClassFromType(tuple.typeHolder, tuple.handle); + + return tuple; } internal static int GetTypeSize(Type type) @@ -1290,7 +1713,7 @@ namespace FlaxEngine.Interop // Returned exception is the last parameter IntPtr exceptionPtr = nativePtrs[parameterTypes.Length]; if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } return returnValue; @@ -1312,7 +1735,7 @@ namespace FlaxEngine.Interop if (type.IsByRef) { // References use indirection to support value returning - nativePtr = Marshal.ReadIntPtr(nativePtr); + nativePtr = Unsafe.Read(nativePtr.ToPointer()); type = elementType; } if (type.IsArray) @@ -1332,7 +1755,7 @@ namespace FlaxEngine.Interop // Returned exception is the last parameter IntPtr exceptionPtr = nativePtrs[numParams]; if (exceptionPtr != IntPtr.Zero) - Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); + Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } @@ -1346,11 +1769,11 @@ namespace FlaxEngine.Interop { type = type.GetElementType(); if (managed == null) - Marshal.WriteIntPtr(nativePtr, IntPtr.Zero); + Unsafe.Write(nativePtr.ToPointer(), IntPtr.Zero); else if (type.IsArray) MarshalToNative(managed, nativePtr, type); else - Marshal.WriteIntPtr(nativePtr, ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managed, GCHandleType.Weak))); + Unsafe.Write(nativePtr.ToPointer(), ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managed, GCHandleType.Weak))); } } diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp index 2bc6e1f58..fc7e8a022 100644 --- a/Source/Engine/Engine/Screen.cpp +++ b/Source/Engine/Engine/Screen.cpp @@ -181,6 +181,11 @@ void Screen::SetGameWindowMode(GameWindowMode windowMode) #endif } +Window* Screen::GetMainWindow() +{ + return Engine::MainWindow; +} + void ScreenService::Update() { #if USE_EDITOR diff --git a/Source/Engine/Engine/Screen.h b/Source/Engine/Engine/Screen.h index 42be20a38..b7bd89c38 100644 --- a/Source/Engine/Engine/Screen.h +++ b/Source/Engine/Engine/Screen.h @@ -96,4 +96,10 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen); /// /// The window mode. API_PROPERTY() static void SetGameWindowMode(GameWindowMode windowMode); + + /// + /// Gets the main window. + /// + /// The current window. Will be null if fails. + API_PROPERTY() static Window* GetMainWindow(); }; diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp index 785010c44..7d530dfed 100644 --- a/Source/Engine/Foliage/FoliageType.cpp +++ b/Source/Engine/Foliage/FoliageType.cpp @@ -130,7 +130,7 @@ void FoliageType::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE(Model); - const std::function IsValidMaterial = [](const ModelInstanceEntry& e) -> bool + const Function IsValidMaterial = [](const ModelInstanceEntry& e) -> bool { return e.Material; }; diff --git a/Source/Engine/Graphics/Models/SkeletonMapping.h b/Source/Engine/Graphics/Models/SkeletonMapping.h index ac2af245b..e3ec0c793 100644 --- a/Source/Engine/Graphics/Models/SkeletonMapping.h +++ b/Source/Engine/Graphics/Models/SkeletonMapping.h @@ -66,7 +66,7 @@ public: const auto parentModelIndex = node.ParentIndex; // Find matching node in skeleton (or map to best parent) - const std::function f = [node](const T& x) -> bool + const Function f = [node](const T& x) -> bool { return x.Name == node.Name; }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index d52b446e3..b0bb1c733 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -459,9 +459,9 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des { case VK_DESCRIPTOR_TYPE_SAMPLER: { - const VkSampler sampler = _samplerHandles[slot]; - ASSERT(sampler); - needsWrite |= dsWriter.WriteSampler(descriptorIndex, sampler, index); + const VkSampler handle = _samplerHandles[slot]; + ASSERT(handle); + needsWrite |= dsWriter.WriteSampler(descriptorIndex, handle, index); break; } case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: @@ -547,12 +547,18 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des } case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: { - auto cb = handles[slot]; - ASSERT(cb); - VkBuffer buffer; - VkDeviceSize offset, range; - uint32 dynamicOffset; - cb->DescriptorAsDynamicUniformBuffer(this, buffer, offset, range, dynamicOffset); + auto handle = handles[slot]; + VkBuffer buffer = VK_NULL_HANDLE; + VkDeviceSize offset = 0, range = 0; + uint32 dynamicOffset = 0; + if (handle) + handle->DescriptorAsDynamicUniformBuffer(this, buffer, offset, range, dynamicOffset); + else + { + const auto dummy = _device->HelperResources.GetDummyBuffer(); + buffer = dummy->GetHandle(); + range = dummy->GetSize(); + } needsWrite |= dsWriter.WriteDynamicUniformBuffer(descriptorIndex, buffer, offset, range, dynamicOffset, index); break; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp index bdd92c762..0b857d394 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp @@ -35,6 +35,10 @@ static const char* GValidationLayers[] = static const char* GInstanceExtensions[] = { +#if PLATFORM_APPLE_FAMILY && defined(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, +#endif #if VK_EXT_validation_cache VK_EXT_VALIDATION_CACHE_EXTENSION_NAME, #endif @@ -46,6 +50,9 @@ static const char* GInstanceExtensions[] = static const char* GDeviceExtensions[] = { +#if PLATFORM_APPLE_FAMILY && defined(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) + VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME, +#endif VK_KHR_SWAPCHAIN_EXTENSION_NAME, #if VK_KHR_maintenance1 VK_KHR_MAINTENANCE1_EXTENSION_NAME, @@ -571,7 +578,7 @@ void GPUDeviceVulkan::ParseOptionalDeviceExtensions(const Array& de const auto HasExtension = [&deviceExtensions](const char* name) -> bool { - const std::function CheckCallback = [&name](const char* const& extension) -> bool + const Function CheckCallback = [&name](const char* const& extension) -> bool { return StringUtils::Compare(extension, name) == 0; }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index eb23c0871..b4a6112a9 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -431,7 +431,7 @@ void DeferredDeletionQueueVulkan::EnqueueGenericResource(Type type, uint64 handl ScopeLock lock(_locker); #if BUILD_DEBUG - const std::function ContainsHandle = [handle](const Entry& e) + const Function ContainsHandle = [handle](const Entry& e) { return e.Handle == handle; }; @@ -868,7 +868,7 @@ GPUBufferVulkan* HelperResourcesVulkan::GetDummyBuffer() if (!_dummyBuffer) { _dummyBuffer = (GPUBufferVulkan*)_device->CreateBuffer(TEXT("DummyBuffer")); - _dummyBuffer->Init(GPUBufferDescription::Buffer(sizeof(int32), GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess, PixelFormat::R32_SInt)); + _dummyBuffer->Init(GPUBufferDescription::Buffer(sizeof(int32) * 256, GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess, PixelFormat::R32_SInt)); } return _dummyBuffer; @@ -1078,13 +1078,16 @@ GPUDevice* GPUDeviceVulkan::Create() VkInstanceCreateInfo instInfo; RenderToolsVulkan::ZeroStruct(instInfo, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO); +#if PLATFORM_APPLE_FAMILY + instInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; +#endif instInfo.pApplicationInfo = &appInfo; GetInstanceLayersAndExtensions(InstanceExtensions, InstanceLayers, SupportsDebugUtilsExt); const auto hasExtension = [](const Array& extensions, const char* name) -> bool { - const std::function callback = [&name](const char* const& extension) -> bool + const Function callback = [&name](const char* const& extension) -> bool { return extension && StringUtils::Compare(extension, name) == 0; }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h b/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h index 3a1b6a615..4e2694aab 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h +++ b/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h @@ -41,4 +41,15 @@ #define VMA_NOT_NULL #include +#if PLATFORM_APPLE_FAMILY +// Declare potentially missing extensions from newer SDKs +#ifndef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME +#define VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME "VK_KHR_portability_enumeration" +#define VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR 0x00000001 +#endif +#ifndef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME +#define VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME "VK_KHR_portability_subset" +#endif +#endif + #endif diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index 661b350cb..dbe8a89b5 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -364,13 +364,20 @@ namespace FlaxEngine /// The point (world-space). /// The axis (normalized). /// The angle (in degrees). - public void RotateAround(Vector3 point, Vector3 axis, float angle) + /// /// Whether to orient the actor the same amount as rotation. + public void RotateAround(Vector3 point, Vector3 axis, float angle, bool orientActor = true) { var transform = Transform; var q = Quaternion.RotationAxis(axis, angle * Mathf.DegreesToRadians); - var dif = (transform.Translation - point) * q; - transform.Translation = point + dif; - transform.Orientation = q; + if (Vector3.NearEqual(point, transform.Translation) && orientActor) + transform.Orientation *= q; + else + { + var dif = (transform.Translation - point) * q; + transform.Translation = point + dif; + if (orientActor) + transform.Orientation *= q; + } Transform = transform; } diff --git a/Source/Engine/Level/Components/MissingScript.h b/Source/Engine/Level/Components/MissingScript.h new file mode 100644 index 000000000..bfb73498f --- /dev/null +++ b/Source/Engine/Level/Components/MissingScript.h @@ -0,0 +1,65 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#if USE_EDITOR + +#include "Engine/Core/Cache.h" +#include "Engine/Scripting/Script.h" +#include "Engine/Scripting/ScriptingObjectReference.h" +#include "Engine/Serialization/JsonWriters.h" + +/// +/// Actor script component that represents missing script. +/// +API_CLASS(Attributes="HideInEditor") class FLAXENGINE_API MissingScript : public Script +{ + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE(MissingScript); + +private: + ScriptingObjectReference