diff --git a/.gitignore b/.gitignore
index 54907892f..b7e11e554 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ Source/*.csproj
/Package_*/
!Source/Engine/Debug
/Source/Platforms/Editor/Linux/Mono/etc/mono/registry
+PackageEditor_Cert.command
PackageEditor_Cert.bat
PackagePlatforms_Cert.bat
diff --git a/Content/Editor/Particles/Smoke.flax b/Content/Editor/Particles/Smoke.flax
index 1335a84f4..b42c2f325 100644
--- a/Content/Editor/Particles/Smoke.flax
+++ b/Content/Editor/Particles/Smoke.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c3dc51e7805056006ca6cbb481ba202583a9b2287c152fc04e28e1d07747d6ce
-size 14706
+oid sha256:334ac0d00495fc88b10839061ff0c3f45323d4f75ab6176b19005199a7324a19
+size 14569
diff --git a/Content/Editor/Particles/Sparks.flax b/Content/Editor/Particles/Sparks.flax
index 7977e231b..7ee0ed6e9 100644
--- a/Content/Editor/Particles/Sparks.flax
+++ b/Content/Editor/Particles/Sparks.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:77d902ab5f79426cc66dc5f19a3b8280136a58aa3c6fd317554d1a032357c65a
-size 15275
+oid sha256:87046a9bfe275cac290b4764de8a512c222ccc386d01af9026d57c3e4b7773b6
+size 13625
diff --git a/Content/Editor/Scripting/ScriptTemplate.cs b/Content/Editor/Scripting/ScriptTemplate.cs
index 663acf05f..30fbe9d86 100644
--- a/Content/Editor/Scripting/ScriptTemplate.cs
+++ b/Content/Editor/Scripting/ScriptTemplate.cs
@@ -33,4 +33,3 @@ public class %class% : Script
// Here you can add code that needs to be called every frame
}
}
-
diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax
index 18f653f86..0e94c5c06 100644
--- a/Content/Shaders/GlobalSignDistanceField.flax
+++ b/Content/Shaders/GlobalSignDistanceField.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2c8aa181a814d69b15ffec6493a71a6f42ae816ce04f7803cff2d5073b4b3c4f
-size 11790
+oid sha256:e075583620e62407503c73f52487c204ddcad421e80fa621aebd55af1cfb08d5
+size 11798
diff --git a/Flax.flaxproj b/Flax.flaxproj
index ebcdc2830..a3157a032 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -3,7 +3,8 @@
"Version": {
"Major": 1,
"Minor": 7,
- "Build": 6401
+ "Revision": 0,
+ "Build": 6404
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",
diff --git a/GenerateProjectFiles.bat b/GenerateProjectFiles.bat
index 622939c34..28970a203 100644
--- a/GenerateProjectFiles.bat
+++ b/GenerateProjectFiles.bat
@@ -15,7 +15,7 @@ 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
+Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor
popd
echo Done!
diff --git a/GenerateProjectFiles.command b/GenerateProjectFiles.command
index 5ee5c0783..a42121252 100755
--- a/GenerateProjectFiles.command
+++ b/GenerateProjectFiles.command
@@ -14,4 +14,4 @@ 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
+Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor
diff --git a/GenerateProjectFiles.sh b/GenerateProjectFiles.sh
index dceb8abe8..76d96c7ef 100755
--- a/GenerateProjectFiles.sh
+++ b/GenerateProjectFiles.sh
@@ -14,4 +14,4 @@ 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
+Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor
diff --git a/README.md b/README.md
index fac631a6a..d6688bd03 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Flax Engine is a high quality modern 3D game engine written in C++ and C#.
-From stunning graphics to powerful scripts - Flax can give everything for your games. Designed for fast workflow with many ready to use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)).
+From stunning graphics to powerful scripts, it's designed for fast workflow with many ready-to-use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)).
This repository contains full source code of the Flax Engine (excluding NDA-protected platforms support). Anyone is welcome to contribute or use the modified source in Flax-based games.
diff --git a/Source/Editor/Content/AssetPickerValidator.cs b/Source/Editor/Content/AssetPickerValidator.cs
new file mode 100644
index 000000000..f43ab6a29
--- /dev/null
+++ b/Source/Editor/Content/AssetPickerValidator.cs
@@ -0,0 +1,292 @@
+using System;
+using System.IO;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.Utilities;
+
+namespace FlaxEditor.Content;
+
+///
+/// Manages and converts the selected content item to the appropriate types. Useful for drag operations.
+///
+public class AssetPickerValidator : IContentItemOwner
+{
+ private Asset _selected;
+ private ContentItem _selectedItem;
+ private ScriptType _type;
+ private string _fileExtension;
+
+ ///
+ /// Gets or sets the selected item.
+ ///
+ public ContentItem SelectedItem
+ {
+ get => _selectedItem;
+ set
+ {
+ if (_selectedItem == value)
+ return;
+ if (value == null)
+ {
+ if (_selected == null && _selectedItem is SceneItem)
+ {
+ // Deselect scene reference
+ _selectedItem.RemoveReference(this);
+ _selectedItem = null;
+ _selected = null;
+ OnSelectedItemChanged();
+ return;
+ }
+
+ // Deselect
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = null;
+ _selected = null;
+ OnSelectedItemChanged();
+ }
+ else if (value is SceneItem item)
+ {
+ if (_selectedItem == item)
+ return;
+ if (!IsValid(item))
+ item = null;
+
+ // Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = item;
+ _selected = null;
+ _selectedItem?.AddReference(this);
+ OnSelectedItemChanged();
+ }
+ else if (value is AssetItem assetItem)
+ {
+ SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
+ }
+ else
+ {
+ // Change value
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = value;
+ _selected = null;
+ OnSelectedItemChanged();
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the selected asset identifier.
+ ///
+ public Guid SelectedID
+ {
+ get
+ {
+ if (_selected != null)
+ return _selected.ID;
+ if (_selectedItem is AssetItem assetItem)
+ return assetItem.ID;
+ return Guid.Empty;
+ }
+ set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
+ }
+
+ ///
+ /// Gets or sets the selected content item path.
+ ///
+ public string SelectedPath
+ {
+ get
+ {
+ string path = _selectedItem?.Path ?? _selected?.Path;
+ if (path != null)
+ {
+ // Convert into path relative to the project (cross-platform)
+ var projectFolder = Globals.ProjectFolder;
+ if (path.StartsWith(projectFolder))
+ path = path.Substring(projectFolder.Length + 1);
+ }
+ return path;
+ }
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ SelectedItem = null;
+ }
+ else
+ {
+ var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
+ SelectedItem = Editor.Instance.ContentDatabase.Find(path);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the selected asset object.
+ ///
+ public Asset SelectedAsset
+ {
+ get => _selected;
+ set
+ {
+ // Check if value won't change
+ if (value == _selected)
+ return;
+
+ // Find item from content database and check it
+ var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
+ if (item != null && !IsValid(item))
+ item = null;
+
+ // Change value
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = item;
+ _selected = value;
+ _selectedItem?.AddReference(this);
+ OnSelectedItemChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use for generic file picker.
+ ///
+ public ScriptType AssetType
+ {
+ get => _type;
+ set
+ {
+ if (_type != value)
+ {
+ _type = value;
+
+ // Auto deselect if the current value is invalid
+ if (_selectedItem != null && !IsValid(_selectedItem))
+ SelectedItem = null;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the content items extensions filter. Null if unused.
+ ///
+ public string FileExtension
+ {
+ get => _fileExtension;
+ set
+ {
+ if (_fileExtension != value)
+ {
+ _fileExtension = value;
+
+ // Auto deselect if the current value is invalid
+ if (_selectedItem != null && !IsValid(_selectedItem))
+ SelectedItem = null;
+ }
+ }
+ }
+
+ ///
+ /// Occurs when selected item gets changed.
+ ///
+ public event Action SelectedItemChanged;
+
+ ///
+ /// The custom callback for assets validation. Cane be used to implement a rule for assets to pick.
+ ///
+ public Func CheckValid;
+
+ ///
+ /// Returns whether item is valid.
+ ///
+ ///
+ ///
+ public bool IsValid(ContentItem item)
+ {
+ if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
+ return false;
+ if (CheckValid != null && !CheckValid(item))
+ return false;
+ if (_type == ScriptType.Null)
+ return true;
+
+ if (item is AssetItem assetItem)
+ {
+ // Faster path for binary items (in-built)
+ if (assetItem is BinaryAssetItem binaryItem)
+ return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
+
+ // Type filter
+ var type = TypeUtils.GetType(assetItem.TypeName);
+ if (_type.IsAssignableFrom(type))
+ return true;
+
+ // Json assets can contain any type of the object defined by the C# type (data oriented design)
+ if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
+ return true;
+
+ // Special case for scene asset references
+ if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AssetPickerValidator()
+ : this(new ScriptType(typeof(Asset)))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The assets types that this picker accepts.
+ public AssetPickerValidator(ScriptType assetType)
+ {
+ _type = assetType;
+ }
+
+ ///
+ /// Called when selected item gets changed.
+ ///
+ protected virtual void OnSelectedItemChanged()
+ {
+ SelectedItemChanged?.Invoke();
+ }
+
+ ///
+ public void OnItemDeleted(ContentItem item)
+ {
+ // Deselect item
+ SelectedItem = null;
+ }
+
+ ///
+ public void OnItemRenamed(ContentItem item)
+ {
+ }
+
+ ///
+ public void OnItemReimported(ContentItem item)
+ {
+ }
+
+ ///
+ public void OnItemDispose(ContentItem item)
+ {
+ // Deselect item
+ SelectedItem = null;
+ }
+
+ ///
+ /// Call to remove reference from the selected item.
+ ///
+ public void OnDestroy()
+ {
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = null;
+ _selected = null;
+ }
+}
diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs
index e1a3acdf6..259be104b 100644
--- a/Source/Editor/Content/GUI/ContentView.cs
+++ b/Source/Editor/Content/GUI/ContentView.cs
@@ -220,8 +220,9 @@ namespace FlaxEditor.Content.GUI
// Remove references and unlink items
for (int i = 0; i < _items.Count; i++)
{
- _items[i].Parent = null;
- _items[i].RemoveReference(this);
+ var item = _items[i];
+ item.Parent = null;
+ item.RemoveReference(this);
}
_items.Clear();
@@ -263,11 +264,12 @@ namespace FlaxEditor.Content.GUI
// Add references and link items
for (int i = 0; i < items.Count; i++)
{
- if (items[i].Visible)
+ var item = items[i];
+ if (item.Visible && !_items.Contains(item))
{
- items[i].Parent = this;
- items[i].AddReference(this);
- _items.Add(items[i]);
+ item.Parent = this;
+ item.AddReference(this);
+ _items.Add(item);
}
}
if (selection != null)
@@ -279,6 +281,8 @@ namespace FlaxEditor.Content.GUI
// Sort items depending on sortMethod parameter
_children.Sort(((control, control1) =>
{
+ if (control == null || control1 == null)
+ return 0;
if (sortType == SortType.AlphabeticReverse)
{
if (control.CompareTo(control1) > 0)
diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs
index 6bbdc0a51..604caa704 100644
--- a/Source/Editor/Content/Items/ContentItem.cs
+++ b/Source/Editor/Content/Items/ContentItem.cs
@@ -323,8 +323,6 @@ namespace FlaxEditor.Content
/// The new path.
internal virtual void UpdatePath(string value)
{
- Assert.AreNotEqual(Path, value);
-
// Set path
Path = StringUtils.NormalizePath(value);
FileName = System.IO.Path.GetFileName(value);
diff --git a/Source/Editor/Content/Proxy/SceneProxy.cs b/Source/Editor/Content/Proxy/SceneProxy.cs
index 78d88b440..004c2aed7 100644
--- a/Source/Editor/Content/Proxy/SceneProxy.cs
+++ b/Source/Editor/Content/Proxy/SceneProxy.cs
@@ -30,6 +30,12 @@ namespace FlaxEditor.Content
return item is SceneItem;
}
+ ///
+ public override bool AcceptsAsset(string typeName, string path)
+ {
+ return (typeName == Scene.AssetTypename || typeName == Scene.EditorPickerTypename) && path.EndsWith(FileExtension, StringComparison.OrdinalIgnoreCase);
+ }
+
///
public override bool CanCreate(ContentFolder targetLocation)
{
diff --git a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
index 160c783ed..1282e4daa 100644
--- a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
+++ b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
@@ -406,18 +406,16 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < maxChecks; i++)
{
var request = _requests[i];
-
try
{
if (request.IsReady)
- {
return request;
- }
}
catch (Exception ex)
{
- Editor.LogWarning(ex);
Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}.");
+ Editor.LogWarning(ex);
+ _requests.RemoveAt(i--);
}
}
@@ -515,7 +513,6 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < checks; i++)
{
var request = _requests[i];
-
try
{
if (request.IsReady)
@@ -529,8 +526,9 @@ namespace FlaxEditor.Content.Thumbnails
}
catch (Exception ex)
{
- Editor.LogWarning(ex);
Editor.LogWarning($"Failed to prepare thumbnail rendering for {request.Item.ShortName}.");
+ Editor.LogWarning(ex);
+ _requests.RemoveAt(i--);
}
}
diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
index ec7b8e7e1..42ef6fcdf 100644
--- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
@@ -104,4 +104,19 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data)
return false;
}
+void LinuxPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
+{
+ // Pick the first executable file
+ Array files;
+ FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
+ for (auto& file : files)
+ {
+ if (FileSystem::GetExtension(file).IsEmpty())
+ {
+ executableFile = file;
+ break;
+ }
+ }
+}
+
#endif
diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
index 562b38962..432240d00 100644
--- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
@@ -20,6 +20,7 @@ public:
ArchitectureType GetArchitecture() const override;
bool UseSystemDotnet() const override;
bool OnDeployBinaries(CookingData& data) override;
+ void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
};
#endif
diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
index a1db61dbb..aa56a6b95 100644
--- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
@@ -249,4 +249,19 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
return false;
}
+void MacPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
+{
+ // Pick the first executable file
+ Array files;
+ FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
+ for (auto& file : files)
+ {
+ if (FileSystem::GetExtension(file).IsEmpty())
+ {
+ executableFile = file;
+ break;
+ }
+ }
+}
+
#endif
diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
index 21d9141e3..efdd0b733 100644
--- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
@@ -27,6 +27,7 @@ public:
bool IsNativeCodeFile(CookingData& data, const String& file) override;
void OnBuildStarted(CookingData& data) override;
bool OnPostProcess(CookingData& data) override;
+ void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
};
#endif
diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
index c9bbc016e..8306475d1 100644
--- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp
+++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
@@ -131,7 +131,8 @@ bool DeployDataStep::Perform(CookingData& data)
if (FileSystem::DirectoryExists(dstDotnet))
{
String cachedData;
- File::ReadAllText(dotnetCacheFilePath, cachedData);
+ if (FileSystem::FileExists(dotnetCacheFilePath))
+ File::ReadAllText(dotnetCacheFilePath, cachedData);
if (cachedData != dotnetCachedValue)
{
FileSystem::DeleteDirectory(dstDotnet);
@@ -360,7 +361,7 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(PRE_INTEGRATED_GF_ASSET_NAME);
data.AddRootEngineAsset(SMAA_AREA_TEX);
data.AddRootEngineAsset(SMAA_SEARCH_TEX);
- if (data.Configuration != BuildConfiguration::Release)
+ if (!buildSettings.SkipDefaultFonts)
data.AddRootEngineAsset(TEXT("Editor/Fonts/Roboto-Regular"));
// Register custom assets (eg. plugins)
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index 03b21e12b..32111e51c 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -157,6 +157,12 @@ namespace FlaxEditor.CustomEditors
var values = _values;
var presenter = _presenter;
var layout = _layout;
+ if (layout.Editors.Count != 1)
+ {
+ // There are more editors using the same layout so rebuild parent editor to prevent removing others editors
+ _parent?.RebuildLayout();
+ return;
+ }
var control = layout.ContainerControl;
var parent = _parent;
var parentScrollV = (_presenter?.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;
diff --git a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
index 71c5f6daf..4099e5aee 100644
--- a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
@@ -225,8 +225,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
_actor = actor;
- var showActorPicker = actor == null || ParentEditor.Values.All(x => x is not Cloth);
- if (showActorPicker)
+ if (ParentEditor.Values.Any(x => x is Cloth))
+ {
+ // Cloth always picks the parent model mesh
+ if (actor == null)
+ {
+ layout.Label("Cloth needs to be added as a child to model actor.");
+ }
+ }
+ else
{
// Actor reference picker
_actorPicker = layout.Custom();
@@ -242,7 +249,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
var model = staticModel.Model;
if (model == null || model.WaitForLoaded())
+ {
+ layout.Label("No model.");
return;
+ }
var materials = model.MaterialSlots;
var lods = model.LODs;
meshNames = new string[lods.Length][];
@@ -267,7 +277,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
var skinnedModel = animatedModel.SkinnedModel;
if (skinnedModel == null || skinnedModel.WaitForLoaded())
+ {
+ layout.Label("No model.");
return;
+ }
var materials = skinnedModel.MaterialSlots;
var lods = skinnedModel.LODs;
meshNames = new string[lods.Length][];
diff --git a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
index a6c4e6623..8fb742b5e 100644
--- a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
@@ -1,6 +1,13 @@
-using FlaxEditor.CustomEditors.Editors;
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using FlaxEditor.Actions;
+using FlaxEditor.CustomEditors.Editors;
+using FlaxEditor.GUI;
+using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using System.Collections.Generic;
namespace FlaxEditor.CustomEditors.Dedicated;
@@ -10,6 +17,10 @@ namespace FlaxEditor.CustomEditors.Dedicated;
[CustomEditor(typeof(MissingScript)), DefaultEditor]
public class MissingScriptEditor : GenericEditor
{
+ private DropPanel _dropPanel;
+ private Button _replaceScriptButton;
+ private CheckBox _shouldReplaceAllCheckbox;
+
///
public override void Initialize(LayoutElementsContainer layout)
{
@@ -18,9 +29,137 @@ public class MissingScriptEditor : GenericEditor
base.Initialize(layout);
return;
}
+ _dropPanel = dropPanel;
+ _dropPanel.HeaderTextColor = Color.OrangeRed;
- dropPanel.HeaderTextColor = Color.OrangeRed;
+ var replaceScriptPanel = new Panel
+ {
+ Parent = _dropPanel,
+ Height = 64,
+ };
+
+ _replaceScriptButton = new Button
+ {
+ Text = "Replace Script",
+ TooltipText = "Replaces the missing script with a given script type",
+ AnchorPreset = AnchorPresets.TopCenter,
+ Width = 240,
+ Height = 24,
+ X = -120,
+ Y = 0,
+ Parent = replaceScriptPanel,
+ };
+ _replaceScriptButton.Clicked += OnReplaceScriptButtonClicked;
+
+ var replaceAllLabel = new Label
+ {
+ Text = "Replace all matching missing scripts",
+ TooltipText = "Whether or not to apply this script change to all scripts missing the same type.",
+ AnchorPreset = AnchorPresets.BottomCenter,
+ Y = -34,
+ Parent = replaceScriptPanel,
+ };
+ replaceAllLabel.X -= FlaxEngine.GUI.Style.Current.FontSmall.MeasureText(replaceAllLabel.Text).X;
+
+ _shouldReplaceAllCheckbox = new CheckBox
+ {
+ TooltipText = replaceAllLabel.TooltipText,
+ AnchorPreset = AnchorPresets.BottomCenter,
+ Y = -34,
+ Parent = replaceScriptPanel,
+ };
+
+ float centerDifference = (_shouldReplaceAllCheckbox.Right - replaceAllLabel.Left) / 2;
+ replaceAllLabel.X += centerDifference;
+ _shouldReplaceAllCheckbox.X += centerDifference;
base.Initialize(layout);
}
+
+ private void FindActorsWithMatchingMissingScript(List missingScripts)
+ {
+ foreach (Actor actor in Level.GetActors(typeof(Actor)))
+ {
+ for (int scriptIndex = 0; scriptIndex < actor.ScriptsCount; scriptIndex++)
+ {
+ Script actorScript = actor.Scripts[scriptIndex];
+ if (actorScript is not MissingScript missingActorScript)
+ continue;
+
+ MissingScript currentMissing = Values[0] as MissingScript;
+ if (missingActorScript.MissingTypeName != currentMissing.MissingTypeName)
+ continue;
+
+ missingScripts.Add(missingActorScript);
+ }
+ }
+ }
+
+ private void RunReplacementMultiCast(List actions)
+ {
+ if (actions.Count == 0)
+ {
+ Editor.LogWarning("Failed to replace scripts!");
+ return;
+ }
+
+ var multiAction = new MultiUndoAction(actions);
+ multiAction.Do();
+ var presenter = ParentEditor.Presenter;
+ if (presenter != null)
+ {
+ presenter.Undo.AddAction(multiAction);
+ presenter.Control.Focus();
+ }
+ }
+
+ private void ReplaceScript(ScriptType script, bool replaceAllInScene)
+ {
+ var actions = new List(4);
+
+ var missingScripts = new List();
+ if (!replaceAllInScene)
+ missingScripts.Add((MissingScript)Values[0]);
+ else
+ FindActorsWithMatchingMissingScript(missingScripts);
+
+ foreach (var missingScript in missingScripts)
+ actions.Add(AddRemoveScript.Add(missingScript.Actor, script));
+ RunReplacementMultiCast(actions);
+
+ for (int actionIdx = 0; actionIdx < actions.Count; actionIdx++)
+ {
+ AddRemoveScript addRemoveScriptAction = (AddRemoveScript)actions[actionIdx];
+ int orderInParent = addRemoveScriptAction.GetOrderInParent();
+
+ Script newScript = missingScripts[actionIdx].Actor.Scripts[orderInParent];
+ missingScripts[actionIdx].ReferenceScript = newScript;
+ }
+ actions.Clear();
+
+ foreach (var missingScript in missingScripts)
+ actions.Add(AddRemoveScript.Remove(missingScript));
+ RunReplacementMultiCast(actions);
+ }
+
+ private void OnReplaceScriptButtonClicked()
+ {
+ var scripts = Editor.Instance.CodeEditing.Scripts.Get();
+ if (scripts.Count == 0)
+ {
+ // No scripts
+ var cm1 = new ContextMenu();
+ cm1.AddButton("No scripts in project");
+ cm1.Show(_dropPanel, _replaceScriptButton.BottomLeft);
+ return;
+ }
+
+ // Show context menu with list of scripts to add
+ var cm = new ItemsListContextMenu(180);
+ for (int i = 0; i < scripts.Count; i++)
+ cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
+ cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag, _shouldReplaceAllCheckbox.Checked);
+ cm.SortItems();
+ cm.Show(_dropPanel, _replaceScriptButton.BottomLeft - new Float2((cm.Width - _replaceScriptButton.Width) / 2, 0));
+ }
}
diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
index 5215e23a4..296560507 100644
--- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
@@ -695,7 +695,41 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void SetType(ref ScriptType controlType, UIControl uiControl)
{
string previousName = uiControl.Control?.GetType().Name ?? nameof(UIControl);
- uiControl.Control = (Control)controlType.CreateInstance();
+
+ var oldControlType = (Control)uiControl.Control;
+ var newControlType = (Control)controlType.CreateInstance();
+
+ // copy old control data to new control
+ if (oldControlType != null)
+ {
+ newControlType.Visible = oldControlType.Visible;
+ newControlType.Enabled = oldControlType.Enabled;
+ newControlType.AutoFocus = oldControlType.AutoFocus;
+
+ newControlType.AnchorMin = oldControlType.AnchorMin;
+ newControlType.AnchorMax = oldControlType.AnchorMax;
+ newControlType.Offsets = oldControlType.Offsets;
+
+ newControlType.LocalLocation = oldControlType.LocalLocation;
+ newControlType.Scale = oldControlType.Scale;
+ newControlType.Bounds = oldControlType.Bounds;
+ newControlType.Width = oldControlType.Width;
+ newControlType.Height = oldControlType.Height;
+ newControlType.Center = oldControlType.Center;
+ newControlType.PivotRelative = oldControlType.PivotRelative;
+
+ newControlType.Pivot = oldControlType.Pivot;
+ newControlType.Shear = oldControlType.Shear;
+ newControlType.Rotation = oldControlType.Rotation;
+ }
+ if (oldControlType is ContainerControl oldContainer && newControlType is ContainerControl newContainer)
+ {
+ newContainer.CullChildren = oldContainer.CullChildren;
+ newContainer.ClipChildren = oldContainer.ClipChildren;
+ }
+
+ uiControl.Control = newControlType;
+
if (uiControl.Name.StartsWith(previousName))
{
string newName = controlType.Name + uiControl.Name.Substring(previousName.Length);
diff --git a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
index bf087feda..4829bd91a 100644
--- a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
@@ -34,7 +34,7 @@ namespace FlaxEditor.CustomEditors.Editors
value = 0;
// If selected is single actor that has children, ask if apply layer to the sub objects as well
- if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren)
+ if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren && !Editor.IsPlayMode)
{
var valueText = comboBox.SelectedItem;
diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
index cfba940c2..1f3359fd5 100644
--- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
@@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
// Generic file picker
assetType = ScriptType.Null;
- Picker.FileExtension = assetReference.TypeName;
+ Picker.Validator.FileExtension = assetReference.TypeName;
}
else
{
@@ -85,7 +85,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
- Picker.AssetType = assetType;
+ Picker.Validator.AssetType = assetType;
Picker.Height = height;
Picker.SelectedItemChanged += OnSelectedItemChanged;
}
@@ -95,15 +95,15 @@ namespace FlaxEditor.CustomEditors.Editors
if (_isRefreshing)
return;
if (typeof(AssetItem).IsAssignableFrom(_valueType.Type))
- SetValue(Picker.SelectedItem);
+ SetValue(Picker.Validator.SelectedItem);
else if (_valueType.Type == typeof(Guid))
- SetValue(Picker.SelectedID);
+ SetValue(Picker.Validator.SelectedID);
else if (_valueType.Type == typeof(SceneReference))
- SetValue(new SceneReference(Picker.SelectedID));
+ SetValue(new SceneReference(Picker.Validator.SelectedID));
else if (_valueType.Type == typeof(string))
- SetValue(Picker.SelectedPath);
+ SetValue(Picker.Validator.SelectedPath);
else
- SetValue(Picker.SelectedAsset);
+ SetValue(Picker.Validator.SelectedAsset);
}
///
@@ -115,15 +115,15 @@ namespace FlaxEditor.CustomEditors.Editors
{
_isRefreshing = true;
if (Values[0] is AssetItem assetItem)
- Picker.SelectedItem = assetItem;
+ Picker.Validator.SelectedItem = assetItem;
else if (Values[0] is Guid guid)
- Picker.SelectedID = guid;
+ Picker.Validator.SelectedID = guid;
else if (Values[0] is SceneReference sceneAsset)
- Picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
+ Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
else if (Values[0] is string path)
- Picker.SelectedPath = path;
+ Picker.Validator.SelectedPath = path;
else
- Picker.SelectedAsset = Values[0] as Asset;
+ Picker.Validator.SelectedAsset = Values[0] as Asset;
_isRefreshing = false;
}
}
diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
index 8922e2d25..6f623fb23 100644
--- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
@@ -3,9 +3,12 @@
using System;
using System.Collections;
using System.Linq;
+using FlaxEditor.Content;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.GUI.Drag;
+using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -110,7 +113,7 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Initialize(LayoutElementsContainer layout)
{
// No support for different collections for now
- if (HasDifferentValues || HasDifferentTypes)
+ if (HasDifferentTypes)
return;
var size = Count;
@@ -135,14 +138,43 @@ namespace FlaxEditor.CustomEditors.Editors
spacing = collection.Spacing;
}
+ var dragArea = layout.CustomContainer();
+ dragArea.CustomControl.Editor = this;
+ dragArea.CustomControl.ElementType = ElementType;
+
+ // Check for the AssetReferenceAttribute. In JSON assets, it can be used to filter
+ // which scripts can be dragged over and dropped on this collection editor.
+ var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
+ if (assetReference != null)
+ {
+ if (string.IsNullOrEmpty(assetReference.TypeName))
+ {
+ }
+ else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
+ {
+ dragArea.CustomControl.ElementType = ScriptType.Null;
+ dragArea.CustomControl.FileExtension = assetReference.TypeName;
+ }
+ else
+ {
+ var customType = TypeUtils.GetType(assetReference.TypeName);
+ if (customType != ScriptType.Null)
+ dragArea.CustomControl.ElementType = customType;
+ else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
+ Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for drag and drop filter.", assetReference.TypeName));
+ else
+ dragArea.CustomControl.ElementType = ScriptType.Void;
+ }
+ }
+
// Size
if (_readOnly || (NotNullItems && size == 0))
{
- layout.Label("Size", size.ToString());
+ dragArea.Label("Size", size.ToString());
}
else
{
- _size = layout.IntegerValue("Size");
+ _size = dragArea.IntegerValue("Size");
_size.IntValue.MinValue = 0;
_size.IntValue.MaxValue = ushort.MaxValue;
_size.IntValue.Value = size;
@@ -152,7 +184,7 @@ namespace FlaxEditor.CustomEditors.Editors
// Elements
if (size > 0)
{
- var panel = layout.VerticalPanel();
+ var panel = dragArea.VerticalPanel();
panel.Panel.BackgroundColor = _background;
var elementType = ElementType;
@@ -212,37 +244,33 @@ namespace FlaxEditor.CustomEditors.Editors
// Add/Remove buttons
if (!_readOnly)
{
- var area = layout.Space(20);
- var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16)
- {
- Text = "+",
- TooltipText = "Add new item",
- AnchorPreset = AnchorPresets.TopRight,
- Parent = area.ContainerControl,
- Enabled = !NotNullItems || size > 0,
- };
- addButton.Clicked += () =>
- {
- if (IsSetBlocked)
- return;
+ var panel = dragArea.HorizontalPanel();
+ panel.Panel.Size = new Float2(0, 20);
+ panel.Panel.Margin = new Margin(2);
- Resize(Count + 1);
- };
- var removeButton = new Button(addButton.Right + 2, addButton.Y, 16, 16)
- {
- Text = "-",
- TooltipText = "Remove last item",
- AnchorPreset = AnchorPresets.TopRight,
- Parent = area.ContainerControl,
- Enabled = size > 0,
- };
- removeButton.Clicked += () =>
+ var removeButton = panel.Button("-", "Remove last item");
+ removeButton.Button.Size = new Float2(16, 16);
+ removeButton.Button.Enabled = size > 0;
+ removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
+ removeButton.Button.Clicked += () =>
{
if (IsSetBlocked)
return;
Resize(Count - 1);
};
+
+ var addButton = panel.Button("+", "Add new item");
+ addButton.Button.Size = new Float2(16, 16);
+ addButton.Button.Enabled = !NotNullItems || size > 0;
+ addButton.Button.AnchorPreset = AnchorPresets.TopRight;
+ addButton.Button.Clicked += () =>
+ {
+ if (IsSetBlocked)
+ return;
+
+ Resize(Count + 1);
+ };
}
}
@@ -369,5 +397,232 @@ namespace FlaxEditor.CustomEditors.Editors
}
return base.OnDirty(editor, value, token);
}
+
+ private class DragAreaControl : VerticalPanel
+ {
+ private DragItems _dragItems;
+ private DragActors _dragActors;
+ private DragHandlers _dragHandlers;
+ private AssetPickerValidator _pickerValidator;
+
+ public ScriptType ElementType
+ {
+ get => _pickerValidator?.AssetType ?? ScriptType.Null;
+ set => _pickerValidator = new AssetPickerValidator(value);
+ }
+
+ public CollectionEditor Editor { get; set; }
+
+ public string FileExtension
+ {
+ set => _pickerValidator.FileExtension = value;
+ }
+
+ ///
+ public override void Draw()
+ {
+ if (_dragHandlers is { HasValidDrag: true })
+ {
+ var area = new Rectangle(Float2.Zero, Size);
+ Render2D.FillRectangle(area, Color.Orange * 0.5f);
+ Render2D.DrawRectangle(area, Color.Black);
+ }
+
+ base.Draw();
+ }
+
+ public override void OnDestroy()
+ {
+ _pickerValidator.OnDestroy();
+ }
+
+ private bool ValidateActors(ActorNode node)
+ {
+ return node.Actor.GetScript(ElementType.Type) || ElementType.Type.IsAssignableTo(typeof(Actor));
+ }
+
+ ///
+ public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
+ {
+ var result = base.OnDragEnter(ref location, data);
+ if (result != DragDropEffect.None)
+ return result;
+
+ if (_dragHandlers == null)
+ {
+ _dragItems = new DragItems(_pickerValidator.IsValid);
+ _dragActors = new DragActors(ValidateActors);
+ _dragHandlers = new DragHandlers
+ {
+ _dragActors,
+ _dragItems
+ };
+ }
+ return _dragHandlers.OnDragEnter(data);
+ }
+
+ ///
+ public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
+ {
+ var result = base.OnDragMove(ref location, data);
+ if (result != DragDropEffect.None)
+ return result;
+
+ return _dragHandlers.Effect;
+ }
+
+ ///
+ public override void OnDragLeave()
+ {
+ _dragHandlers.OnDragLeave();
+
+ base.OnDragLeave();
+ }
+
+ ///
+ public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
+ {
+ var result = base.OnDragDrop(ref location, data);
+ if (result != DragDropEffect.None)
+ {
+ _dragHandlers.OnDragDrop(null);
+ return result;
+ }
+
+ if (_dragHandlers.HasValidDrag)
+ {
+ if (_dragItems.HasValidDrag)
+ {
+ var list = Editor.CloneValues();
+ if (list == null)
+ {
+ if (Editor.Values.Type.IsArray)
+ {
+ list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
+ }
+ else
+ {
+ list = Editor.Values.Type.CreateInstance() as IList;
+ }
+ }
+ if (list.IsFixedSize)
+ {
+ var oldSize = list.Count;
+ var newSize = list.Count + _dragItems.Objects.Count;
+ var type = Editor.Values.Type.GetElementType();
+ var array = TypeUtils.CreateArrayInstance(type, newSize);
+ list.CopyTo(array, 0);
+
+ for (var i = oldSize; i < newSize; i++)
+ {
+ var validator = new AssetPickerValidator
+ {
+ FileExtension = _pickerValidator.FileExtension,
+ AssetType = _pickerValidator.AssetType,
+ SelectedItem = _dragItems.Objects[i - oldSize],
+ };
+
+ if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
+ array.SetValue(validator.SelectedItem, i);
+ else if (ElementType.Type == typeof(Guid))
+ array.SetValue(validator.SelectedID, i);
+ else if (ElementType.Type == typeof(SceneReference))
+ array.SetValue(new SceneReference(validator.SelectedID), i);
+ else if (ElementType.Type == typeof(string))
+ array.SetValue(validator.SelectedPath, i);
+ else
+ array.SetValue(validator.SelectedAsset, i);
+
+ validator.OnDestroy();
+ }
+ Editor.SetValue(array);
+ }
+ else
+ {
+ foreach (var item in _dragItems.Objects)
+ {
+ var validator = new AssetPickerValidator
+ {
+ FileExtension = _pickerValidator.FileExtension,
+ AssetType = _pickerValidator.AssetType,
+ SelectedItem = item,
+ };
+
+ if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
+ list.Add(validator.SelectedItem);
+ else if (ElementType.Type == typeof(Guid))
+ list.Add(validator.SelectedID);
+ else if (ElementType.Type == typeof(SceneReference))
+ list.Add(new SceneReference(validator.SelectedID));
+ else if (ElementType.Type == typeof(string))
+ list.Add(validator.SelectedPath);
+ else
+ list.Add(validator.SelectedAsset);
+
+ validator.OnDestroy();
+ }
+ Editor.SetValue(list);
+ }
+ }
+ else if (_dragActors.HasValidDrag)
+ {
+ var list = Editor.CloneValues();
+ if (list == null)
+ {
+ if (Editor.Values.Type.IsArray)
+ {
+ list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
+ }
+ else
+ {
+ list = Editor.Values.Type.CreateInstance() as IList;
+ }
+ }
+
+ if (list.IsFixedSize)
+ {
+ var oldSize = list.Count;
+ var newSize = list.Count + _dragActors.Objects.Count;
+ var type = Editor.Values.Type.GetElementType();
+ var array = TypeUtils.CreateArrayInstance(type, newSize);
+ list.CopyTo(array, 0);
+
+ for (var i = oldSize; i < newSize; i++)
+ {
+ var actor = _dragActors.Objects[i - oldSize].Actor;
+ if (ElementType.Type.IsAssignableTo(typeof(Actor)))
+ {
+ array.SetValue(actor, i);
+ }
+ else
+ {
+ array.SetValue(actor.GetScript(ElementType.Type), i);
+ }
+ }
+ Editor.SetValue(array);
+ }
+ else
+ {
+ foreach (var actorNode in _dragActors.Objects)
+ {
+ if (ElementType.Type.IsAssignableTo(typeof(Actor)))
+ {
+ list.Add(actorNode.Actor);
+ }
+ else
+ {
+ list.Add(actorNode.Actor.GetScript(ElementType.Type));
+ }
+ }
+ Editor.SetValue(list);
+ }
+ }
+
+ _dragHandlers.OnDragDrop(null);
+ }
+
+ return result;
+ }
+ }
}
}
diff --git a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
index 9607680f2..f901b20d9 100644
--- a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
@@ -28,14 +28,16 @@ namespace FlaxEditor.CustomEditors.Editors
var group = layout.Group("Entry");
_group = group;
- if (ParentEditor == null)
+ if (ParentEditor == null || HasDifferentTypes)
return;
var entry = (ModelInstanceEntry)Values[0];
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
var materialLabel = new PropertyNameLabel("Material");
materialLabel.TooltipText = "The mesh surface material used for the rendering.";
- if (ParentEditor.ParentEditor?.Values[0] is ModelInstanceActor modelInstance)
+ var parentEditorValues = ParentEditor.ParentEditor?.Values;
+ if (parentEditorValues?[0] is ModelInstanceActor modelInstance)
{
+ // TODO: store _modelInstance and _material in array for each selected model instance actor
_entryIndex = entryIndex;
_modelInstance = modelInstance;
var slots = modelInstance.MaterialSlots;
@@ -56,6 +58,8 @@ namespace FlaxEditor.CustomEditors.Editors
// Create material picker
var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase);
+ for (var i = 1; i < parentEditorValues.Count; i++)
+ materialValue.Add(_material);
var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue);
materialEditor.Values.SetDefaultValue(defaultValue);
materialEditor.RefreshDefaultValue();
@@ -72,14 +76,14 @@ namespace FlaxEditor.CustomEditors.Editors
return;
_isRefreshing = true;
var slots = _modelInstance.MaterialSlots;
- var material = _materialEditor.Picker.SelectedAsset as MaterialBase;
+ var material = _materialEditor.Picker.Validator.SelectedAsset as MaterialBase;
var defaultMaterial = GPUDevice.Instance.DefaultMaterial;
var value = (ModelInstanceEntry)Values[0];
var prevMaterial = value.Material;
if (!material)
{
// Fallback to default material
- _materialEditor.Picker.SelectedAsset = defaultMaterial;
+ _materialEditor.Picker.Validator.SelectedAsset = defaultMaterial;
value.Material = defaultMaterial;
}
else if (material == slots[_entryIndex].Material)
diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs
index 8d6b0f9e2..1476832c4 100644
--- a/Source/Editor/GUI/AssetPicker.cs
+++ b/Source/Editor/GUI/AssetPicker.cs
@@ -5,6 +5,7 @@ using System.IO;
using FlaxEditor.Content;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
+using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -17,189 +18,21 @@ namespace FlaxEditor.GUI
///
///
[HideInEditor]
- public class AssetPicker : Control, IContentItemOwner
+ public class AssetPicker : Control
{
private const float DefaultIconSize = 64;
private const float ButtonsOffset = 2;
private const float ButtonsSize = 12;
- private Asset _selected;
- private ContentItem _selectedItem;
- private ScriptType _type;
- private string _fileExtension;
-
private bool _isMouseDown;
private Float2 _mouseDownPos;
private Float2 _mousePos;
private DragItems _dragOverElement;
///
- /// Gets or sets the selected item.
+ /// The asset validator. Used to ensure only appropriate items can be picked.
///
- public ContentItem SelectedItem
- {
- get => _selectedItem;
- set
- {
- if (_selectedItem == value)
- return;
- if (value == null)
- {
- if (_selected == null && _selectedItem is SceneItem)
- {
- // Deselect scene reference
- _selectedItem.RemoveReference(this);
- _selectedItem = null;
- _selected = null;
- OnSelectedItemChanged();
- return;
- }
-
- // Deselect
- _selectedItem?.RemoveReference(this);
- _selectedItem = null;
- _selected = null;
- OnSelectedItemChanged();
- }
- else if (value is SceneItem item)
- {
- if (_selectedItem == item)
- return;
- if (!IsValid(item))
- item = null;
-
- // Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
- _selectedItem?.RemoveReference(this);
- _selectedItem = item;
- _selected = null;
- _selectedItem?.AddReference(this);
- OnSelectedItemChanged();
- }
- else if (value is AssetItem assetItem)
- {
- SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
- }
- else
- {
- // Change value
- _selectedItem?.RemoveReference(this);
- _selectedItem = value;
- _selected = null;
- OnSelectedItemChanged();
- }
- }
- }
-
- ///
- /// Gets or sets the selected asset identifier.
- ///
- public Guid SelectedID
- {
- get
- {
- if (_selected != null)
- return _selected.ID;
- if (_selectedItem is AssetItem assetItem)
- return assetItem.ID;
- return Guid.Empty;
- }
- set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
- }
-
- ///
- /// Gets or sets the selected content item path.
- ///
- public string SelectedPath
- {
- get
- {
- string path = _selectedItem?.Path ?? _selected?.Path;
- if (path != null)
- {
- // Convert into path relative to the project (cross-platform)
- var projectFolder = Globals.ProjectFolder;
- if (path.StartsWith(projectFolder))
- path = path.Substring(projectFolder.Length + 1);
- }
- return path;
- }
- set
- {
- if (string.IsNullOrEmpty(value))
- {
- SelectedItem = null;
- }
- else
- {
- var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
- SelectedItem = Editor.Instance.ContentDatabase.Find(path);
- }
- }
- }
-
- ///
- /// Gets or sets the selected asset object.
- ///
- public Asset SelectedAsset
- {
- get => _selected;
- set
- {
- // Check if value won't change
- if (value == _selected)
- return;
-
- // Find item from content database and check it
- var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
- if (item != null && !IsValid(item))
- item = null;
-
- // Change value
- _selectedItem?.RemoveReference(this);
- _selectedItem = item;
- _selected = value;
- _selectedItem?.AddReference(this);
- OnSelectedItemChanged();
- }
- }
-
- ///
- /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use for generic file picker.
- ///
- public ScriptType AssetType
- {
- get => _type;
- set
- {
- if (_type != value)
- {
- _type = value;
-
- // Auto deselect if the current value is invalid
- if (_selectedItem != null && !IsValid(_selectedItem))
- SelectedItem = null;
- }
- }
- }
-
- ///
- /// Gets or sets the content items extensions filter. Null if unused.
- ///
- public string FileExtension
- {
- get => _fileExtension;
- set
- {
- if (_fileExtension != value)
- {
- _fileExtension = value;
-
- // Auto deselect if the current value is invalid
- if (_selectedItem != null && !IsValid(_selectedItem))
- SelectedItem = null;
- }
- }
- }
+ public AssetPickerValidator Validator { get; }
///
/// Occurs when selected item gets changed.
@@ -216,38 +49,6 @@ namespace FlaxEditor.GUI
///
public bool CanEdit = true;
- private bool IsValid(ContentItem item)
- {
- if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
- return false;
- if (CheckValid != null && !CheckValid(item))
- return false;
- if (_type == ScriptType.Null)
- return true;
-
- if (item is AssetItem assetItem)
- {
- // Faster path for binary items (in-built)
- if (assetItem is BinaryAssetItem binaryItem)
- return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
-
- // Type filter
- var type = TypeUtils.GetType(assetItem.TypeName);
- if (_type.IsAssignableFrom(type))
- return true;
-
- // Json assets can contain any type of the object defined by the C# type (data oriented design)
- if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
- return true;
-
- // Special case for scene asset references
- if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
- return true;
- }
-
- return false;
- }
-
///
/// Initializes a new instance of the class.
///
@@ -264,7 +65,8 @@ namespace FlaxEditor.GUI
public AssetPicker(ScriptType assetType, Float2 location)
: base(location, new Float2(DefaultIconSize + ButtonsOffset + ButtonsSize, DefaultIconSize))
{
- _type = assetType;
+ Validator = new AssetPickerValidator(assetType);
+ Validator.SelectedItemChanged += OnSelectedItemChanged;
_mousePos = Float2.Minimum;
}
@@ -275,10 +77,10 @@ namespace FlaxEditor.GUI
{
// Update tooltip
string tooltip;
- if (_selectedItem is AssetItem assetItem)
+ if (Validator.SelectedItem is AssetItem assetItem)
tooltip = assetItem.NamePath;
else
- tooltip = SelectedPath;
+ tooltip = Validator.SelectedPath;
TooltipText = tooltip;
SelectedItemChanged?.Invoke();
@@ -289,37 +91,13 @@ namespace FlaxEditor.GUI
// Do the drag drop operation if has selected element
if (new Rectangle(Float2.Zero, Size).Contains(ref _mouseDownPos))
{
- if (_selected != null)
- DoDragDrop(DragAssets.GetDragData(_selected));
- else if (_selectedItem != null)
- DoDragDrop(DragItems.GetDragData(_selectedItem));
+ if (Validator.SelectedAsset != null)
+ DoDragDrop(DragAssets.GetDragData(Validator.SelectedAsset));
+ else if (Validator.SelectedItem != null)
+ DoDragDrop(DragItems.GetDragData(Validator.SelectedItem));
}
}
- ///
- public void OnItemDeleted(ContentItem item)
- {
- // Deselect item
- SelectedItem = null;
- }
-
- ///
- public void OnItemRenamed(ContentItem item)
- {
- }
-
- ///
- public void OnItemReimported(ContentItem item)
- {
- }
-
- ///
- public void OnItemDispose(ContentItem item)
- {
- // Deselect item
- SelectedItem = null;
- }
-
private Rectangle IconRect => new Rectangle(0, 0, Height, Height);
private Rectangle Button1Rect => new Rectangle(Height + ButtonsOffset, 0, ButtonsSize, ButtonsSize);
@@ -341,10 +119,10 @@ namespace FlaxEditor.GUI
if (CanEdit)
Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
- if (_selectedItem != null)
+ if (Validator.SelectedItem != null)
{
// Draw item preview
- _selectedItem.DrawThumbnail(ref iconRect);
+ Validator.SelectedItem.DrawThumbnail(ref iconRect);
// Draw buttons
if (CanEdit)
@@ -363,7 +141,7 @@ namespace FlaxEditor.GUI
{
Render2D.DrawText(
style.FontSmall,
- _selectedItem.ShortName,
+ Validator.SelectedItem.ShortName,
new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize),
style.Foreground,
TextAlignment.Near,
@@ -371,7 +149,7 @@ namespace FlaxEditor.GUI
}
}
// Check if has no item but has an asset (eg. virtual asset)
- else if (_selected)
+ else if (Validator.SelectedAsset)
{
// Draw remove button
Render2D.DrawSprite(style.Cross, button3Rect, button3Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
@@ -380,8 +158,8 @@ namespace FlaxEditor.GUI
float sizeForTextLeft = Width - button1Rect.Right;
if (sizeForTextLeft > 30)
{
- var name = _selected.GetType().Name;
- if (_selected.IsVirtual)
+ var name = Validator.SelectedAsset.GetType().Name;
+ if (Validator.SelectedAsset.IsVirtual)
name += " (virtual)";
Render2D.DrawText(
style.FontSmall,
@@ -407,9 +185,7 @@ namespace FlaxEditor.GUI
///
public override void OnDestroy()
{
- _selectedItem?.RemoveReference(this);
- _selectedItem = null;
- _selected = null;
+ Validator.OnDestroy();
base.OnDestroy();
}
@@ -463,57 +239,57 @@ namespace FlaxEditor.GUI
// Buttons logic
if (!CanEdit)
{
- if (Button1Rect.Contains(location) && _selectedItem != null)
+ if (Button1Rect.Contains(location) && Validator.SelectedItem != null)
{
// Select asset
- Editor.Instance.Windows.ContentWin.Select(_selectedItem);
+ Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
}
}
else if (Button1Rect.Contains(location))
{
Focus();
- if (_type != ScriptType.Null)
+ if (Validator.AssetType != ScriptType.Null)
{
// Show asset picker popup
- var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
+ var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
- SelectedItem = item;
+ Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
- if (_selected != null)
+ if (Validator.SelectedAsset != null)
{
- var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
+ var selectedAssetName = Path.GetFileNameWithoutExtension(Validator.SelectedAsset.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName);
}
}
else
{
// Show content item picker popup
- var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
+ var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
- SelectedItem = item;
+ Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
- if (_selectedItem != null)
+ if (Validator.SelectedItem != null)
{
- popup.ScrollToAndHighlightItemByName(_selectedItem.ShortName);
+ popup.ScrollToAndHighlightItemByName(Validator.SelectedItem.ShortName);
}
}
}
- else if (_selected != null || _selectedItem != null)
+ else if (Validator.SelectedAsset != null || Validator.SelectedItem != null)
{
- if (Button2Rect.Contains(location) && _selectedItem != null)
+ if (Button2Rect.Contains(location) && Validator.SelectedItem != null)
{
// Select asset
- Editor.Instance.Windows.ContentWin.Select(_selectedItem);
+ Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
}
else if (Button3Rect.Contains(location))
{
// Deselect asset
Focus();
- SelectedItem = null;
+ Validator.SelectedItem = null;
}
}
}
@@ -540,10 +316,10 @@ namespace FlaxEditor.GUI
{
Focus();
- if (_selectedItem != null && IconRect.Contains(location))
+ if (Validator.SelectedItem != null && IconRect.Contains(location))
{
// Open it
- Editor.Instance.ContentEditing.Open(_selectedItem);
+ Editor.Instance.ContentEditing.Open(Validator.SelectedItem);
}
// Handled
@@ -557,7 +333,7 @@ namespace FlaxEditor.GUI
// Check if drop asset
if (_dragOverElement == null)
- _dragOverElement = new DragItems(IsValid);
+ _dragOverElement = new DragItems(Validator.IsValid);
if (CanEdit && _dragOverElement.OnDragEnter(data))
{
}
@@ -590,7 +366,7 @@ namespace FlaxEditor.GUI
if (CanEdit && _dragOverElement.HasValidDrag)
{
// Select element
- SelectedItem = _dragOverElement.Objects[0];
+ Validator.SelectedItem = _dragOverElement.Objects[0];
}
// Clear cache
diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
index 161b3f4ae..27878a763 100644
--- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
+++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
@@ -319,7 +319,9 @@ namespace FlaxEditor.GUI.Dialogs
protected override void OnShow()
{
// Auto cancel on lost focus
+#if !PLATFORM_LINUX
((WindowRootControl)Root).Window.LostFocus += OnCancel;
+#endif
base.OnShow();
}
diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs
index 5054aee98..86e24e3b3 100644
--- a/Source/Editor/GUI/Dialogs/Dialog.cs
+++ b/Source/Editor/GUI/Dialogs/Dialog.cs
@@ -39,6 +39,11 @@ namespace FlaxEditor.GUI.Dialogs
///
public DialogResult Result => _result;
+ ///
+ /// Returns the size of the dialog.
+ ///
+ public Float2 DialogSize => _dialogSize;
+
///
/// Initializes a new instance of the class.
///
diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs
index 52c5dcd3c..6e2353441 100644
--- a/Source/Editor/GUI/Docking/DockHintWindow.cs
+++ b/Source/Editor/GUI/Docking/DockHintWindow.cs
@@ -44,11 +44,11 @@ namespace FlaxEditor.GUI.Docking
var mousePos = window.MousePosition;
var previousSize = window.Size;
window.Restore();
- window.Position = FlaxEngine.Input.MouseScreenPosition - mousePos * window.Size / previousSize;
+ window.Position = Platform.MousePosition - mousePos * window.Size / previousSize;
}
// Calculate dragging offset and move window to the destination position
- var mouseScreenPosition = FlaxEngine.Input.MouseScreenPosition;
+ var mouseScreenPosition = Platform.MousePosition;
// If the _toMove window was not focused when initializing this window, the result vector only contains zeros
// and to prevent a failure, we need to perform an update for the drag offset at later time which will be done in the OnMouseMove event handler.
@@ -83,6 +83,7 @@ namespace FlaxEditor.GUI.Docking
// Enable hit window presentation
Proxy.Window.RenderingEnabled = true;
Proxy.Window.Show();
+ Proxy.Window.Focus();
}
///
@@ -113,7 +114,7 @@ namespace FlaxEditor.GUI.Docking
var window = _toMove.Window?.Window;
if (window == null)
return;
- var mouse = FlaxEngine.Input.MouseScreenPosition;
+ var mouse = Platform.MousePosition;
// Move base window
window.Position = mouse - _dragOffset;
@@ -193,7 +194,7 @@ namespace FlaxEditor.GUI.Docking
// Move window to the mouse position (with some offset for caption bar)
var window = (WindowRootControl)toMove.Root;
- var mouse = FlaxEngine.Input.MouseScreenPosition;
+ var mouse = Platform.MousePosition;
window.Window.Position = mouse - new Float2(8, 8);
// Get floating panel
@@ -244,7 +245,7 @@ namespace FlaxEditor.GUI.Docking
private void UpdateRects()
{
// Cache mouse position
- _mouse = FlaxEngine.Input.MouseScreenPosition;
+ _mouse = Platform.MousePosition;
// Check intersection with any dock panel
var uiMouse = _mouse;
@@ -270,15 +271,16 @@ namespace FlaxEditor.GUI.Docking
// Cache dock rectangles
var size = _rectDock.Size;
var offset = _rectDock.Location;
- float BorderMargin = 4.0f;
- float ProxyHintWindowsSize2 = Proxy.HintWindowsSize * 0.5f;
- float centerX = size.X * 0.5f;
- float centerY = size.Y * 0.5f;
- _rUpper = new Rectangle(centerX - ProxyHintWindowsSize2, BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rBottom = new Rectangle(centerX - ProxyHintWindowsSize2, size.Y - Proxy.HintWindowsSize - BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rLeft = new Rectangle(BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rRight = new Rectangle(size.X - Proxy.HintWindowsSize - BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rCenter = new Rectangle(centerX - ProxyHintWindowsSize2, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
+ var borderMargin = 4.0f;
+ var hintWindowsSize = Proxy.HintWindowsSize * Platform.DpiScale;
+ var hintWindowsSize2 = hintWindowsSize * 0.5f;
+ var centerX = size.X * 0.5f;
+ var centerY = size.Y * 0.5f;
+ _rUpper = new Rectangle(centerX - hintWindowsSize2, borderMargin, hintWindowsSize, hintWindowsSize) + offset;
+ _rBottom = new Rectangle(centerX - hintWindowsSize2, size.Y - hintWindowsSize - borderMargin, hintWindowsSize, hintWindowsSize) + offset;
+ _rLeft = new Rectangle(borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
+ _rRight = new Rectangle(size.X - hintWindowsSize - borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
+ _rCenter = new Rectangle(centerX - hintWindowsSize2, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
// Hit test
DockState toSet = DockState.Float;
@@ -428,7 +430,6 @@ namespace FlaxEditor.GUI.Docking
{
if (Window == null)
{
- // Create proxy window
var settings = CreateWindowSettings.Default;
settings.Title = "DockHint.Window";
settings.Size = initSize;
@@ -440,12 +441,10 @@ namespace FlaxEditor.GUI.Docking
settings.IsRegularWindow = false;
settings.SupportsTransparency = true;
settings.ShowInTaskbar = false;
- settings.ShowAfterFirstPaint = true;
+ settings.ShowAfterFirstPaint = false;
settings.IsTopmost = true;
Window = Platform.CreateWindow(ref settings);
-
- // Set opacity and background color
Window.Opacity = 0.6f;
Window.GUI.BackgroundColor = Style.Current.DragWindow;
}
@@ -465,7 +464,7 @@ namespace FlaxEditor.GUI.Docking
var settings = CreateWindowSettings.Default;
settings.Title = name;
- settings.Size = new Float2(HintWindowsSize);
+ settings.Size = new Float2(HintWindowsSize * Platform.DpiScale);
settings.AllowInput = false;
settings.AllowMaximize = false;
settings.AllowMinimize = false;
@@ -479,7 +478,6 @@ namespace FlaxEditor.GUI.Docking
settings.ShowAfterFirstPaint = false;
win = Platform.CreateWindow(ref settings);
-
win.Opacity = 0.6f;
win.GUI.BackgroundColor = Style.Current.DragWindow;
}
diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs
index e544285a5..b04aad08c 100644
--- a/Source/Editor/GUI/Docking/DockPanel.cs
+++ b/Source/Editor/GUI/Docking/DockPanel.cs
@@ -465,36 +465,47 @@ namespace FlaxEditor.GUI.Docking
{
if (Parent.Parent is SplitPanel splitter)
{
- // Check if has any child panels
- var childPanel = new List(_childPanels);
- for (int i = 0; i < childPanel.Count; i++)
+ // Check if there is another nested dock panel inside this dock panel and extract it here
+ var childPanels = _childPanels.ToArray();
+ if (childPanels.Length != 0)
{
- // Undock all tabs
- var panel = childPanel[i];
- int count = panel.TabsCount;
- while (count-- > 0)
+ // Move tabs from child panels into this one
+ DockWindow selectedTab = null;
+ foreach (var childPanel in childPanels)
{
- panel.GetTab(0).Close();
+ var childPanelTabs = childPanel.Tabs.ToArray();
+ for (var i = 0; i < childPanelTabs.Length; i++)
+ {
+ var childPanelTab = childPanelTabs[i];
+ if (selectedTab == null && childPanelTab.IsSelected)
+ selectedTab = childPanelTab;
+ childPanel.UndockWindow(childPanelTab);
+ AddTab(childPanelTab, false);
+ }
}
+ if (selectedTab != null)
+ SelectTab(selectedTab);
}
-
- // Unlink splitter
- var splitterParent = splitter.Parent;
- Assert.IsNotNull(splitterParent);
- splitter.Parent = null;
-
- // Move controls from second split panel to the split panel parent
- var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2;
- var srcPanelChildrenCount = scrPanel.ChildrenCount;
- for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
+ else
{
- scrPanel.GetChild(i).Parent = splitterParent;
- }
- Assert.IsTrue(scrPanel.ChildrenCount == 0);
- Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
+ // Unlink splitter
+ var splitterParent = splitter.Parent;
+ Assert.IsNotNull(splitterParent);
+ splitter.Parent = null;
- // Delete
- splitter.Dispose();
+ // Move controls from second split panel to the split panel parent
+ var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2;
+ var srcPanelChildrenCount = scrPanel.ChildrenCount;
+ for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
+ {
+ scrPanel.GetChild(i).Parent = splitterParent;
+ }
+ Assert.IsTrue(scrPanel.ChildrenCount == 0);
+ Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
+
+ // Delete
+ splitter.Dispose();
+ }
}
else if (!IsMaster)
{
@@ -582,19 +593,17 @@ namespace FlaxEditor.GUI.Docking
/// Adds the tab.
///
/// The window to insert as a tab.
- protected virtual void AddTab(DockWindow window)
+ /// True if auto-select newly added tab.
+ protected virtual void AddTab(DockWindow window, bool autoSelect = true)
{
- // Dock
_tabs.Add(window);
window.ParentDockPanel = this;
-
- // Select tab
- SelectTab(window);
+ if (autoSelect)
+ SelectTab(window);
}
private void CreateTabsProxy()
{
- // Check if has no tabs proxy created
if (_tabsProxy == null)
{
// Create proxy and make set simple full dock
diff --git a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
index f8ffe62ad..7bb85751a 100644
--- a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
+++ b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
@@ -72,7 +72,7 @@ namespace FlaxEditor.GUI.Docking
settings.Size = size;
settings.Position = location;
settings.MinimumSize = new Float2(1);
- settings.MaximumSize = new Float2(4096);
+ settings.MaximumSize = Float2.Zero; // Unlimited size
settings.Fullscreen = false;
settings.HasBorder = true;
settings.SupportsTransparency = false;
diff --git a/Source/Editor/GUI/Input/ColorValueBox.cs b/Source/Editor/GUI/Input/ColorValueBox.cs
index 167cc65bb..fdd7d073a 100644
--- a/Source/Editor/GUI/Input/ColorValueBox.cs
+++ b/Source/Editor/GUI/Input/ColorValueBox.cs
@@ -14,6 +14,8 @@ namespace FlaxEditor.GUI.Input
[HideInEditor]
public class ColorValueBox : Control
{
+ private bool _isMouseDown;
+
///
/// Delegate function used for the color picker events handling.
///
@@ -134,11 +136,22 @@ namespace FlaxEditor.GUI.Input
Render2D.DrawRectangle(r, IsMouseOver || IsNavFocused ? style.BackgroundSelected : Color.Black);
}
+ ///
+ public override bool OnMouseDown(Float2 location, MouseButton button)
+ {
+ _isMouseDown = true;
+ return base.OnMouseDown(location, button);
+ }
+
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
- Focus();
- OnSubmit();
+ if (_isMouseDown)
+ {
+ _isMouseDown = false;
+ Focus();
+ OnSubmit();
+ }
return true;
}
diff --git a/Source/Editor/GUI/Tabs/Tab.cs b/Source/Editor/GUI/Tabs/Tab.cs
index 3968cc17d..85f805af0 100644
--- a/Source/Editor/GUI/Tabs/Tab.cs
+++ b/Source/Editor/GUI/Tabs/Tab.cs
@@ -13,6 +13,8 @@ namespace FlaxEditor.GUI.Tabs
[HideInEditor]
public class Tab : ContainerControl
{
+ internal Tabs _selectedInTabs;
+
///
/// Gets or sets the text.
///
@@ -86,5 +88,25 @@ namespace FlaxEditor.GUI.Tabs
{
return new Tabs.TabHeader((Tabs)Parent, this);
}
+
+ ///
+ protected override void OnParentChangedInternal()
+ {
+ if (_selectedInTabs != null)
+ _selectedInTabs.SelectedTab = null;
+
+ base.OnParentChangedInternal();
+ }
+
+ ///
+ public override void OnDestroy()
+ {
+ if (IsDisposing)
+ return;
+ if (_selectedInTabs != null)
+ _selectedInTabs.SelectedTab = null;
+
+ base.OnDestroy();
+ }
}
}
diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs
index f9bfd3f6c..b5e2cfe39 100644
--- a/Source/Editor/GUI/Tabs/Tabs.cs
+++ b/Source/Editor/GUI/Tabs/Tabs.cs
@@ -191,6 +191,8 @@ namespace FlaxEditor.GUI.Tabs
get => _autoTabsSizeAuto;
set
{
+ if (_autoTabsSizeAuto == value)
+ return;
_autoTabsSizeAuto = value;
PerformLayout();
}
@@ -204,11 +206,11 @@ namespace FlaxEditor.GUI.Tabs
get => _orientation;
set
{
+ if (_orientation == value)
+ return;
_orientation = value;
-
if (UseScroll)
TabsPanel.ScrollBars = _orientation == Orientation.Horizontal ? ScrollBars.Horizontal : ScrollBars.Vertical;
-
PerformLayout();
}
}
@@ -261,7 +263,12 @@ namespace FlaxEditor.GUI.Tabs
// Check if index will change
if (_selectedIndex != index)
{
- SelectedTab?.OnDeselected();
+ var prev = SelectedTab;
+ if (prev != null)
+ {
+ prev._selectedInTabs = null;
+ prev.OnDeselected();
+ }
_selectedIndex = index;
PerformLayout();
OnSelectedTabChanged();
@@ -340,8 +347,13 @@ namespace FlaxEditor.GUI.Tabs
///
protected virtual void OnSelectedTabChanged()
{
+ var selectedTab = SelectedTab;
SelectedTabChanged?.Invoke(this);
- SelectedTab?.OnSelected();
+ if (selectedTab != null)
+ {
+ selectedTab._selectedInTabs = this;
+ selectedTab.OnSelected();
+ }
}
///
@@ -402,6 +414,14 @@ namespace FlaxEditor.GUI.Tabs
tabHeader.Size = tabsSize;
}
}
+ else if (UseScroll)
+ {
+ // If scroll bar is visible it covers part of the tab header so include this in tab size to improve usability
+ if (_orientation == Orientation.Horizontal && TabsPanel.HScrollBar.Visible)
+ tabsSize.Y += TabsPanel.HScrollBar.Height;
+ else if (_orientation == Orientation.Vertical && TabsPanel.VScrollBar.Visible)
+ tabsSize.X += TabsPanel.VScrollBar.Width;
+ }
// Fit the tabs panel
TabsPanel.Size = _orientation == Orientation.Horizontal
diff --git a/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs b/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
index b7c87cb01..3bbca15ef 100644
--- a/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
@@ -39,7 +39,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
if (AssetID == value?.ID)
return;
AssetID = value?.ID ?? Guid.Empty;
- _picker.SelectedAsset = value;
+ _picker.Validator.SelectedAsset = value;
OnAssetChanged();
Timeline?.MarkAsEdited();
}
@@ -63,10 +63,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void OnPickerSelectedItemChanged()
{
- if (Asset == (TAsset)_picker.SelectedAsset)
+ if (Asset == (TAsset)_picker.Validator.SelectedAsset)
return;
using (new TrackUndoBlock(this))
- Asset = (TAsset)_picker.SelectedAsset;
+ Asset = (TAsset)_picker.Validator.SelectedAsset;
}
///
diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs
index 7e3af05bf..703079469 100644
--- a/Source/Editor/GUI/Tree/TreeNode.cs
+++ b/Source/Editor/GUI/Tree/TreeNode.cs
@@ -776,11 +776,20 @@ namespace FlaxEditor.GUI.Tree
// Check if mouse hits arrow
if (_mouseOverArrow && HasAnyVisibleChild)
{
- // Toggle open state
- if (_opened)
- Collapse();
+ if (ParentTree.Root.GetKey(KeyboardKeys.Alt))
+ {
+ if (_opened)
+ CollapseAll();
+ else
+ ExpandAll();
+ }
else
- Expand();
+ {
+ if (_opened)
+ Collapse();
+ else
+ Expand();
+ }
}
// Check if mouse hits bar
diff --git a/Source/Editor/Gizmo/IGizmoOwner.cs b/Source/Editor/Gizmo/IGizmoOwner.cs
index a28810179..ff653196e 100644
--- a/Source/Editor/Gizmo/IGizmoOwner.cs
+++ b/Source/Editor/Gizmo/IGizmoOwner.cs
@@ -11,6 +11,11 @@ namespace FlaxEditor.Gizmo
[HideInEditor]
public interface IGizmoOwner
{
+ ///
+ /// Gets the gizmos collection.
+ ///
+ FlaxEditor.Viewport.EditorViewport Viewport { get; }
+
///
/// Gets the gizmos collection.
///
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs
index dd5cc6c76..6123d348a 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.cs
@@ -162,10 +162,23 @@ namespace FlaxEditor.Gizmo
// Scale gizmo to fit on-screen
Vector3 position = Position;
- Vector3 vLength = Owner.ViewPosition - position;
- float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
- _screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize);
-
+ if (Owner.Viewport.UseOrthographicProjection)
+ {
+ //[hack] this is far form ideal the View Position is in wrong location, any think using the View Position will have problem
+ //the camera system needs rewrite the to be a camera on springarm, similar how the ArcBallCamera is handled
+ //the ortho projection cannot exist with fps camera because there is no
+ // - focus point to calculate correct View Position with Orthographic Scale as a reference and Orthographic Scale from View Position
+ // with make the camera jump
+ // - and deaph so w and s movment in orto mode moves the cliping plane now
+ float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
+ _screenScale = gizmoSize * (50 * Owner.Viewport.OrthographicScale);
+ }
+ else
+ {
+ Vector3 vLength = Owner.ViewPosition - position;
+ float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
+ _screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize);
+ }
// Setup world
Quaternion orientation = GetSelectedObject(0).Orientation;
_gizmoWorld = new Transform(position, orientation, new Float3(_screenScale));
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index e01fd04bb..969608676 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -513,7 +513,9 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa
WindowsManager::WindowsLocker.Unlock();
}
WindowsManager::WindowsLocker.Lock();
- for (auto& win : WindowsManager::Windows)
+ Array> windows;
+ windows.Add(WindowsManager::Windows);
+ for (Window* win : windows)
{
if (win->IsVisible())
win->OnUpdate(deltaTime);
diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp
index fd9219d35..bed742885 100644
--- a/Source/Editor/Managed/ManagedEditor.cpp
+++ b/Source/Editor/Managed/ManagedEditor.cpp
@@ -330,14 +330,15 @@ bool ManagedEditor::CanReloadScripts()
bool ManagedEditor::CanAutoBuildCSG()
{
+ if (!ManagedEditorOptions.AutoRebuildCSG)
+ return false;
+
// Skip calls from non-managed thread (eg. physics worker)
if (!MCore::Thread::IsAttached())
return false;
if (!HasManagedInstance())
return false;
- if (!ManagedEditorOptions.AutoRebuildCSG)
- return false;
if (Internal_CanAutoBuildCSG == nullptr)
{
Internal_CanAutoBuildCSG = GetClass()->GetMethod("Internal_CanAutoBuildCSG");
@@ -348,14 +349,15 @@ bool ManagedEditor::CanAutoBuildCSG()
bool ManagedEditor::CanAutoBuildNavMesh()
{
+ if (!ManagedEditorOptions.AutoRebuildNavMesh)
+ return false;
+
// Skip calls from non-managed thread (eg. physics worker)
if (!MCore::Thread::IsAttached())
return false;
if (!HasManagedInstance())
return false;
- if (!ManagedEditorOptions.AutoRebuildNavMesh)
- return false;
if (Internal_CanAutoBuildNavMesh == nullptr)
{
Internal_CanAutoBuildNavMesh = GetClass()->GetMethod("Internal_CanAutoBuildNavMesh");
diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs
index eec55c788..80f419a6f 100644
--- a/Source/Editor/Modules/ContentDatabaseModule.cs
+++ b/Source/Editor/Modules/ContentDatabaseModule.cs
@@ -649,8 +649,6 @@ namespace FlaxEditor.Modules
// Special case for folders
if (item is ContentFolder folder)
{
- // TODO: maybe don't remove folders recursive but at once?
-
// Delete all children
if (folder.Children.Count > 0)
{
@@ -664,6 +662,9 @@ namespace FlaxEditor.Modules
// Remove directory
if (deletedByUser && Directory.Exists(path))
{
+ // Flush files removal before removing folder (loaded assets remove file during object destruction in Asset::OnDeleteObject)
+ FlaxEngine.Scripting.FlushRemovedObjects();
+
try
{
Directory.Delete(path, true);
@@ -810,10 +811,9 @@ namespace FlaxEditor.Modules
{
if (node == null)
return;
-
- // Temporary data
var folder = node.Folder;
var path = folder.Path;
+ var canHaveAssets = node.CanHaveAssets;
if (_isDuringFastSetup)
{
@@ -832,20 +832,38 @@ namespace FlaxEditor.Modules
var child = folder.Children[i];
if (!child.Exists)
{
- // Send info
+ // Item doesn't exist anymore
Editor.Log(string.Format($"Content item \'{child.Path}\' has been removed"));
-
- // Destroy it
Delete(child, false);
-
i--;
}
+ else if (canHaveAssets && child is AssetItem childAsset)
+ {
+ // Check if asset type doesn't match the item proxy (eg. item reimported as Material Instance instead of Material)
+ if (FlaxEngine.Content.GetAssetInfo(child.Path, out var assetInfo))
+ {
+ bool changed = assetInfo.ID != childAsset.ID;
+ if (!changed && assetInfo.TypeName != childAsset.TypeName)
+ {
+ // Use proxy check (eg. scene asset might accept different typename than AssetInfo reports)
+ var proxy = GetAssetProxy(childAsset.TypeName, child.Path);
+ if (proxy == null)
+ proxy = GetAssetProxy(assetInfo.TypeName, child.Path);
+ changed = !proxy.AcceptsAsset(assetInfo.TypeName, child.Path);
+ }
+ if (changed)
+ {
+ OnAssetTypeInfoChanged(childAsset, ref assetInfo);
+ i--;
+ }
+ }
+ }
}
}
// Find files
var files = Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly);
- if (node.CanHaveAssets)
+ if (canHaveAssets)
{
LoadAssets(node, files);
}
@@ -1134,17 +1152,19 @@ namespace FlaxEditor.Modules
RebuildInternal();
- Editor.ContentImporting.ImportFileEnd += ContentImporting_ImportFileDone;
+ Editor.ContentImporting.ImportFileEnd += (obj, failed) =>
+ {
+ var path = obj.ResultUrl;
+ if (!failed)
+ FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileDone(path));
+ };
_enableEvents = true;
}
- private void ContentImporting_ImportFileDone(IFileEntryAction obj, bool failed)
+ private void OnImportFileDone(string path)
{
- if (failed)
- return;
-
// Check if already has that element
- var item = Find(obj.ResultUrl);
+ var item = Find(path);
if (item is BinaryAssetItem binaryAssetItem)
{
// Get asset info from the registry (content layer will update cache it just after import)
@@ -1154,19 +1174,8 @@ namespace FlaxEditor.Modules
// For eg. change texture to sprite atlas on reimport
if (binaryAssetItem.TypeName != assetInfo.TypeName)
{
- // Asset type has been changed!
- Editor.LogWarning(string.Format("Asset \'{0}\' changed type from {1} to {2}", item.Path, binaryAssetItem.TypeName, assetInfo.TypeName));
- Editor.Windows.CloseAllEditors(item);
-
- // Remove this item from the database and some related data
var toRefresh = binaryAssetItem.ParentFolder;
- binaryAssetItem.Dispose();
- toRefresh.Children.Remove(binaryAssetItem);
- if (!binaryAssetItem.HasDefaultThumbnail)
- {
- // Delete old thumbnail and remove it from the cache
- Editor.Instance.Thumbnails.DeletePreview(binaryAssetItem);
- }
+ OnAssetTypeInfoChanged(binaryAssetItem, ref assetInfo);
// Refresh the parent folder to find the new asset (it should have different type or some other format)
RefreshFolder(toRefresh, false);
@@ -1183,6 +1192,23 @@ namespace FlaxEditor.Modules
}
}
+ private void OnAssetTypeInfoChanged(AssetItem assetItem, ref AssetInfo assetInfo)
+ {
+ // Asset type has been changed!
+ Editor.LogWarning(string.Format("Asset \'{0}\' changed type from {1} to {2}", assetItem.Path, assetItem.TypeName, assetInfo.TypeName));
+ Editor.Windows.CloseAllEditors(assetItem);
+
+ // Remove this item from the database and some related data
+ assetItem.Dispose();
+ assetItem.ParentFolder.Children.Remove(assetItem);
+
+ // Delete old thumbnail and remove it from the cache
+ if (!assetItem.HasDefaultThumbnail)
+ {
+ Editor.Instance.Thumbnails.DeletePreview(assetItem);
+ }
+ }
+
internal void OnDirectoryEvent(MainContentTreeNode node, FileSystemEventArgs e)
{
// Ensure to be ready for external events
diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs
index c94ba650e..85dd50d35 100644
--- a/Source/Editor/Modules/ContentImportingModule.cs
+++ b/Source/Editor/Modules/ContentImportingModule.cs
@@ -55,7 +55,7 @@ namespace FlaxEditor.Modules
public event Action ImportingQueueBegin;
///
- /// Occurs when file is being imported.
+ /// Occurs when file is being imported. Can be called on non-main thread.
///
public event Action ImportFileBegin;
@@ -67,12 +67,12 @@ namespace FlaxEditor.Modules
public delegate void ImportFileEndDelegate(IFileEntryAction entry, bool failed);
///
- /// Occurs when file importing end.
+ /// Occurs when file importing end. Can be called on non-main thread.
///
public event ImportFileEndDelegate ImportFileEnd;
///
- /// Occurs when assets importing ends.
+ /// Occurs when assets importing ends. Can be called on non-main thread.
///
public event Action ImportingQueueEnd;
diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs
index 7789eb7c4..4fb28ac76 100644
--- a/Source/Editor/Modules/SceneModule.cs
+++ b/Source/Editor/Modules/SceneModule.cs
@@ -266,6 +266,19 @@ namespace FlaxEditor.Modules
Editor.StateMachine.ChangingScenesState.LoadScene(sceneId, additive);
}
+ ///
+ /// Reload all loaded scenes.
+ ///
+ public void ReloadScenes()
+ {
+ foreach (var scene in Level.Scenes)
+ {
+ var sceneId = scene.ID;
+ if (!Level.UnloadScene(scene))
+ Level.LoadScene(sceneId);
+ }
+ }
+
///
/// Closes scene (async).
///
diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
index 225bad082..082439402 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
@@ -147,6 +147,17 @@ namespace FlaxEditor.Modules.SourceCodeEditing
}
if (key != null)
xml.TryGetValue(key, out text);
+
+ // Customize tooltips for properties to be more human-readable in UI
+ if (text != null && memberType.HasFlag(MemberTypes.Property) && text.StartsWith("Gets or sets ", StringComparison.Ordinal))
+ {
+ text = text.Substring(13);
+ unsafe
+ {
+ fixed (char* e = text)
+ e[0] = char.ToUpper(e[0]);
+ }
+ }
}
}
diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
index e9ff4c5b5..96cf44e09 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
@@ -410,9 +410,11 @@ namespace FlaxEditor.Modules.SourceCodeEditing
base.OnUpdate();
// Automatic project files generation after workspace modifications
- if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty)
+ if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty && !ScriptsBuilder.IsCompiling)
{
- Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync();
+ // Try to delay generation when a lot of files are added at once
+ if (ScriptsBuilder.IsSourceDirtyFor(TimeSpan.FromMilliseconds(150)))
+ Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync();
}
}
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 492d78918..3386d411c 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -2,7 +2,6 @@
using System;
using System.IO;
-using System.Linq;
using System.Collections.Generic;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI;
@@ -11,7 +10,6 @@ using FlaxEditor.GUI.Dialogs;
using FlaxEditor.GUI.Input;
using FlaxEditor.Progress.Handlers;
using FlaxEditor.SceneGraph;
-using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.Utilities;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Windows;
@@ -41,6 +39,7 @@ namespace FlaxEditor.Modules
ContextMenuSingleSelectGroup _numberOfClientsGroup = new ContextMenuSingleSelectGroup();
private ContextMenuButton _menuFileSaveScenes;
+ private ContextMenuButton _menuFileReloadScenes;
private ContextMenuButton _menuFileCloseScenes;
private ContextMenuButton _menuFileOpenScriptsProject;
private ContextMenuButton _menuFileGenerateScriptsProjectFiles;
@@ -208,6 +207,7 @@ namespace FlaxEditor.Modules
_toolStripScale.Checked = gizmoMode == TransformGizmoBase.Mode.Scale;
//
_toolStripBuildScenes.Enabled = (canEditScene && !isPlayMode) || Editor.StateMachine.BuildingScenesState.IsActive;
+ _toolStripBuildScenes.Visible = Editor.Options.Options.General.BuildActions?.Length != 0;
_toolStripCook.Enabled = Editor.Windows.GameCookerWin.CanBuild(Platform.PlatformType) && !GameCooker.IsRunning;
//
var play = _toolStripPlay;
@@ -471,13 +471,13 @@ namespace FlaxEditor.Modules
// Place dialog nearby the target control
var targetControlDesktopCenter = targetControl.PointToScreen(targetControl.Size * 0.5f);
var desktopSize = Platform.GetMonitorBounds(targetControlDesktopCenter);
- var pos = targetControlDesktopCenter + new Float2(10.0f, -dialog.Height * 0.5f);
- var dialogEnd = pos + dialog.Size;
+ var pos = targetControlDesktopCenter + new Float2(10.0f, -dialog.DialogSize.Y * 0.5f);
+ var dialogEnd = pos + dialog.DialogSize;
var desktopEnd = desktopSize.BottomRight - new Float2(10.0f);
if (dialogEnd.X >= desktopEnd.X || dialogEnd.Y >= desktopEnd.Y)
- pos = targetControl.PointToScreen(Float2.Zero) - new Float2(10.0f + dialog.Width, dialog.Height);
+ pos = targetControl.PointToScreen(Float2.Zero) - new Float2(10.0f + dialog.DialogSize.X, dialog.DialogSize.Y);
var desktopBounds = Platform.VirtualDesktopBounds;
- pos = Float2.Clamp(pos, desktopBounds.UpperLeft, desktopBounds.BottomRight - dialog.Size);
+ pos = Float2.Clamp(pos, desktopBounds.UpperLeft, desktopBounds.BottomRight - dialog.DialogSize);
dialog.RootWindow.Window.Position = pos;
// Register for context menu (prevent auto-closing context menu when selecting color)
@@ -528,6 +528,7 @@ namespace FlaxEditor.Modules
_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);
+ _menuFileReloadScenes = cm.AddButton("Reload scenes", Editor.Scene.ReloadScenes);
cm.AddSeparator();
_menuFileOpenScriptsProject = cm.AddButton("Open scripts project", inputOptions.OpenScriptsProject, Editor.CodeEditing.OpenSolution);
_menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", inputOptions.GenerateScriptsProject, Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync);
@@ -653,7 +654,7 @@ namespace FlaxEditor.Modules
cm.AddButton("Information about Flax", () => new AboutDialog().Show());
}
- private void OnOptionsChanged(FlaxEditor.Options.EditorOptions options)
+ private void OnOptionsChanged(EditorOptions options)
{
var inputOptions = options.Input;
@@ -688,6 +689,8 @@ namespace FlaxEditor.Modules
_menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString();
MainMenuShortcutKeysUpdated?.Invoke();
+
+ UpdateToolstrip();
}
private void InitToolstrip(RootControl mainWindow)
@@ -709,11 +712,11 @@ namespace FlaxEditor.Modules
_toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})");
ToolStrip.AddSeparator();
- // Cook scenes
+ // Build 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 ({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 ({inputOptions.Play})");
+ _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.CookAndRun})");
_toolStripCook.ContextMenu = new ContextMenu();
_toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
_toolStripCook.ContextMenu.AddSeparator();
@@ -829,6 +832,7 @@ namespace FlaxEditor.Modules
_menuFileSaveScenes.Enabled = hasOpenedScene;
_menuFileCloseScenes.Enabled = hasOpenedScene;
+ _menuFileReloadScenes.Enabled = hasOpenedScene;
_menuFileGenerateScriptsProjectFiles.Enabled = !Editor.ProgressReporting.GenerateScriptsProjectFiles.IsActive;
c.PerformLayout();
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index d0c8b9747..cda83b56e 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -171,9 +171,13 @@ namespace FlaxEditor.Modules
var mainWindow = MainWindow;
if (mainWindow)
{
- var projectPath = Globals.ProjectFolder.Replace('/', '\\');
- var platformBit = Platform.Is64BitApp ? "64" : "32";
- var title = string.Format("Flax Editor - \'{0}\' ({1}-bit)", projectPath, platformBit);
+ var projectPath = Globals.ProjectFolder;
+#if PLATFORM_WINDOWS
+ projectPath = projectPath.Replace('/', '\\');
+#endif
+ var engineVersion = Editor.EngineProject.Version;
+ var engineVersionText = engineVersion.Revision > 0 ? $"{engineVersion.Major}.{engineVersion.Minor}.{engineVersion.Revision}" : $"{engineVersion.Major}.{engineVersion.Minor}";
+ var title = $"Flax Editor {engineVersionText} - \'{projectPath}\'";
mainWindow.Title = title;
}
}
@@ -237,7 +241,11 @@ namespace FlaxEditor.Modules
///
public void LoadDefaultLayout()
{
- LoadLayout(StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/LayoutDefault.xml"));
+ var path = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/LayoutDefault.xml");
+ if (File.Exists(path))
+ {
+ LoadLayout(path);
+ }
}
///
@@ -276,9 +284,6 @@ namespace FlaxEditor.Modules
// Get metadata
int version = int.Parse(root.Attributes["Version"].Value, CultureInfo.InvariantCulture);
- var virtualDesktopBounds = Platform.VirtualDesktopBounds;
- var virtualDesktopSafeLeftCorner = virtualDesktopBounds.Location;
- var virtualDesktopSafeRightCorner = virtualDesktopBounds.BottomRight;
switch (version)
{
@@ -288,31 +293,9 @@ namespace FlaxEditor.Modules
if (MainWindow)
{
var mainWindowNode = root["MainWindow"];
- Rectangle bounds = LoadBounds(mainWindowNode["Bounds"]);
- bool isMaximized = bool.Parse(mainWindowNode.GetAttribute("IsMaximized"));
-
- // Clamp position to match current desktop dimensions (if window was on desktop that is now inactive)
- if (bounds.X < virtualDesktopSafeLeftCorner.X || bounds.Y < virtualDesktopSafeLeftCorner.Y || bounds.X > virtualDesktopSafeRightCorner.X || bounds.Y > virtualDesktopSafeRightCorner.Y)
- bounds.Location = virtualDesktopSafeLeftCorner;
-
- if (isMaximized)
- {
- if (MainWindow.IsMaximized)
- MainWindow.Restore();
- MainWindow.ClientPosition = bounds.Location;
- MainWindow.Maximize();
- }
- else
- {
- if (Mathf.Min(bounds.Size.X, bounds.Size.Y) >= 1)
- {
- MainWindow.ClientBounds = bounds;
- }
- else
- {
- MainWindow.ClientPosition = bounds.Location;
- }
- }
+ bool isMaximized = true, isMinimized = false;
+ Rectangle bounds = LoadBounds(mainWindowNode, ref isMaximized, ref isMinimized);
+ LoadWindow(MainWindow, ref bounds, isMaximized, false);
}
// Load master panel structure
@@ -332,11 +315,13 @@ namespace FlaxEditor.Modules
continue;
// Get window properties
- Rectangle bounds = LoadBounds(child["Bounds"]);
+ bool isMaximized = false, isMinimized = false;
+ Rectangle bounds = LoadBounds(child, ref isMaximized, ref isMinimized);
// Create window and floating dock panel
var window = FloatWindowDockPanel.CreateFloatWindow(MainWindow.GUI, bounds.Location, bounds.Size, WindowStartPosition.Manual, string.Empty);
var panel = new FloatWindowDockPanel(masterPanel, window.GUI);
+ LoadWindow(panel.Window.Window, ref bounds, isMaximized, isMinimized);
// Load structure
LoadPanel(child, panel);
@@ -493,23 +478,67 @@ namespace FlaxEditor.Modules
private static void SaveBounds(XmlWriter writer, Window win)
{
- var bounds = win.ClientBounds;
-
- writer.WriteAttributeString("X", bounds.X.ToString(CultureInfo.InvariantCulture));
- writer.WriteAttributeString("Y", bounds.Y.ToString(CultureInfo.InvariantCulture));
- writer.WriteAttributeString("Width", bounds.Width.ToString(CultureInfo.InvariantCulture));
- writer.WriteAttributeString("Height", bounds.Height.ToString(CultureInfo.InvariantCulture));
+ writer.WriteStartElement("Bounds");
+ {
+ var bounds = win.ClientBounds;
+ writer.WriteAttributeString("X", bounds.X.ToString(CultureInfo.InvariantCulture));
+ writer.WriteAttributeString("Y", bounds.Y.ToString(CultureInfo.InvariantCulture));
+ writer.WriteAttributeString("Width", bounds.Width.ToString(CultureInfo.InvariantCulture));
+ writer.WriteAttributeString("Height", bounds.Height.ToString(CultureInfo.InvariantCulture));
+ writer.WriteAttributeString("IsMaximized", win.IsMaximized.ToString());
+ writer.WriteAttributeString("IsMinimized", win.IsMinimized.ToString());
+ }
+ writer.WriteEndElement();
}
- private static Rectangle LoadBounds(XmlElement node)
+ private static Rectangle LoadBounds(XmlElement node, ref bool isMaximized, ref bool isMinimized)
{
- float x = float.Parse(node.GetAttribute("X"), CultureInfo.InvariantCulture);
- float y = float.Parse(node.GetAttribute("Y"), CultureInfo.InvariantCulture);
- float width = float.Parse(node.GetAttribute("Width"), CultureInfo.InvariantCulture);
- float height = float.Parse(node.GetAttribute("Height"), CultureInfo.InvariantCulture);
+ var bounds = node["Bounds"];
+ var isMaximizedText = bounds.GetAttribute("IsMaximized");
+ if (!string.IsNullOrEmpty(isMaximizedText))
+ isMaximized = bool.Parse(isMaximizedText);
+ var isMinimizedText = bounds.GetAttribute("IsMinimized");
+ if (!string.IsNullOrEmpty(isMinimizedText))
+ isMinimized = bool.Parse(isMinimizedText);
+ float x = float.Parse(bounds.GetAttribute("X"), CultureInfo.InvariantCulture);
+ float y = float.Parse(bounds.GetAttribute("Y"), CultureInfo.InvariantCulture);
+ float width = float.Parse(bounds.GetAttribute("Width"), CultureInfo.InvariantCulture);
+ float height = float.Parse(bounds.GetAttribute("Height"), CultureInfo.InvariantCulture);
return new Rectangle(x, y, width, height);
}
+ private static void LoadWindow(Window win, ref Rectangle bounds, bool isMaximized, bool isMinimized)
+ {
+ var virtualDesktopBounds = Platform.VirtualDesktopBounds;
+ var virtualDesktopSafeLeftCorner = virtualDesktopBounds.Location;
+ var virtualDesktopSafeRightCorner = virtualDesktopBounds.BottomRight;
+
+ // Clamp position to match current desktop dimensions (if window was on desktop that is now inactive)
+ if (bounds.X < virtualDesktopSafeLeftCorner.X || bounds.Y < virtualDesktopSafeLeftCorner.Y || bounds.X > virtualDesktopSafeRightCorner.X || bounds.Y > virtualDesktopSafeRightCorner.Y)
+ bounds.Location = virtualDesktopSafeLeftCorner;
+
+ if (isMaximized)
+ {
+ if (win.IsMaximized)
+ win.Restore();
+ win.ClientPosition = bounds.Location;
+ win.Maximize();
+ }
+ else
+ {
+ if (Mathf.Min(bounds.Size.X, bounds.Size.Y) >= 1)
+ {
+ win.ClientBounds = bounds;
+ }
+ else
+ {
+ win.ClientPosition = bounds.Location;
+ }
+ if (isMinimized)
+ win.Minimize();
+ }
+ }
+
private class LayoutNameDialog : Dialog
{
private TextBox _textbox;
@@ -609,13 +638,8 @@ namespace FlaxEditor.Modules
if (MainWindow)
{
writer.WriteStartElement("MainWindow");
- writer.WriteAttributeString("IsMaximized", MainWindow.IsMaximized.ToString());
-
- writer.WriteStartElement("Bounds");
SaveBounds(writer, MainWindow);
writer.WriteEndElement();
-
- writer.WriteEndElement();
}
// Master panel structure
@@ -628,22 +652,13 @@ namespace FlaxEditor.Modules
{
var panel = masterPanel.FloatingPanels[i];
var window = panel.Window;
-
if (window == null)
- {
- Editor.LogWarning("Floating panel has missing window");
continue;
- }
writer.WriteStartElement("Float");
-
SavePanel(writer, panel);
-
- writer.WriteStartElement("Bounds");
SaveBounds(writer, window.Window);
writer.WriteEndElement();
-
- writer.WriteEndElement();
}
writer.WriteEndElement();
@@ -724,7 +739,6 @@ namespace FlaxEditor.Modules
settings.Size = Platform.DesktopSize * 0.75f;
settings.StartPosition = WindowStartPosition.CenterScreen;
settings.ShowAfterFirstPaint = true;
-
#if PLATFORM_WINDOWS
if (!Editor.Instance.Options.Options.Interface.UseNativeWindowSystem)
{
@@ -736,12 +750,9 @@ namespace FlaxEditor.Modules
#elif PLATFORM_LINUX
settings.HasBorder = false;
#endif
-
MainWindow = Platform.CreateWindow(ref settings);
-
if (MainWindow == null)
{
- // Error
Editor.LogError("Failed to create editor main window!");
return;
}
diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs
index 33124bab0..9f29f4bdb 100644
--- a/Source/Editor/Options/GeneralOptions.cs
+++ b/Source/Editor/Options/GeneralOptions.cs
@@ -117,7 +117,7 @@ namespace FlaxEditor.Options
///
/// Gets or sets the sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).
///
- [EditorDisplay("General"), EditorOrder(200), Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")]
+ [EditorDisplay("General"), EditorOrder(200), ExpandGroups, Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")]
public BuildAction[] BuildActions { get; set; } =
{
BuildAction.CSG,
diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs
index eb61c0f68..95c3c1d6f 100644
--- a/Source/Editor/Options/InputBinding.cs
+++ b/Source/Editor/Options/InputBinding.cs
@@ -259,10 +259,7 @@ namespace FlaxEditor.Options
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
- {
return true;
- }
-
return base.CanConvertFrom(context, sourceType);
}
@@ -270,9 +267,7 @@ namespace FlaxEditor.Options
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
- {
return false;
- }
return base.CanConvertTo(context, destinationType);
}
@@ -284,7 +279,6 @@ namespace FlaxEditor.Options
InputBinding.TryParse(str, out var result);
return result;
}
-
return base.ConvertFrom(context, culture, value);
}
@@ -295,7 +289,6 @@ namespace FlaxEditor.Options
{
return ((InputBinding)value).ToString();
}
-
return base.ConvertTo(context, culture, value, destinationType);
}
}
diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs
index 90d098bb6..e2e7d0e71 100644
--- a/Source/Editor/Options/InputOptions.cs
+++ b/Source/Editor/Options/InputOptions.cs
@@ -76,6 +76,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Common"), EditorOrder(230)]
public InputBinding RotateSelection = new InputBinding(KeyboardKeys.R);
+ [DefaultValue(typeof(InputBinding), "F11")]
+ [EditorDisplay("Common"), EditorOrder(240)]
+ public InputBinding ToggleFullscreen = new InputBinding(KeyboardKeys.F11);
+
#endregion
#region File
@@ -208,16 +212,20 @@ namespace FlaxEditor.Options
[EditorDisplay("Debugger", "Continue"), EditorOrder(810)]
public InputBinding DebuggerContinue = new InputBinding(KeyboardKeys.F5);
+ [DefaultValue(typeof(InputBinding), "Shift+F11")]
+ [EditorDisplay("Debugger", "Unlock mouse in Play Mode"), EditorOrder(820)]
+ public InputBinding DebuggerUnlockMouse = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift);
+
[DefaultValue(typeof(InputBinding), "F10")]
- [EditorDisplay("Debugger", "Step Over"), EditorOrder(820)]
+ [EditorDisplay("Debugger", "Step Over"), EditorOrder(830)]
public InputBinding DebuggerStepOver = new InputBinding(KeyboardKeys.F10);
[DefaultValue(typeof(InputBinding), "F11")]
- [EditorDisplay("Debugger", "Step Into"), EditorOrder(830)]
+ [EditorDisplay("Debugger", "Step Into"), EditorOrder(840)]
public InputBinding DebuggerStepInto = new InputBinding(KeyboardKeys.F11);
[DefaultValue(typeof(InputBinding), "Shift+F11")]
- [EditorDisplay("Debugger", "Step Out"), EditorOrder(840)]
+ [EditorDisplay("Debugger", "Step Out"), EditorOrder(850)]
public InputBinding DebuggerStepOut = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift);
#endregion
diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs
index c2d744239..07e899c5e 100644
--- a/Source/Editor/Options/OptionsModule.cs
+++ b/Source/Editor/Options/OptionsModule.cs
@@ -244,11 +244,11 @@ namespace FlaxEditor.Options
CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC),
ProgressNormal = Color.FromBgra(0xFF0ad328),
- Statusbar = new Style.StatusbarStyle()
+ Statusbar = new Style.StatusbarStyle
{
PlayMode = Color.FromBgra(0xFF2F9135),
Failed = Color.FromBgra(0xFF9C2424),
- Loading = Color.FromBgra(0xFF2D2D30)
+ Loading = Color.FromBgra(0xFF2D2D30),
},
// Fonts
@@ -271,7 +271,7 @@ namespace FlaxEditor.Options
Scale = Editor.Icons.Scale32,
Scalar = Editor.Icons.Scalar32,
- SharedTooltip = new Tooltip()
+ SharedTooltip = new Tooltip(),
};
style.DragWindow = style.BackgroundSelected * 0.7f;
diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs
index cee63a562..0fd018cbc 100644
--- a/Source/Editor/Options/ViewportOptions.cs
+++ b/Source/Editor/Options/ViewportOptions.cs
@@ -26,45 +26,108 @@ namespace FlaxEditor.Options
public float MouseWheelSensitivity { get; set; } = 1.0f;
///
- /// Gets or sets the default movement speed for the viewport camera (must match the dropdown menu values in the viewport).
+ /// Gets or sets the total amount of steps the camera needs to go from minimum to maximum speed.
///
- [DefaultValue(1.0f), Limit(0.01f, 100.0f)]
- [EditorDisplay("Defaults"), EditorOrder(110), Tooltip("The default movement speed for the viewport camera (must match the dropdown menu values in the viewport).")]
- public float DefaultMovementSpeed { get; set; } = 1.0f;
+ [DefaultValue(64), Limit(1, 128)]
+ [EditorDisplay("Camera"), EditorOrder(110), Tooltip("The total amount of steps the camera needs to go from minimum to maximum speed.")]
+ public int TotalCameraSpeedSteps { get; set; } = 64;
+
+ ///
+ /// Gets or sets the degree to which the camera will be eased when using camera flight in the editor window.
+ ///
+ [DefaultValue(3.0f), Limit(1.0f, 8.0f)]
+ [EditorDisplay("Camera"), EditorOrder(111), Tooltip("The degree to which the camera will be eased when using camera flight in the editor window (ignored if camera easing degree is enabled).")]
+ public float CameraEasingDegree { get; set; } = 3.0f;
+
+ ///
+ /// Gets or sets the default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).
+ ///
+ [DefaultValue(1.0f), Limit(0.05f, 32.0f)]
+ [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).")]
+ public float MovementSpeed { get; set; } = 1.0f;
+
+ ///
+ /// Gets or sets the default minimum camera movement speed.
+ ///
+ [DefaultValue(0.05f), Limit(0.05f, 32.0f)]
+ [EditorDisplay("Defaults"), EditorOrder(121), Tooltip("The default minimum movement speed for the viewport camera.")]
+ public float MinMovementSpeed { get; set; } = 0.05f;
+
+ ///
+ /// Gets or sets the default maximum camera movement speed.
+ ///
+ [DefaultValue(32.0f), Limit(16.0f, 1000.0f)]
+ [EditorDisplay("Defaults"), EditorOrder(122), Tooltip("The default maximum movement speed for the viewport camera.")]
+ public float MaxMovementSpeed { get; set; } = 32f;
+
+ ///
+ /// Gets or sets the default camera easing mode.
+ ///
+ [DefaultValue(true)]
+ [EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default camera easing mode.")]
+ public bool UseCameraEasing { get; set; } = true;
///
/// Gets or sets the default near clipping plane distance for the viewport camera.
///
[DefaultValue(10.0f), Limit(0.001f, 1000.0f)]
- [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default near clipping plane distance for the viewport camera.")]
- public float DefaultNearPlane { get; set; } = 10.0f;
+ [EditorDisplay("Defaults"), EditorOrder(140), Tooltip("The default near clipping plane distance for the viewport camera.")]
+ public float NearPlane { get; set; } = 10.0f;
///
/// Gets or sets the default far clipping plane distance for the viewport camera.
///
[DefaultValue(40000.0f), Limit(10.0f)]
- [EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default far clipping plane distance for the viewport camera.")]
- public float DefaultFarPlane { get; set; } = 40000.0f;
+ [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("The default far clipping plane distance for the viewport camera.")]
+ public float FarPlane { get; set; } = 40000.0f;
///
/// Gets or sets the default field of view angle (in degrees) for the viewport camera.
///
[DefaultValue(60.0f), Limit(35.0f, 160.0f, 0.1f)]
- [EditorDisplay("Defaults", "Default Field Of View"), EditorOrder(140), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
- public float DefaultFieldOfView { get; set; } = 60.0f;
+ [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
+ public float FieldOfView { get; set; } = 60.0f;
///
- /// Gets or sets if the panning direction is inverted for the viewport camera.
+ /// Gets or sets the default camera orthographic mode.
///
[DefaultValue(false)]
- [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("Invert the panning direction for the viewport camera.")]
- public bool DefaultInvertPanning { get; set; } = false;
+ [EditorDisplay("Defaults"), EditorOrder(170), Tooltip("The default camera orthographic mode.")]
+ public bool UseOrthographicProjection { get; set; } = false;
///
- /// Scales editor viewport grid.
+ /// Gets or sets the default camera orthographic scale (if camera uses orthographic mode).
+ ///
+ [DefaultValue(5.0f), Limit(0.001f, 100000.0f, 0.1f)]
+ [EditorDisplay("Defaults"), EditorOrder(180), Tooltip("The default camera orthographic scale (if camera uses orthographic mode).")]
+ public float OrthographicScale { get; set; } = 5.0f;
+
+ ///
+ /// Gets or sets the default panning direction for the viewport camera.
+ ///
+ [DefaultValue(false)]
+ [EditorDisplay("Defaults"), EditorOrder(190), Tooltip("The default panning direction for the viewport camera.")]
+ public bool InvertPanning { get; set; } = false;
+
+ ///
+ /// Gets or sets the default relative panning mode.
+ ///
+ [DefaultValue(true)]
+ [EditorDisplay("Defaults"), EditorOrder(200), Tooltip("The default relative panning mode. Uses distance between camera and target to determine panning speed.")]
+ public bool UseRelativePanning { get; set; } = true;
+
+ ///
+ /// Gets or sets the default panning speed (ignored if relative panning is speed enabled).
+ ///
+ [DefaultValue(0.8f), Limit(0.01f, 128.0f, 0.1f)]
+ [EditorDisplay("Defaults"), EditorOrder(210), Tooltip("The default camera panning speed (ignored if relative panning is enabled).")]
+ public float PanningSpeed { get; set; } = 0.8f;
+
+ ///
+ /// Gets or sets the default editor viewport grid scale.
///
[DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)]
- [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("Scales editor viewport grid.")]
+ [EditorDisplay("Defaults"), EditorOrder(220), Tooltip("The default editor viewport grid scale.")]
public float ViewportGridScale { get; set; } = 50.0f;
}
}
diff --git a/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs b/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs
index 7bbefbc2d..fa1e943f0 100644
--- a/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs
+++ b/Source/Editor/Progress/Handlers/ImportAssetsProgress.cs
@@ -19,25 +19,25 @@ namespace FlaxEditor.Progress.Handlers
public ImportAssetsProgress()
{
var importing = Editor.Instance.ContentImporting;
- importing.ImportingQueueBegin += OnStart;
- importing.ImportingQueueEnd += OnEnd;
+ importing.ImportingQueueBegin += () => FlaxEngine.Scripting.InvokeOnUpdate(OnStart);
+ importing.ImportingQueueEnd += () => FlaxEngine.Scripting.InvokeOnUpdate(OnEnd);
importing.ImportFileBegin += OnImportFileBegin;
}
private void OnImportFileBegin(IFileEntryAction importFileEntry)
{
+ string info;
if (importFileEntry is ImportFileEntry)
- _currentInfo = string.Format("Importing \'{0}\'", System.IO.Path.GetFileName(importFileEntry.SourceUrl));
+ info = string.Format("Importing \'{0}\'", System.IO.Path.GetFileName(importFileEntry.SourceUrl));
else
- _currentInfo = string.Format("Creating \'{0}\'", importFileEntry.SourceUrl);
- UpdateProgress();
- }
-
- private void UpdateProgress()
- {
- var importing = Editor.Instance.ContentImporting;
- var info = string.Format("{0} ({1}/{2})...", _currentInfo, importing.ImportBatchDone, importing.ImportBatchSize);
- OnUpdate(importing.ImportingProgress, info);
+ info = string.Format("Creating \'{0}\'", importFileEntry.SourceUrl);
+ FlaxEngine.Scripting.InvokeOnUpdate(() =>
+ {
+ _currentInfo = info;
+ var importing = Editor.Instance.ContentImporting;
+ var text = string.Format("{0} ({1}/{2})...", _currentInfo, importing.ImportBatchDone, importing.ImportBatchSize);
+ OnUpdate(importing.ImportingProgress, text);
+ });
}
}
}
diff --git a/Source/Editor/ProjectInfo.cpp b/Source/Editor/ProjectInfo.cpp
index 4e7ee4483..30c558b5d 100644
--- a/Source/Editor/ProjectInfo.cpp
+++ b/Source/Editor/ProjectInfo.cpp
@@ -154,7 +154,8 @@ bool ProjectInfo::LoadProject(const String& projectPath)
Version = ::Version(
JsonTools::GetInt(version, "Major", 0),
JsonTools::GetInt(version, "Minor", 0),
- JsonTools::GetInt(version, "Build", 0));
+ JsonTools::GetInt(version, "Build", -1),
+ JsonTools::GetInt(version, "Revision", -1));
}
}
if (Version.Revision() == 0)
diff --git a/Source/Editor/ProjectInfo.cs b/Source/Editor/ProjectInfo.cs
index b00c4e042..083665f0f 100644
--- a/Source/Editor/ProjectInfo.cs
+++ b/Source/Editor/ProjectInfo.cs
@@ -23,17 +23,11 @@ namespace FlaxEditor
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
- {
writer.WriteNull();
- }
else if (value is Version)
- {
writer.WriteValue(value.ToString());
- }
else
- {
throw new JsonSerializationException("Expected Version object value");
- }
}
///
@@ -47,65 +41,60 @@ namespace FlaxEditor
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
- {
return null;
- }
- else
+
+ if (reader.TokenType == JsonToken.StartObject)
{
- if (reader.TokenType == JsonToken.StartObject)
+ try
{
- try
+ reader.Read();
+ var values = new Dictionary();
+ while (reader.TokenType == JsonToken.PropertyName)
{
+ var key = reader.Value as string;
reader.Read();
- Dictionary values = new Dictionary();
- while (reader.TokenType == JsonToken.PropertyName)
- {
- var key = reader.Value as string;
- reader.Read();
- var val = (long)reader.Value;
- reader.Read();
- values.Add(key, (int)val);
- }
+ var val = (long)reader.Value;
+ reader.Read();
+ values.Add(key, (int)val);
+ }
- int major = 0, minor = 0, build = 0;
- values.TryGetValue("Major", out major);
- values.TryGetValue("Minor", out minor);
- values.TryGetValue("Build", out build);
+ values.TryGetValue("Major", out var major);
+ values.TryGetValue("Minor", out var minor);
+ if (!values.TryGetValue("Build", out var build))
+ build = -1;
+ if (!values.TryGetValue("Revision", out var revision))
+ revision = -1;
- Version v = new Version(major, minor, build);
- return v;
- }
- catch (Exception ex)
- {
- throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
- }
+ if (build <= 0)
+ return new Version(major, minor);
+ if (revision <= 0)
+ return new Version(major, minor, build);
+ return new Version(major, minor, build, revision);
}
- else if (reader.TokenType == JsonToken.String)
+ catch (Exception ex)
{
- try
- {
- Version v = new Version((string)reader.Value!);
- return v;
- }
- catch (Exception ex)
- {
- throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
- }
- }
- else
- {
- throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value));
+ throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
}
}
+ if (reader.TokenType == JsonToken.String)
+ {
+ try
+ {
+ return new Version((string)reader.Value!);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
+ }
+ }
+ throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value));
}
///
/// Determines whether this instance can convert the specified object type.
///
/// Type of the object.
- ///
- /// true if this instance can convert the specified object type; otherwise, false.
- ///
+ /// true if this instance can convert the specified object type; otherwise, false.
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Version);
diff --git a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
index c09e1a246..ed6fc08d6 100644
--- a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
+++ b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
@@ -7,6 +7,7 @@ using Real = System.Single;
#endif
using FlaxEngine;
+using FlaxEngine.GUI;
namespace FlaxEditor.SceneGraph.Actors
{
@@ -30,6 +31,13 @@ namespace FlaxEditor.SceneGraph.Actors
// Rotate to match the space (GUI uses upper left corner as a root)
Actor.LocalOrientation = Quaternion.Euler(0, -180, -180);
+ var uiControl = new UIControl
+ {
+ Name = "Canvas Scalar",
+ Transform = Actor.Transform,
+ Control = new CanvasScaler()
+ };
+ Root.Spawn(uiControl, Actor);
}
///
diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
index d392e8309..f64e46385 100644
--- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
+++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
@@ -66,7 +66,8 @@ namespace FlaxEditor.SceneGraph.GUI
_orderInParent = actor.OrderInParent;
Visible = (actor.HideFlags & HideFlags.HideInHierarchy) == 0;
- var id = actor.ID;
+ // Pick the correct id when inside a prefab window.
+ var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID;
if (Editor.Instance.ProjectCache.IsExpandedActor(ref id))
{
Expand(true);
@@ -171,7 +172,8 @@ namespace FlaxEditor.SceneGraph.GUI
// Restore cached state on query filter clear
if (noFilter && actor != null)
{
- var id = actor.ID;
+ // Pick the correct id when inside a prefab window.
+ var id = actor.HasPrefabLink && actor.Scene.Scene == null ? actor.PrefabObjectID : actor.ID;
isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id);
}
@@ -264,7 +266,7 @@ namespace FlaxEditor.SceneGraph.GUI
///
/// Starts the actor renaming action.
///
- public void StartRenaming(EditorWindow window)
+ public void StartRenaming(EditorWindow window, Panel treePanel = null)
{
// Block renaming during scripts reload
if (Editor.Instance.ProgressReporting.CompileScripts.IsActive)
@@ -279,7 +281,13 @@ namespace FlaxEditor.SceneGraph.GUI
(window as PrefabWindow).ScrollingOnTreeView(false);
// Start renaming the actor
- var dialog = RenamePopup.Show(this, TextRect, _actorNode.Name, false);
+ var rect = TextRect;
+ if (treePanel != null)
+ {
+ treePanel.ScrollViewTo(this, true);
+ rect.Size = new Float2(treePanel.Width - TextRect.Location.X, TextRect.Height);
+ }
+ var dialog = RenamePopup.Show(this, rect, _actorNode.Name, false);
dialog.Renamed += OnRenamed;
dialog.Closed += popup =>
{
@@ -301,10 +309,12 @@ namespace FlaxEditor.SceneGraph.GUI
protected override void OnExpandedChanged()
{
base.OnExpandedChanged();
+ var actor = Actor;
- if (!IsLayoutLocked && Actor)
+ if (!IsLayoutLocked && actor)
{
- var id = Actor.ID;
+ // Pick the correct id when inside a prefab window.
+ var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID;
Editor.Instance.ProjectCache.SetExpandedActor(ref id, IsExpanded);
}
}
diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
index 7f5ca6f17..de0a30e40 100644
--- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
@@ -207,9 +207,9 @@ void RiderCodeEditor::FindEditors(Array* output)
FileSystem::GetChildDirectories(subDirectories, TEXT("/opt/"));
// Versions installed via JetBrains Toolbox
- SearchDirectory(&installations, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/rider/"));
- FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/Rider/ch-0"));
- FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/Rider/ch-1")); // Beta versions
+ SearchDirectory(&installations, localAppDataPath / TEXT("JetBrains/Toolbox/apps/rider/"));
+ FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains/Toolbox/apps/Rider/ch-0"));
+ FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains/Toolbox/apps/Rider/ch-1")); // Beta versions
// Detect Flatpak installations
SearchDirectory(&installations,
diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp
index 2357fd241..a65b3ec9e 100644
--- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp
+++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioConnection.cpp
@@ -286,81 +286,17 @@ namespace VisualStudio
return "Visual Studio open timout";
}
- static ComPtr FindItem(const ComPtr& projectItems, BSTR filePath)
- {
- long count;
- projectItems->get_Count(&count);
- if (count == 0)
- return nullptr;
-
- for (long i = 1; i <= count; i++) // They are counting from [1..Count]
- {
- ComPtr projectItem;
- projectItems->Item(_variant_t(i), &projectItem);
- if (!projectItem)
- continue;
-
- short fileCount = 0;
- projectItem->get_FileCount(&fileCount);
- for (short fileIndex = 1; fileIndex <= fileCount; fileIndex++)
- {
- _bstr_t filename;
- projectItem->get_FileNames(fileIndex, filename.GetAddress());
-
- if (filename.GetBSTR() != nullptr && AreFilePathsEqual(filePath, filename))
- {
- return projectItem;
- }
- }
-
- ComPtr childProjectItems;
- projectItem->get_ProjectItems(&childProjectItems);
- if (childProjectItems)
- {
- ComPtr result = FindItem(childProjectItems, filePath);
- if (result)
- return result;
- }
- }
-
- return nullptr;
- }
-
- static ComPtr FindItem(const ComPtr& solution, BSTR filePath)
- {
+ static ComPtr FindItem(const ComPtr& solution, BSTR filePath)
+ {
HRESULT result;
- ComPtr projects;
- result = solution->get_Projects(&projects);
+ ComPtr projectItem;
+ result = solution->FindProjectItem(filePath, &projectItem);
if (FAILED(result))
return nullptr;
- long projectsCount = 0;
- result = projects->get_Count(&projectsCount);
- if (FAILED(result))
- return nullptr;
-
- for (long projectIndex = 1; projectIndex <= projectsCount; projectIndex++) // They are counting from [1..Count]
- {
- ComPtr project;
- result = projects->Item(_variant_t(projectIndex), &project);
- if (FAILED(result) || !project)
- continue;
-
- ComPtr projectItems;
- result = project->get_ProjectItems(&projectItems);
- if (FAILED(result) || !projectItems)
- continue;
-
- auto projectItem = FindItem(projectItems, filePath);
- if (projectItem)
- {
- return projectItem;
- }
- }
-
- return nullptr;
- }
+ return projectItem;
+ }
// Opens a file on a specific line in a running Visual Studio instance.
//
diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp
index 8807379cf..0b3cf6452 100644
--- a/Source/Editor/Scripting/ScriptsBuilder.cpp
+++ b/Source/Editor/Scripting/ScriptsBuilder.cpp
@@ -170,7 +170,7 @@ bool ScriptsBuilder::IsSourceWorkspaceDirty()
bool ScriptsBuilder::IsSourceDirtyFor(const TimeSpan& timeout)
{
ScopeLock scopeLock(_locker);
- return _lastSourceCodeEdited > (_lastCompileAction + timeout);
+ return _lastSourceCodeEdited > _lastCompileAction && DateTime::Now() > _lastSourceCodeEdited + timeout;
}
bool ScriptsBuilder::IsCompiling()
@@ -266,7 +266,7 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& work
bool ScriptsBuilder::GenerateProject(const StringView& customArgs)
{
- String args(TEXT("-log -genproject "));
+ String args(TEXT("-log -mutex -genproject "));
args += customArgs;
_wasProjectStructureChanged = false;
return RunBuildTool(args);
@@ -669,7 +669,7 @@ void ScriptsBuilderService::Update()
}
// Check if compile code (if has been edited)
- const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(50);
+ const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(150);
auto mainWindow = Engine::MainWindow;
if (ScriptsBuilder::IsSourceDirtyFor(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused())
{
diff --git a/Source/Editor/Scripting/ScriptsBuilder.h b/Source/Editor/Scripting/ScriptsBuilder.h
index f954b0fd0..0a11bd9b7 100644
--- a/Source/Editor/Scripting/ScriptsBuilder.h
+++ b/Source/Editor/Scripting/ScriptsBuilder.h
@@ -68,7 +68,7 @@ public:
///
/// Time to use for checking.
/// True if source code is dirty, otherwise false.
- static bool IsSourceDirtyFor(const TimeSpan& timeout);
+ API_FUNCTION() static bool IsSourceDirtyFor(const TimeSpan& timeout);
///
/// Returns true if scripts are being now compiled/reloaded.
diff --git a/Source/Editor/States/LoadingState.cs b/Source/Editor/States/LoadingState.cs
index 698dc192f..d48918e1b 100644
--- a/Source/Editor/States/LoadingState.cs
+++ b/Source/Editor/States/LoadingState.cs
@@ -56,12 +56,14 @@ namespace FlaxEditor.States
else if (Editor.Options.Options.General.ForceScriptCompilationOnStartup && !skipCompile)
{
// Generate project files when Cache is missing or was cleared previously
- if (!Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Intermediate")) ||
- !Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Projects")))
+ var projectFolderPath = Editor.GameProject?.ProjectFolderPath;
+ if (!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Intermediate")) ||
+ !Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Projects")))
{
- var customArgs = Editor.Instance.CodeEditing.SelectedEditor.GenerateProjectCustomArgs;
+ var customArgs = Editor.CodeEditing.SelectedEditor?.GenerateProjectCustomArgs;
ScriptsBuilder.GenerateProject(customArgs);
}
+
// Compile scripts before loading any scenes, then we load them and can open scenes
ScriptsBuilder.Compile();
}
diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
index 9773e9695..7c3aa4f13 100644
--- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
@@ -465,7 +465,7 @@ namespace FlaxEditor.Surface.Archetypes
if (selectedIndex != -1)
{
var index = 5 + selectedIndex * 2;
- SetValue(index, _animationPicker.SelectedID);
+ SetValue(index, _animationPicker.Validator.SelectedID);
}
}
@@ -495,7 +495,7 @@ namespace FlaxEditor.Surface.Archetypes
{
if (isValid)
{
- _animationPicker.SelectedID = data1;
+ _animationPicker.Validator.SelectedID = data1;
_animationSpeed.Value = data0.W;
var path = string.Empty;
@@ -505,7 +505,7 @@ namespace FlaxEditor.Surface.Archetypes
}
else
{
- _animationPicker.SelectedID = Guid.Empty;
+ _animationPicker.Validator.SelectedID = Guid.Empty;
_animationSpeed.Value = 1.0f;
}
_animationPicker.Enabled = isValid;
diff --git a/Source/Editor/Surface/Archetypes/Bitwise.cs b/Source/Editor/Surface/Archetypes/Bitwise.cs
index 06f719adc..43dcb91b5 100644
--- a/Source/Editor/Surface/Archetypes/Bitwise.cs
+++ b/Source/Editor/Surface/Archetypes/Bitwise.cs
@@ -60,7 +60,7 @@ namespace FlaxEditor.Surface.Archetypes
Op1(1, "Bitwise NOT", "Negates the value using bitwise operation", new[] { "!", "~" }),
Op2(2, "Bitwise AND", "Performs a bitwise conjunction on two values", new[] { "&" }),
Op2(3, "Bitwise OR", "", new[] { "|" }),
- Op2(4, "Bitwise XOR", ""),
+ Op2(4, "Bitwise XOR", "", new[] { "^" }),
};
}
}
diff --git a/Source/Editor/Surface/Archetypes/Boolean.cs b/Source/Editor/Surface/Archetypes/Boolean.cs
index 153b2fead..ed97a9642 100644
--- a/Source/Editor/Surface/Archetypes/Boolean.cs
+++ b/Source/Editor/Surface/Archetypes/Boolean.cs
@@ -60,7 +60,7 @@ namespace FlaxEditor.Surface.Archetypes
Op1(1, "Boolean NOT", "Negates the boolean value", new[] { "!", "~" }),
Op2(2, "Boolean AND", "Performs a logical conjunction on two values", new[] { "&&" }),
Op2(3, "Boolean OR", "Returns true if either (or both) of its operands is true", new[] { "||" }),
- Op2(4, "Boolean XOR", ""),
+ Op2(4, "Boolean XOR", "", new [] { "^" } ),
Op2(5, "Boolean NOR", ""),
Op2(6, "Boolean NAND", ""),
};
diff --git a/Source/Editor/Surface/Archetypes/Comparisons.cs b/Source/Editor/Surface/Archetypes/Comparisons.cs
index a0efbd92f..b72111c46 100644
--- a/Source/Editor/Surface/Archetypes/Comparisons.cs
+++ b/Source/Editor/Surface/Archetypes/Comparisons.cs
@@ -14,7 +14,7 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Comparisons
{
- private static NodeArchetype Op(ushort id, string title, string desc)
+ private static NodeArchetype Op(ushort id, string title, string desc, string[] altTitles = null)
{
return new NodeArchetype
{
@@ -22,6 +22,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = title,
Description = desc,
Flags = NodeFlags.AllGraphs,
+ AlternativeTitles = altTitles,
ConnectionsHints = ConnectionsHint.Value,
Size = new Float2(100, 40),
IndependentBoxes = new[]
@@ -170,12 +171,12 @@ namespace FlaxEditor.Surface.Archetypes
///
public static NodeArchetype[] Nodes =
{
- Op(1, "==", "Determines whether two values are equal"),
- Op(2, "!=", "Determines whether two values are not equal"),
- Op(3, ">", "Determines whether the first value is greater than the other"),
- Op(4, "<", "Determines whether the first value is less than the other"),
- Op(5, "<=", "Determines whether the first value is less or equal to the other"),
- Op(6, ">=", "Determines whether the first value is greater or equal to the other"),
+ Op(1, "==", "Determines whether two values are equal", new[] { "equals" }),
+ Op(2, "!=", "Determines whether two values are not equal", new[] { "not equals" }),
+ Op(3, ">", "Determines whether the first value is greater than the other", new[] { "greater than", "larger than", "bigger than" }),
+ Op(4, "<", "Determines whether the first value is less than the other", new[] { "less than", "smaller than" }),
+ Op(5, "<=", "Determines whether the first value is less or equal to the other", new[] { "less equals than", "smaller equals than" }),
+ Op(6, ">=", "Determines whether the first value is greater or equal to the other", new[] { "greater equals than", "larger equals than", "bigger equals than" }),
new NodeArchetype
{
TypeID = 7,
diff --git a/Source/Editor/Surface/Archetypes/Constants.cs b/Source/Editor/Surface/Archetypes/Constants.cs
index cbe7822e3..e58d917d1 100644
--- a/Source/Editor/Surface/Archetypes/Constants.cs
+++ b/Source/Editor/Surface/Archetypes/Constants.cs
@@ -7,11 +7,12 @@ using Real = System.Single;
#endif
using System;
-using System.Reflection;
+using System.Linq;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
+using FlaxEditor.Surface.Undo;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -24,6 +25,109 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Constants
{
+ ///
+ /// A special type of node that adds the functionality to convert nodes to parameters.
+ ///
+ internal class ConvertToParameterNode : SurfaceNode
+ {
+ private readonly ScriptType _type;
+ private readonly Func
public object Tag;
+ ///
+ /// Custom score value to use when sorting node archetypes in Editor. If positive (eg. 1, 2) can be used to add more importance for a specific node type.
+ ///
+ public float SortScore;
+
///
/// Default node values. This array supports types: bool, int, float, Vector2, Vector3, Vector4, Color, Rectangle, Guid, string, Matrix and byte[].
///
@@ -215,14 +220,17 @@ namespace FlaxEditor.Surface
Size = Size,
Flags = Flags,
Title = Title,
- Description = Title,
+ SubTitle = SubTitle,
+ Description = Description,
AlternativeTitles = (string[])AlternativeTitles?.Clone(),
Tag = Tag,
+ SortScore = SortScore,
DefaultValues = (object[])DefaultValues?.Clone(),
DefaultType = DefaultType,
ConnectionsHints = ConnectionsHints,
IndependentBoxes = (int[])IndependentBoxes?.Clone(),
DependentBoxes = (int[])DependentBoxes?.Clone(),
+ DependentBoxFilter = DependentBoxFilter,
Elements = (NodeElementArchetype[])Elements?.Clone(),
TryParseText = TryParseText,
};
diff --git a/Source/Editor/Surface/ParticleEmitterSurface.cs b/Source/Editor/Surface/ParticleEmitterSurface.cs
index 76f96f06c..b7cf83c62 100644
--- a/Source/Editor/Surface/ParticleEmitterSurface.cs
+++ b/Source/Editor/Surface/ParticleEmitterSurface.cs
@@ -93,7 +93,7 @@ namespace FlaxEditor.Surface
}
///
- protected override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
+ protected internal override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
{
groupId = 6;
return Archetypes.Parameters.Nodes[1];
diff --git a/Source/Editor/Surface/SurfaceComment.cs b/Source/Editor/Surface/SurfaceComment.cs
index ff38b1aa8..aad45190e 100644
--- a/Source/Editor/Surface/SurfaceComment.cs
+++ b/Source/Editor/Surface/SurfaceComment.cs
@@ -2,6 +2,7 @@
using System;
using FlaxEditor.GUI;
+using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -52,6 +53,12 @@ namespace FlaxEditor.Surface
set => SetValue(2, value, false);
}
+ private int OrderValue
+ {
+ get => (int)Values[3];
+ set => SetValue(3, value, false);
+ }
+
///
public SurfaceComment(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
@@ -67,6 +74,19 @@ namespace FlaxEditor.Surface
Title = TitleValue;
Color = ColorValue;
Size = SizeValue;
+
+ // Order
+ // Backwards compatibility - When opening with an older version send the old comments to the back
+ if (Values.Length < 4)
+ {
+ if (IndexInParent > 0)
+ IndexInParent = 0;
+ OrderValue = IndexInParent;
+ }
+ else if(OrderValue != -1)
+ {
+ IndexInParent = OrderValue;
+ }
}
///
@@ -76,6 +96,10 @@ namespace FlaxEditor.Surface
// Randomize color
Color = ColorValue = Color.FromHSV(new Random().NextFloat(0, 360), 0.7f, 0.25f, 0.8f);
+
+ if(OrderValue == -1)
+ OrderValue = Context.CommentCount - 1;
+ IndexInParent = OrderValue;
}
///
@@ -314,5 +338,38 @@ namespace FlaxEditor.Surface
Color = ColorValue = color;
Surface.MarkAsEdited(false);
}
+
+ ///
+ public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location)
+ {
+ base.OnShowSecondaryContextMenu(menu, location);
+
+ menu.AddSeparator();
+ ContextMenuChildMenu cmOrder = menu.AddChildMenu("Order");
+ {
+ cmOrder.ContextMenu.AddButton("Bring Forward", () =>
+ {
+ if(IndexInParent < Context.CommentCount-1)
+ IndexInParent++;
+ OrderValue = IndexInParent;
+ });
+ cmOrder.ContextMenu.AddButton("Bring to Front", () =>
+ {
+ IndexInParent = Context.CommentCount-1;
+ OrderValue = IndexInParent;
+ });
+ cmOrder.ContextMenu.AddButton("Send Backward", () =>
+ {
+ if(IndexInParent > 0)
+ IndexInParent--;
+ OrderValue = IndexInParent;
+ });
+ cmOrder.ContextMenu.AddButton("Send to Back", () =>
+ {
+ IndexInParent = 0;
+ OrderValue = IndexInParent;
+ });
+ }
+ }
}
}
diff --git a/Source/Editor/Surface/SurfaceParameter.cs b/Source/Editor/Surface/SurfaceParameter.cs
index 6eafdc2aa..dae82e111 100644
--- a/Source/Editor/Surface/SurfaceParameter.cs
+++ b/Source/Editor/Surface/SurfaceParameter.cs
@@ -3,7 +3,6 @@
using System;
using FlaxEditor.Scripting;
using FlaxEngine;
-using FlaxEngine.Utilities;
namespace FlaxEditor.Surface
{
@@ -27,7 +26,7 @@ namespace FlaxEditor.Surface
///
/// Parameter unique ID
///
- public Guid ID;
+ public Guid ID = Guid.Empty;
///
/// Parameter name
@@ -49,23 +48,5 @@ namespace FlaxEditor.Surface
///
[NoSerialize, HideInEditor]
public readonly SurfaceMeta Meta = new SurfaceMeta();
-
- ///
- /// Creates the new parameter of the given type.
- ///
- /// The type.
- /// The name.
- /// The created parameter.
- public static SurfaceParameter Create(ScriptType type, string name)
- {
- return new SurfaceParameter
- {
- ID = Guid.NewGuid(),
- IsPublic = true,
- Name = name,
- Type = type,
- Value = TypeUtils.GetDefaultValue(type),
- };
- }
}
}
diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs
index de1de1e81..2a0c177d7 100644
--- a/Source/Editor/Surface/SurfaceUtils.cs
+++ b/Source/Editor/Surface/SurfaceUtils.cs
@@ -541,13 +541,15 @@ namespace FlaxEditor.Surface
Action save, Action showWholeGraph, Action toggleGridSnap, InputActionsContainer actionsContainer,
out ToolStripButton saveButton, out ToolStripButton undoButton, out ToolStripButton redoButton, out ToolStripButton gridSnapButton)
{
+ var inputOptions = editor.Options.Options.Input;
+
// Toolstrip
saveButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Save64, save).LinkTooltip("Save");
toolStrip.AddSeparator();
- undoButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- redoButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ undoButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ redoButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
toolStrip.AddSeparator();
- toolStrip.AddButton(editor.Icons.Search64, editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)");
+ toolStrip.AddButton(editor.Icons.Search64, editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
toolStrip.AddButton(editor.Icons.CenterView64, showWholeGraph).LinkTooltip("Show whole graph");
gridSnapButton = (ToolStripButton)toolStrip.AddButton(editor.Icons.Stop64, toggleGridSnap).LinkTooltip("Toggle grid snapping for nodes.");
gridSnapButton.BackgroundColor = Style.Current.Background; // Default color for grid snap button.
diff --git a/Source/Editor/Surface/VisjectSurface.DragDrop.cs b/Source/Editor/Surface/VisjectSurface.DragDrop.cs
index 1728c282f..2ff82c269 100644
--- a/Source/Editor/Surface/VisjectSurface.DragDrop.cs
+++ b/Source/Editor/Surface/VisjectSurface.DragDrop.cs
@@ -151,7 +151,7 @@ namespace FlaxEditor.Surface
///
/// The group ID.
/// The node archetype.
- protected virtual NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
+ protected internal virtual NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
{
groupId = 6;
return Archetypes.Parameters.Nodes[0];
diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs
index 9d9199b3b..3e0992eed 100644
--- a/Source/Editor/Surface/VisjectSurface.cs
+++ b/Source/Editor/Surface/VisjectSurface.cs
@@ -726,7 +726,18 @@ namespace FlaxEditor.Surface
return null;
Rectangle surfaceArea = GetNodesBounds(selection).MakeExpanded(80.0f);
- return _context.CreateComment(ref surfaceArea, string.IsNullOrEmpty(text) ? "Comment" : text, new Color(1.0f, 1.0f, 1.0f, 0.2f));
+ // Order below other selected comments
+ bool hasCommentsSelected = false;
+ int lowestCommentOrder = int.MaxValue;
+ for (int i = 0; i < selection.Count; i++)
+ {
+ if (selection[i] is not SurfaceComment || selection[i].IndexInParent >= lowestCommentOrder)
+ continue;
+ hasCommentsSelected = true;
+ lowestCommentOrder = selection[i].IndexInParent;
+ }
+
+ return _context.CreateComment(ref surfaceArea, string.IsNullOrEmpty(text) ? "Comment" : text, new Color(1.0f, 1.0f, 1.0f, 0.2f), hasCommentsSelected ? lowestCommentOrder : -1);
}
private static Rectangle GetNodesBounds(List nodes)
diff --git a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs
index 7d75e006b..12f01d4f1 100644
--- a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs
+++ b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs
@@ -920,12 +920,6 @@ namespace FlaxEditor.Surface
// Link control
control.OnLoaded(action);
control.Parent = RootControl;
-
- if (control is SurfaceComment)
- {
- // Move comments to the background
- control.IndexInParent = 0;
- }
}
///
diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs
index 36e811c48..0886996b6 100644
--- a/Source/Editor/Surface/VisjectSurfaceContext.cs
+++ b/Source/Editor/Surface/VisjectSurfaceContext.cs
@@ -85,6 +85,27 @@ namespace FlaxEditor.Surface
}
}
+ ///
+ /// Gets the amount of surface comments
+ ///
+ ///
+ /// This is used as an alternative to , if only the amount of comments is important.
+ /// Is faster and doesn't allocate as much memory
+ ///
+ public int CommentCount
+ {
+ get
+ {
+ int count = 0;
+ for (int i = 0; i < RootControl.Children.Count; i++)
+ {
+ if (RootControl.Children[i] is SurfaceComment)
+ count++;
+ }
+ return count;
+ }
+ }
+
///
/// Gets a value indicating whether this context is modified (needs saving and flushing with surface data context source).
///
@@ -285,14 +306,16 @@ namespace FlaxEditor.Surface
/// The surface area to create comment.
/// The comment title.
/// The comment color.
+ /// The comment order or -1 to use default.
/// The comment object
- public virtual SurfaceComment SpawnComment(ref Rectangle surfaceArea, string title, Color color)
+ public virtual SurfaceComment SpawnComment(ref Rectangle surfaceArea, string title, Color color, int customOrder = -1)
{
var values = new object[]
{
title, // Title
color, // Color
surfaceArea.Size, // Size
+ customOrder, // Order
};
return (SurfaceComment)SpawnNode(7, 11, surfaceArea.Location, values);
}
@@ -303,11 +326,12 @@ namespace FlaxEditor.Surface
/// The surface area to create comment.
/// The comment title.
/// The comment color.
+ /// The comment order or -1 to use default.
/// The comment object
- public SurfaceComment CreateComment(ref Rectangle surfaceArea, string title, Color color)
+ public SurfaceComment CreateComment(ref Rectangle surfaceArea, string title, Color color, int customOrder = -1)
{
// Create comment
- var comment = SpawnComment(ref surfaceArea, title, color);
+ var comment = SpawnComment(ref surfaceArea, title, color, customOrder);
if (comment == null)
{
Editor.LogWarning("Failed to create comment.");
diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs
index eff8ec8a3..bcd058985 100644
--- a/Source/Editor/Surface/VisjectSurfaceWindow.cs
+++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs
@@ -17,6 +17,7 @@ using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface
{
@@ -258,6 +259,11 @@ namespace FlaxEditor.Surface
///
public IVisjectSurfaceWindow Window;
+ ///
+ /// The identifier of the parameter. Empty to auto generate it.
+ ///
+ public Guid Id = Guid.NewGuid();
+
///
/// True if adding, false if removing parameter.
///
@@ -278,6 +284,11 @@ namespace FlaxEditor.Surface
///
public ScriptType Type;
+ ///
+ /// The value to initialize the parameter with. Can be null to use default one for the parameter type.
+ ///
+ public object InitValue;
+
///
public string ActionString => IsAdd ? "Add parameter" : "Remove parameter";
@@ -304,7 +315,14 @@ namespace FlaxEditor.Surface
var type = Type;
if (IsAdd && type.Type == typeof(NormalMap))
type = new ScriptType(typeof(Texture));
- var param = SurfaceParameter.Create(type, Name);
+ var param = new SurfaceParameter
+ {
+ ID = Id,
+ IsPublic = true,
+ Name = Name,
+ Type = type,
+ Value = InitValue ?? TypeUtils.GetDefaultValue(type),
+ };
if (IsAdd && Type.Type == typeof(NormalMap))
param.Value = FlaxEngine.Content.LoadAsyncInternal("Engine/Textures/NormalTexture");
Window.VisjectSurface.Parameters.Insert(Index, param);
@@ -726,6 +744,8 @@ namespace FlaxEditor.Surface
protected VisjectSurfaceWindow(Editor editor, AssetItem item, bool useTabs = false)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new FlaxEditor.Undo();
_undo.UndoDone += OnUndoRedo;
@@ -1054,7 +1074,6 @@ namespace FlaxEditor.Surface
public virtual void OnParamRemoveUndo()
{
_refreshPropertiesOnLoad = true;
- //_propertiesEditor.BuildLayoutOnUpdate();
_propertiesEditor.BuildLayout();
}
diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs
index 902582311..2f8d4cda8 100644
--- a/Source/Editor/Surface/VisualScriptSurface.cs
+++ b/Source/Editor/Surface/VisualScriptSurface.cs
@@ -144,7 +144,7 @@ namespace FlaxEditor.Surface
}
///
- protected override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
+ protected internal override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
{
groupId = 6;
return Archetypes.Parameters.Nodes[2];
diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs
index 226c7b49c..f12fca9db 100644
--- a/Source/Editor/Tools/ClothPainting.cs
+++ b/Source/Editor/Tools/ClothPainting.cs
@@ -225,6 +225,7 @@ namespace FlaxEngine.Tools
var cloth = _cloth;
if (cloth == null)
return;
+ var hasPaintInput = Owner.IsLeftMouseButtonDown && !Owner.IsAltKeyDown;
// Perform detailed tracing to find cursor location for the brush
var ray = Owner.MouseRay;
@@ -240,7 +241,7 @@ namespace FlaxEngine.Tools
// Cursor hit other object or nothing
PaintEnd();
- if (Owner.IsLeftMouseButtonDown)
+ if (hasPaintInput)
{
// Select something else
var view = new Ray(Owner.ViewPosition, Owner.ViewDirection);
@@ -253,7 +254,7 @@ namespace FlaxEngine.Tools
}
// Handle painting
- if (Owner.IsLeftMouseButtonDown)
+ if (hasPaintInput)
PaintStart();
else
PaintEnd();
diff --git a/Source/Editor/Tools/Terrain/EditTab.cs b/Source/Editor/Tools/Terrain/EditTab.cs
index 008be64f4..d52c1ae1d 100644
--- a/Source/Editor/Tools/Terrain/EditTab.cs
+++ b/Source/Editor/Tools/Terrain/EditTab.cs
@@ -290,7 +290,7 @@ namespace FlaxEditor.Tools.Terrain
var patchCoord = Gizmo.SelectedPatchCoord;
var chunkCoord = Gizmo.SelectedChunkCoord;
- var action = new EditChunkMaterialAction(CarveTab.SelectedTerrain, ref patchCoord, ref chunkCoord, _chunkOverrideMaterial.SelectedAsset as MaterialBase);
+ var action = new EditChunkMaterialAction(CarveTab.SelectedTerrain, ref patchCoord, ref chunkCoord, _chunkOverrideMaterial.Validator.SelectedAsset as MaterialBase);
action.Do();
CarveTab.Editor.Undo.AddAction(action);
}
@@ -336,12 +336,12 @@ namespace FlaxEditor.Tools.Terrain
_isUpdatingUI = true;
if (terrain.HasPatch(ref patchCoord))
{
- _chunkOverrideMaterial.SelectedAsset = terrain.GetChunkOverrideMaterial(ref patchCoord, ref chunkCoord);
+ _chunkOverrideMaterial.Validator.SelectedAsset = terrain.GetChunkOverrideMaterial(ref patchCoord, ref chunkCoord);
_chunkOverrideMaterial.Enabled = true;
}
else
{
- _chunkOverrideMaterial.SelectedAsset = null;
+ _chunkOverrideMaterial.Validator.SelectedAsset = null;
_chunkOverrideMaterial.Enabled = false;
}
_isUpdatingUI = false;
diff --git a/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs b/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs
index a5710494c..dac2900f4 100644
--- a/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs
+++ b/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs
@@ -75,6 +75,11 @@ namespace FlaxEditor.Actions
_enabled = true;
}
+ public int GetOrderInParent()
+ {
+ return _orderInParent;
+ }
+
///
/// Creates a new added script undo action.
///
@@ -184,6 +189,7 @@ namespace FlaxEditor.Actions
script.Parent = parentActor;
if (_orderInParent != -1)
script.OrderInParent = _orderInParent;
+ _orderInParent = script.OrderInParent; // Ensure order is correct for script that want to use it later
if (_prefabObjectId != Guid.Empty)
SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(script), ref _prefabId, ref _prefabObjectId);
Editor.Instance.Scene.MarkSceneEdited(parentActor.Scene);
diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp
index 8e3fc0e4a..a2418ceb1 100644
--- a/Source/Editor/Utilities/EditorUtilities.cpp
+++ b/Source/Editor/Utilities/EditorUtilities.cpp
@@ -394,8 +394,12 @@ bool EditorUtilities::UpdateExeIcon(const String& path, const TextureData& icon)
// - icon/cursor/etc data
std::fstream stream;
+#if PLATFORM_WINDOWS
+ stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary);
+#else
StringAsANSI<> pathAnsi(path.Get());
stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary);
+#endif
if (!stream.is_open())
{
LOG(Warning, "Cannot open file");
diff --git a/Source/Editor/Utilities/ScreenUtilities.cpp b/Source/Editor/Utilities/ScreenUtilities.cpp
index 44f52350e..730a69aa3 100644
--- a/Source/Editor/Utilities/ScreenUtilities.cpp
+++ b/Source/Editor/Utilities/ScreenUtilities.cpp
@@ -73,6 +73,7 @@ Color32 ScreenUtilities::GetColorAt(const Float2& pos)
outputColor.R = color.red / 256;
outputColor.G = color.green / 256;
outputColor.B = color.blue / 256;
+ outputColor.A = 255;
return outputColor;
}
diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs
index e578933cf..bf2e840ea 100644
--- a/Source/Editor/Viewport/Cameras/FPSCamera.cs
+++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs
@@ -259,7 +259,10 @@ namespace FlaxEditor.Viewport.Cameras
// Pan
if (input.IsPanning)
{
- var panningSpeed = 0.8f;
+ var panningSpeed = (Viewport.RelativePanning)
+ ? Mathf.Abs((position - TargetPoint).Length) * 0.005f
+ : Viewport.PanningSpeed;
+
if (Viewport.InvertPanning)
{
position += up * (mouseDelta.Y * panningSpeed);
diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs
index 9a4fd34a2..a24dd2f38 100644
--- a/Source/Editor/Viewport/EditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/EditorGizmoViewport.cs
@@ -41,6 +41,8 @@ namespace FlaxEditor.Viewport
Gizmos[i].Update(deltaTime);
}
}
+ ///
+ public EditorViewport Viewport => this;
///
public GizmosCollection Gizmos { get; }
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index e20069120..c49392d01 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -128,12 +128,26 @@ namespace FlaxEditor.Viewport
public const int FpsCameraFilteringFrames = 3;
///
- /// The speed widget button.
+ /// The camera settings widget.
///
- protected ViewportWidgetButton _speedWidget;
+ protected ViewportWidgetsContainer _cameraWidget;
+
+ ///
+ /// The camera settings widget button.
+ ///
+ protected ViewportWidgetButton _cameraButton;
+
+ ///
+ /// The orthographic mode widget button.
+ ///
+ protected ViewportWidgetButton _orthographicModeButton;
+
+ private readonly Editor _editor;
private float _mouseSensitivity;
private float _movementSpeed;
+ private float _minMovementSpeed;
+ private float _maxMovementSpeed;
private float _mouseAccelerationScale;
private bool _useMouseFiltering;
private bool _useMouseAcceleration;
@@ -174,11 +188,17 @@ namespace FlaxEditor.Viewport
private float _fieldOfView;
private float _nearPlane;
private float _farPlane;
- private float _orthoSize = 1.0f;
- private bool _isOrtho = false;
- private float _wheelMovementChangeDeltaSum = 0;
+ private float _orthoSize;
+ private bool _isOrtho;
+ private bool _useCameraEasing;
+ private float _cameraEasingDegree;
+ private float _panningSpeed;
+ private bool _relativePanning;
private bool _invertPanning;
+ private int _speedStep;
+ private int _maxSpeedSteps;
+
///
/// Speed of the mouse.
///
@@ -189,6 +209,25 @@ namespace FlaxEditor.Viewport
///
public float MouseWheelZoomSpeedFactor = 1;
+ ///
+ /// Format of the text for the camera move speed.
+ ///
+ private string MovementSpeedTextFormat
+ {
+ get
+ {
+ if (Mathf.Abs(_movementSpeed - _maxMovementSpeed) < Mathf.Epsilon || Mathf.Abs(_movementSpeed - _minMovementSpeed) < Mathf.Epsilon)
+ return "{0:0.##}";
+
+ if (_movementSpeed < 10.0f)
+ return "{0:0.00}";
+ else if (_movementSpeed < 100.0f)
+ return "{0:0.0}";
+ else
+ return "{0:#}";
+ }
+ }
+
///
/// Gets or sets the camera movement speed.
///
@@ -197,19 +236,40 @@ namespace FlaxEditor.Viewport
get => _movementSpeed;
set
{
- for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
- {
- if (Math.Abs(value - EditorViewportCameraSpeedValues[i]) < 0.001f)
- {
- _movementSpeed = EditorViewportCameraSpeedValues[i];
- if (_speedWidget != null)
- _speedWidget.Text = _movementSpeed.ToString();
- break;
- }
- }
+ _movementSpeed = value;
+
+ if (_cameraButton != null)
+ _cameraButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed);
}
}
+ ///
+ /// Gets or sets the minimum camera movement speed.
+ ///
+ public float MinMovementSpeed
+ {
+ get => _minMovementSpeed;
+ set => _minMovementSpeed = value;
+ }
+
+ ///
+ /// Gets or sets the maximum camera movement speed.
+ ///
+ public float MaxMovementSpeed
+ {
+ get => _maxMovementSpeed;
+ set => _maxMovementSpeed = value;
+ }
+
+ ///
+ /// Gets or sets the camera easing mode.
+ ///
+ public bool UseCameraEasing
+ {
+ get => _useCameraEasing;
+ set => _useCameraEasing = value;
+ }
+
///
/// Gets the mouse movement position delta (user press and move).
///
@@ -396,6 +456,15 @@ namespace FlaxEditor.Viewport
set => _isOrtho = value;
}
+ ///
+ /// Gets or sets if the panning speed should be relative to the camera target.
+ ///
+ public bool RelativePanning
+ {
+ get => _relativePanning;
+ set => _relativePanning = value;
+ }
+
///
/// Gets or sets if the panning direction is inverted.
///
@@ -405,6 +474,15 @@ namespace FlaxEditor.Viewport
set => _invertPanning = value;
}
+ ///
+ /// Gets or sets the camera panning speed.
+ ///
+ public float PanningSpeed
+ {
+ get => _panningSpeed;
+ set => _panningSpeed = value;
+ }
+
///
/// The input actions collection to processed during user input.
///
@@ -419,6 +497,8 @@ namespace FlaxEditor.Viewport
public EditorViewport(SceneRenderTask task, ViewportCamera camera, bool useWidgets)
: base(task)
{
+ _editor = Editor.Instance;
+
_mouseAccelerationScale = 0.1f;
_useMouseFiltering = false;
_useMouseAcceleration = false;
@@ -431,43 +511,299 @@ namespace FlaxEditor.Viewport
// Setup options
{
- var options = Editor.Instance.Options.Options;
- _movementSpeed = options.Viewport.DefaultMovementSpeed;
- _nearPlane = options.Viewport.DefaultNearPlane;
- _farPlane = options.Viewport.DefaultFarPlane;
- _fieldOfView = options.Viewport.DefaultFieldOfView;
- _invertPanning = options.Viewport.DefaultInvertPanning;
-
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
- OnEditorOptionsChanged(options);
+ SetupViewportOptions();
}
+ // Initialize camera values from cache
+ if (_editor.ProjectCache.TryGetCustomData("CameraMovementSpeedValue", out var cachedState))
+ MovementSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraMinMovementSpeedValue", out cachedState))
+ _minMovementSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraMaxMovementSpeedValue", out cachedState))
+ _maxMovementSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("UseCameraEasingState", out cachedState))
+ _useCameraEasing = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraPanningSpeedValue", out cachedState))
+ _panningSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraInvertPanningState", out cachedState))
+ _invertPanning = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraRelativePanningState", out cachedState))
+ _relativePanning = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraOrthographicState", out cachedState))
+ _isOrtho = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraOrthographicSizeValue", out cachedState))
+ _orthoSize = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraFieldOfViewValue", out cachedState))
+ _fieldOfView = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraNearPlaneValue", out cachedState))
+ _nearPlane = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraFarPlaneValue", out cachedState))
+ _farPlane = float.Parse(cachedState);
+
+ OnCameraMovementProgressChanged();
+
if (useWidgets)
{
- var largestText = "Invert Panning";
+ #region Camera settings widget
+
+ var largestText = "Relative 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();
- var camSpeedButton = new ViewportWidgetButton(_movementSpeed.ToString(), Editor.Instance.Icons.CamSpeed32, camSpeedCM)
+ var cameraSpeedTextWidth = Style.Current.FontMedium.MeasureText("0.00").X;
+
+ // Camera Settings Widget
+ _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
+
+ // Camera Settings Menu
+ var cameraCM = new ContextMenu();
+ _cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), Editor.Instance.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth)
{
Tag = this,
- TooltipText = "Camera speed scale"
+ TooltipText = "Camera Settings",
+ Parent = _cameraWidget
};
- _speedWidget = camSpeedButton;
- for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
- {
- var v = EditorViewportCameraSpeedValues[i];
- var button = camSpeedCM.AddButton(v.ToString());
- button.Tag = v;
- }
- camSpeedCM.ButtonClicked += button => MovementSpeed = (float)button.Tag;
- camSpeedCM.VisibleChanged += WidgetCamSpeedShowHide;
- camSpeedButton.Parent = camSpeed;
- camSpeed.Parent = this;
+ _cameraWidget.Parent = this;
+
+ // Orthographic/Perspective Mode Widget
+ _orthographicModeButton = new ViewportWidgetButton(string.Empty, Editor.Instance.Icons.CamSpeed32, null, true)
+ {
+ Checked = !_isOrtho,
+ TooltipText = "Toggle Orthographic/Perspective Mode",
+ Parent = _cameraWidget
+ };
+ _orthographicModeButton.Toggled += OnOrthographicModeToggled;
+
+ // Camera Speed
+ var camSpeedButton = cameraCM.AddButton("Camera Speed");
+ camSpeedButton.CloseMenuOnClick = false;
+ var camSpeedValue = new FloatValueBox(_movementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, _maxMovementSpeed, 0.5f)
+ {
+ Parent = camSpeedButton
+ };
+
+ camSpeedValue.ValueChanged += () => OnMovementSpeedChanged(camSpeedValue);
+ cameraCM.VisibleChanged += control => camSpeedValue.Value = _movementSpeed;
+
+ // Minimum & Maximum Camera Speed
+ var minCamSpeedButton = cameraCM.AddButton("Min Cam Speed");
+ minCamSpeedButton.CloseMenuOnClick = false;
+ var minCamSpeedValue = new FloatValueBox(_minMovementSpeed, xLocationForExtras, 2, 70.0f, 0.05f, _maxMovementSpeed, 0.5f)
+ {
+ Parent = minCamSpeedButton
+ };
+ var maxCamSpeedButton = cameraCM.AddButton("Max Cam Speed");
+ maxCamSpeedButton.CloseMenuOnClick = false;
+ var maxCamSpeedValue = new FloatValueBox(_maxMovementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, 1000.0f, 0.5f)
+ {
+ Parent = maxCamSpeedButton
+ };
+
+ minCamSpeedValue.ValueChanged += () =>
+ {
+ OnMinMovementSpeedChanged(minCamSpeedValue);
+
+ maxCamSpeedValue.MinValue = minCamSpeedValue.Value;
+
+ if (Math.Abs(camSpeedValue.MinValue - minCamSpeedValue.Value) > Mathf.Epsilon)
+ camSpeedValue.MinValue = minCamSpeedValue.Value;
+ };
+ cameraCM.VisibleChanged += control => minCamSpeedValue.Value = _minMovementSpeed;
+ maxCamSpeedValue.ValueChanged += () =>
+ {
+ OnMaxMovementSpeedChanged(maxCamSpeedValue);
+
+ minCamSpeedValue.MaxValue = maxCamSpeedValue.Value;
+
+ if (Math.Abs(camSpeedValue.MaxValue - maxCamSpeedValue.Value) > Mathf.Epsilon)
+ camSpeedValue.MaxValue = maxCamSpeedValue.Value;
+ };
+ cameraCM.VisibleChanged += control => maxCamSpeedValue.Value = _maxMovementSpeed;
+
+ // Camera Easing
+ {
+ var useCameraEasing = cameraCM.AddButton("Camera Easing");
+ useCameraEasing.CloseMenuOnClick = false;
+ var useCameraEasingValue = new CheckBox(xLocationForExtras, 2, _useCameraEasing)
+ {
+ Parent = useCameraEasing
+ };
+
+ useCameraEasingValue.StateChanged += OnCameraEasingToggled;
+ cameraCM.VisibleChanged += control => useCameraEasingValue.Checked = _useCameraEasing;
+ }
+
+ // Panning Speed
+ {
+ var panningSpeed = cameraCM.AddButton("Panning Speed");
+ panningSpeed.CloseMenuOnClick = false;
+ var panningSpeedValue = new FloatValueBox(_panningSpeed, xLocationForExtras, 2, 70.0f, 0.01f, 128.0f, 0.1f)
+ {
+ Parent = panningSpeed
+ };
+
+ panningSpeedValue.ValueChanged += () => OnPanningSpeedChanged(panningSpeedValue);
+ cameraCM.VisibleChanged += control =>
+ {
+ panningSpeed.Visible = !_relativePanning;
+ panningSpeedValue.Value = _panningSpeed;
+ };
+ }
+
+ // Relative Panning
+ {
+ var relativePanning = cameraCM.AddButton("Relative Panning");
+ relativePanning.CloseMenuOnClick = false;
+ var relativePanningValue = new CheckBox(xLocationForExtras, 2, _relativePanning)
+ {
+ Parent = relativePanning
+ };
+
+ relativePanningValue.StateChanged += checkBox =>
+ {
+ if (checkBox.Checked != _relativePanning)
+ {
+ OnRelativePanningToggled(checkBox);
+ cameraCM.Hide();
+ }
+ };
+ cameraCM.VisibleChanged += control => relativePanningValue.Checked = _relativePanning;
+ }
+
+ // Invert Panning
+ {
+ var invertPanning = cameraCM.AddButton("Invert Panning");
+ invertPanning.CloseMenuOnClick = false;
+ var invertPanningValue = new CheckBox(xLocationForExtras, 2, _invertPanning)
+ {
+ Parent = invertPanning
+ };
+
+ invertPanningValue.StateChanged += OnInvertPanningToggled;
+ cameraCM.VisibleChanged += control => invertPanningValue.Checked = _invertPanning;
+ }
+
+ cameraCM.AddSeparator();
+
+ // Camera Viewpoints
+ {
+ var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu;
+ for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++)
+ {
+ var co = EditorViewportCameraViewpointValues[i];
+ var button = cameraView.AddButton(co.Name);
+ button.Tag = co.Orientation;
+ }
+
+ cameraView.ButtonClicked += OnViewpointChanged;
+ }
+
+ // Orthographic Mode
+ {
+ var ortho = cameraCM.AddButton("Orthographic");
+ ortho.CloseMenuOnClick = false;
+ var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho)
+ {
+ Parent = ortho
+ };
+
+ orthoValue.StateChanged += checkBox =>
+ {
+ if (checkBox.Checked != _isOrtho)
+ {
+ OnOrthographicModeToggled(checkBox);
+ cameraCM.Hide();
+ }
+ };
+ cameraCM.VisibleChanged += control => orthoValue.Checked = _isOrtho;
+ }
+
+ // Field of View
+ {
+ var fov = cameraCM.AddButton("Field Of View");
+ fov.CloseMenuOnClick = false;
+ var fovValue = new FloatValueBox(_fieldOfView, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f)
+ {
+ Parent = fov
+ };
+
+ fovValue.ValueChanged += () => OnFieldOfViewChanged(fovValue);
+ cameraCM.VisibleChanged += control =>
+ {
+ fov.Visible = !_isOrtho;
+ fovValue.Value = _fieldOfView;
+ };
+ }
+
+ // Orthographic Scale
+ {
+ var orthoSize = cameraCM.AddButton("Ortho Scale");
+ orthoSize.CloseMenuOnClick = false;
+ var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f)
+ {
+ Parent = orthoSize
+ };
+
+ orthoSizeValue.ValueChanged += () => OnOrthographicSizeChanged(orthoSizeValue);
+ cameraCM.VisibleChanged += control =>
+ {
+ orthoSize.Visible = _isOrtho;
+ orthoSizeValue.Value = _orthoSize;
+ };
+ }
+
+ // Near Plane
+ {
+ var nearPlane = cameraCM.AddButton("Near Plane");
+ nearPlane.CloseMenuOnClick = false;
+ var nearPlaneValue = new FloatValueBox(_nearPlane, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f)
+ {
+ Parent = nearPlane
+ };
+
+ nearPlaneValue.ValueChanged += () => OnNearPlaneChanged(nearPlaneValue);
+ cameraCM.VisibleChanged += control => nearPlaneValue.Value = _nearPlane;
+ }
+
+ // Far Plane
+ {
+ var farPlane = cameraCM.AddButton("Far Plane");
+ farPlane.CloseMenuOnClick = false;
+ var farPlaneValue = new FloatValueBox(_farPlane, xLocationForExtras, 2, 70.0f, 10.0f)
+ {
+ Parent = farPlane
+ };
+
+ farPlaneValue.ValueChanged += () => OnFarPlaneChanged(farPlaneValue);
+ cameraCM.VisibleChanged += control => farPlaneValue.Value = _farPlane;
+ }
+
+ cameraCM.AddSeparator();
+
+ // Reset Button
+ {
+ var reset = cameraCM.AddButton("Reset to default");
+ reset.ButtonClicked += button =>
+ {
+ SetupViewportOptions();
+
+ // if the context menu is opened without triggering the value changes beforehand,
+ // the movement speed will not be correctly reset to its default value in certain cases
+ // therefore, a UI update needs to be triggered here
+ minCamSpeedValue.Value = _minMovementSpeed;
+ camSpeedValue.Value = _movementSpeed;
+ maxCamSpeedValue.Value = _maxMovementSpeed;
+ };
+ }
+
+ #endregion Camera settings widget
+
+ #region View mode widget
+
+ largestText = "Brightness";
+ textSize = Style.Current.FontMedium.MeasureText(largestText);
+ xLocationForExtras = textSize.X + 5;
- // View mode widget
var viewMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
ViewWidgetButtonMenu = new ContextMenu();
var viewModeButton = new ViewportWidgetButton("View", SpriteHandle.Invalid, ViewWidgetButtonMenu)
@@ -484,8 +820,8 @@ namespace FlaxEditor.Viewport
// Show FPS
{
InitFpsCounter();
- _showFpsButon = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter);
- _showFpsButon.CloseMenuOnClick = false;
+ _showFpsButton = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter);
+ _showFpsButton.CloseMenuOnClick = false;
}
}
@@ -593,12 +929,14 @@ namespace FlaxEditor.Viewport
{
ref var vv = ref v.Options[j];
var button = childMenu.AddButton(vv.Name);
+ button.CloseMenuOnClick = false;
button.Tag = vv.Mode;
}
}
else
{
var button = debugView.AddButton(v.Name);
+ button.CloseMenuOnClick = false;
button.Tag = v.Mode;
}
}
@@ -608,104 +946,6 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu.AddSeparator();
- // Orthographic
- {
- var ortho = ViewWidgetButtonMenu.AddButton("Orthographic");
- ortho.CloseMenuOnClick = false;
- var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho)
- {
- Parent = ortho
- };
- orthoValue.StateChanged += checkBox =>
- {
- if (checkBox.Checked != _isOrtho)
- {
- _isOrtho = checkBox.Checked;
- ViewWidgetButtonMenu.Hide();
- if (_isOrtho)
- {
- var orient = ViewOrientation;
- OrientViewport(ref orient);
- }
- }
- };
- ViewWidgetButtonMenu.VisibleChanged += control => orthoValue.Checked = _isOrtho;
- }
-
- // Camera Viewpoints
- {
- var cameraView = ViewWidgetButtonMenu.AddChildMenu("Viewpoints").ContextMenu;
- for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++)
- {
- var co = EditorViewportCameraViewpointValues[i];
- var button = cameraView.AddButton(co.Name);
- button.Tag = co.Orientation;
- }
- cameraView.ButtonClicked += button =>
- {
- var orient = Quaternion.Euler((Float3)button.Tag);
- OrientViewport(ref orient);
- };
- }
-
- // Field of View
- {
- var fov = ViewWidgetButtonMenu.AddButton("Field Of View");
- fov.CloseMenuOnClick = false;
- var fovValue = new FloatValueBox(1, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f)
- {
- Parent = fov
- };
-
- fovValue.ValueChanged += () => _fieldOfView = fovValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control =>
- {
- fov.Visible = !_isOrtho;
- fovValue.Value = _fieldOfView;
- };
- }
-
- // Ortho Scale
- {
- var orthoSize = ViewWidgetButtonMenu.AddButton("Ortho Scale");
- orthoSize.CloseMenuOnClick = false;
- var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f)
- {
- Parent = orthoSize
- };
-
- orthoSizeValue.ValueChanged += () => _orthoSize = orthoSizeValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control =>
- {
- orthoSize.Visible = _isOrtho;
- orthoSizeValue.Value = _orthoSize;
- };
- }
-
- // Near Plane
- {
- var nearPlane = ViewWidgetButtonMenu.AddButton("Near Plane");
- nearPlane.CloseMenuOnClick = false;
- var nearPlaneValue = new FloatValueBox(2.0f, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f)
- {
- Parent = nearPlane
- };
- nearPlaneValue.ValueChanged += () => _nearPlane = nearPlaneValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control => nearPlaneValue.Value = _nearPlane;
- }
-
- // Far Plane
- {
- var farPlane = ViewWidgetButtonMenu.AddButton("Far Plane");
- farPlane.CloseMenuOnClick = false;
- var farPlaneValue = new FloatValueBox(1000, xLocationForExtras, 2, 70.0f, 10.0f)
- {
- Parent = farPlane
- };
- farPlaneValue.ValueChanged += () => _farPlane = farPlaneValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control => farPlaneValue.Value = _farPlane;
- }
-
// Brightness
{
var brightness = ViewWidgetButtonMenu.AddButton("Brightness");
@@ -730,24 +970,7 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale;
}
- // Invert Panning
- {
- var invert = ViewWidgetButtonMenu.AddButton("Invert Panning");
- invert.CloseMenuOnClick = false;
- var invertValue = new CheckBox(xLocationForExtras, 2, _invertPanning)
- {
- Parent = invert
- };
-
- invertValue.StateChanged += checkBox =>
- {
- if (checkBox.Checked != _invertPanning)
- {
- _invertPanning = checkBox.Checked;
- }
- };
- ViewWidgetButtonMenu.VisibleChanged += control => invertValue.Checked = _invertPanning;
- }
+ #endregion View mode widget
}
InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation)));
@@ -764,6 +987,135 @@ namespace FlaxEditor.Viewport
task.Begin += OnRenderBegin;
}
+ ///
+ /// Sets the viewport options to the default values.
+ ///
+ private void SetupViewportOptions()
+ {
+ var options = Editor.Instance.Options.Options;
+ _minMovementSpeed = options.Viewport.MinMovementSpeed;
+ MovementSpeed = options.Viewport.MovementSpeed;
+ _maxMovementSpeed = options.Viewport.MaxMovementSpeed;
+ _useCameraEasing = options.Viewport.UseCameraEasing;
+ _panningSpeed = options.Viewport.PanningSpeed;
+ _invertPanning = options.Viewport.InvertPanning;
+ _relativePanning = options.Viewport.UseRelativePanning;
+
+ _isOrtho = options.Viewport.UseOrthographicProjection;
+ _orthoSize = options.Viewport.OrthographicScale;
+ _fieldOfView = options.Viewport.FieldOfView;
+ _nearPlane = options.Viewport.NearPlane;
+ _farPlane = options.Viewport.FarPlane;
+
+ OnEditorOptionsChanged(options);
+ }
+
+ private void OnMovementSpeedChanged(FloatValueBox control)
+ {
+ var value = Mathf.Clamp(control.Value, _minMovementSpeed, _maxMovementSpeed);
+ MovementSpeed = value;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("CameraMovementSpeedValue", _movementSpeed.ToString());
+ }
+
+ private void OnMinMovementSpeedChanged(FloatValueBox control)
+ {
+ var value = Mathf.Clamp(control.Value, 0.05f, _maxMovementSpeed);
+ _minMovementSpeed = value;
+
+ if (_movementSpeed < value)
+ MovementSpeed = value;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("CameraMinMovementSpeedValue", _minMovementSpeed.ToString());
+ }
+
+ private void OnMaxMovementSpeedChanged(FloatValueBox control)
+ {
+ var value = Mathf.Clamp(control.Value, _minMovementSpeed, 1000.0f);
+ _maxMovementSpeed = value;
+
+ if (_movementSpeed > value)
+ MovementSpeed = value;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("CameraMaxMovementSpeedValue", _maxMovementSpeed.ToString());
+ }
+
+ private void OnCameraEasingToggled(Control control)
+ {
+ _useCameraEasing = !_useCameraEasing;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("UseCameraEasingState", _useCameraEasing.ToString());
+ }
+
+ private void OnPanningSpeedChanged(FloatValueBox control)
+ {
+ _panningSpeed = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraPanningSpeedValue", _panningSpeed.ToString());
+ }
+
+ private void OnRelativePanningToggled(Control control)
+ {
+ _relativePanning = !_relativePanning;
+ _editor.ProjectCache.SetCustomData("CameraRelativePanningState", _relativePanning.ToString());
+ }
+
+ private void OnInvertPanningToggled(Control control)
+ {
+ _invertPanning = !_invertPanning;
+ _editor.ProjectCache.SetCustomData("CameraInvertPanningState", _invertPanning.ToString());
+ }
+
+
+ private void OnViewpointChanged(ContextMenuButton button)
+ {
+ var orient = Quaternion.Euler((Float3)button.Tag);
+ OrientViewport(ref orient);
+ }
+
+ private void OnFieldOfViewChanged(FloatValueBox control)
+ {
+ _fieldOfView = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraFieldOfViewValue", _fieldOfView.ToString());
+ }
+
+ private void OnOrthographicModeToggled(Control control)
+ {
+ _isOrtho = !_isOrtho;
+
+ if (_orthographicModeButton != null)
+ _orthographicModeButton.Checked = !_isOrtho;
+
+ if (_isOrtho)
+ {
+ var orient = ViewOrientation;
+ OrientViewport(ref orient);
+ }
+
+ _editor.ProjectCache.SetCustomData("CameraOrthographicState", _isOrtho.ToString());
+ }
+
+ private void OnOrthographicSizeChanged(FloatValueBox control)
+ {
+ _orthoSize = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraOrthographicSizeValue", _orthoSize.ToString());
+ }
+
+ private void OnNearPlaneChanged(FloatValueBox control)
+ {
+ _nearPlane = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraNearPlaneValue", _nearPlane.ToString());
+ }
+
+ private void OnFarPlaneChanged(FloatValueBox control)
+ {
+ _farPlane = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraNearPlaneValue", _farPlane.ToString());
+ }
+
///
/// Gets a value indicating whether this viewport is using mouse currently (eg. user moving objects).
///
@@ -796,33 +1148,59 @@ namespace FlaxEditor.Viewport
}
}
+ private void OnCameraMovementProgressChanged()
+ {
+ // prevent NaN
+ if (Math.Abs(_minMovementSpeed - _maxMovementSpeed) < Mathf.Epsilon)
+ {
+ _speedStep = 0;
+ return;
+ }
+
+ if (Math.Abs(_movementSpeed - _maxMovementSpeed) < Mathf.Epsilon)
+ {
+ _speedStep = _maxSpeedSteps;
+ return;
+ }
+ else if (Math.Abs(_movementSpeed - _minMovementSpeed) < Mathf.Epsilon)
+ {
+ _speedStep = 0;
+ return;
+ }
+
+ // calculate current linear/eased progress
+ var progress = Mathf.Remap(_movementSpeed, _minMovementSpeed, _maxMovementSpeed, 0.0f, 1.0f);
+
+ if (_useCameraEasing)
+ progress = Mathf.Pow(progress, 1.0f / _cameraEasingDegree);
+
+ _speedStep = Mathf.RoundToInt(progress * _maxSpeedSteps);
+ }
+
///
/// 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;
+ _speedStep = Mathf.Clamp(_speedStep + step, 0, _maxSpeedSteps);
- if (step > 0)
- MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)];
- else if (step < 0)
- MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)];
+ // calculate new linear/eased progress
+ var progress = _useCameraEasing
+ ? Mathf.Pow((float)_speedStep / _maxSpeedSteps, _cameraEasingDegree)
+ : (float)_speedStep / _maxSpeedSteps;
+
+ var speed = Mathf.Lerp(_minMovementSpeed, _maxMovementSpeed, progress);
+ MovementSpeed = (float)Math.Round(speed, 3);
+ _editor.ProjectCache.SetCustomData("CameraMovementSpeedValue", _movementSpeed.ToString());
}
private void OnEditorOptionsChanged(EditorOptions options)
{
_mouseSensitivity = options.Viewport.MouseSensitivity;
+ _maxSpeedSteps = options.Viewport.TotalCameraSpeedSteps;
+ _cameraEasingDegree = options.Viewport.CameraEasingDegree;
+ OnCameraMovementProgressChanged();
}
private void OnRenderBegin(RenderTask task, GPUContext context)
@@ -861,7 +1239,7 @@ namespace FlaxEditor.Viewport
}
private FpsCounter _fpsCounter;
- private ContextMenuButton _showFpsButon;
+ private ContextMenuButton _showFpsButton;
///
/// Gets or sets a value indicating whether show or hide FPS counter.
@@ -873,7 +1251,7 @@ namespace FlaxEditor.Viewport
{
_fpsCounter.Visible = value;
_fpsCounter.Enabled = value;
- _showFpsButon.Icon = value ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
+ _showFpsButton.Icon = value ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
@@ -1014,8 +1392,6 @@ namespace FlaxEditor.Viewport
/// The parent window.
protected virtual void OnControlMouseBegin(Window win)
{
- _wheelMovementChangeDeltaSum = 0;
-
// Hide cursor and start tracking mouse movement
win.StartTrackingMouse(false);
win.Cursor = CursorType.Hidden;
@@ -1111,8 +1487,8 @@ namespace FlaxEditor.Viewport
_camera.Update(deltaTime);
useMovementSpeed = _camera.UseMovementSpeed;
- if (_speedWidget != null)
- _speedWidget.Parent.Visible = useMovementSpeed;
+ if (_cameraButton != null)
+ _cameraButton.Parent.Visible = useMovementSpeed;
}
// Get parent window
@@ -1215,18 +1591,8 @@ namespace FlaxEditor.Viewport
rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse;
if (rmbWheel)
{
- const float step = 4.0f;
- _wheelMovementChangeDeltaSum += _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity;
- if (_wheelMovementChangeDeltaSum >= step)
- {
- _wheelMovementChangeDeltaSum -= step;
- AdjustCameraMoveSpeed(1);
- }
- else if (_wheelMovementChangeDeltaSum <= -step)
- {
- _wheelMovementChangeDeltaSum += step;
- AdjustCameraMoveSpeed(-1);
- }
+ var step = _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity;
+ AdjustCameraMoveSpeed(step > 0.0f ? 1 : -1);
}
}
@@ -1495,22 +1861,6 @@ namespace FlaxEditor.Viewport
new CameraViewpoint("Bottom", new Float3(-90, 0, 0))
};
- private readonly float[] EditorViewportCameraSpeedValues =
- {
- 0.05f,
- 0.1f,
- 0.25f,
- 0.5f,
- 1.0f,
- 2.0f,
- 4.0f,
- 6.0f,
- 8.0f,
- 16.0f,
- 32.0f,
- 64.0f,
- };
-
private struct ViewModeOptions
{
public readonly string Name;
@@ -1566,28 +1916,17 @@ namespace FlaxEditor.Viewport
new ViewModeOptions(ViewMode.GlobalIllumination, "Global Illumination"),
};
- private void WidgetCamSpeedShowHide(Control cm)
- {
- if (cm.Visible == false)
- return;
-
- var ccm = (ContextMenu)cm;
- foreach (var e in ccm.Items)
- {
- if (e is ContextMenuButton b)
- {
- var v = (float)b.Tag;
- b.Icon = Mathf.Abs(MovementSpeed - v) < 0.001f
- ? Style.Current.CheckBoxTick
- : SpriteHandle.Invalid;
- }
- }
- }
-
private void WidgetViewModeShowHideClicked(ContextMenuButton button)
{
if (button.Tag is ViewMode v)
+ {
Task.ViewMode = v;
+ var cm = button.ParentContextMenu;
+ WidgetViewModeShowHide(cm);
+ var mainCM = ViewWidgetButtonMenu.GetChildMenu("Debug View").ContextMenu;
+ if (mainCM != null && cm != mainCM)
+ WidgetViewModeShowHide(mainCM);
+ }
}
private void WidgetViewModeShowHide(Control cm)
@@ -1599,7 +1938,7 @@ namespace FlaxEditor.Viewport
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b && b.Tag is ViewMode v)
- b.Icon = Task.View.Mode == v ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
+ b.Icon = Task.ViewMode == v ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs
index 1dc459135..eb462deb1 100644
--- a/Source/Editor/Viewport/PrefabWindowViewport.cs
+++ b/Source/Editor/Viewport/PrefabWindowViewport.cs
@@ -306,6 +306,8 @@ namespace FlaxEditor.Viewport
var orient = ViewOrientation;
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient);
}
+ ///
+ public EditorViewport Viewport => this;
///
public GizmosCollection Gizmos { get; }
diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs
index 5520d6c4c..46aac1cdf 100644
--- a/Source/Editor/Viewport/Previews/MaterialPreview.cs
+++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs
@@ -7,6 +7,8 @@ using FlaxEngine.GUI;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.GUI.ContextMenu;
using Object = FlaxEngine.Object;
+using FlaxEditor.GUI;
+using FlaxEditor.Scripting;
namespace FlaxEditor.Viewport.Previews
{
@@ -49,6 +51,8 @@ namespace FlaxEditor.Viewport.Previews
private Image _guiMaterialControl;
private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1];
private ContextMenu _modelWidgetButtonMenu;
+ private AssetPicker _customModelPicker;
+ private Model _customModel;
///
/// Gets or sets the material asset to preview. It can be or .
@@ -74,15 +78,66 @@ namespace FlaxEditor.Viewport.Previews
get => _selectedModelIndex;
set
{
+ if (value == -1) // Using Custom Model
+ return;
if (value < 0 || value > Models.Length)
throw new ArgumentOutOfRangeException();
+ if (_customModelPicker != null)
+ _customModelPicker.Validator.SelectedAsset = null;
_selectedModelIndex = value;
_previewModel.Model = FlaxEngine.Content.LoadAsyncInternal("Editor/Primitives/" + Models[value]);
_previewModel.Transform = Transforms[value];
}
}
+ // Used to automatically update which entry is checked.
+ // TODO: Maybe a better system with predicate bool checks could be used?
+ private void ResetModelContextMenu()
+ {
+ _modelWidgetButtonMenu.ItemsContainer.DisposeChildren();
+
+ // Fill out all models
+ for (int i = 0; i < Models.Length; i++)
+ {
+ var index = i;
+ var button = _modelWidgetButtonMenu.AddButton(Models[index]);
+ button.ButtonClicked += _ => SelectedModelIndex = index;
+ button.Checked = SelectedModelIndex == index && _customModel == null;
+ button.Tag = index;
+ }
+
+ _modelWidgetButtonMenu.AddSeparator();
+ _customModelPicker = new AssetPicker(new ScriptType(typeof(Model)), Float2.Zero);
+
+ // Label button
+ var customModelPickerLabel = _modelWidgetButtonMenu.AddButton("Custom Model:");
+ customModelPickerLabel.CloseMenuOnClick = false;
+ customModelPickerLabel.Checked = _customModel != null;
+
+ // Container button
+ var customModelPickerButton = _modelWidgetButtonMenu.AddButton("");
+ customModelPickerButton.Height = _customModelPicker.Height + 4;
+ customModelPickerButton.CloseMenuOnClick = false;
+ _customModelPicker.Parent = customModelPickerButton;
+ _customModelPicker.Validator.SelectedAsset = _customModel;
+ _customModelPicker.SelectedItemChanged += () =>
+ {
+ _customModel = _customModelPicker.Validator.SelectedAsset as Model;
+ if (_customModelPicker.Validator.SelectedAsset == null)
+ {
+ SelectedModelIndex = 0;
+ ResetModelContextMenu();
+ return;
+ }
+
+ _previewModel.Model = _customModel;
+ _previewModel.Transform = Transforms[0];
+ SelectedModelIndex = -1;
+ ResetModelContextMenu();
+ };
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -107,17 +162,7 @@ namespace FlaxEditor.Viewport.Previews
{
if (!control.Visible)
return;
- _modelWidgetButtonMenu.ItemsContainer.DisposeChildren();
-
- // Fill out all models
- for (int i = 0; i < Models.Length; i++)
- {
- var index = i;
- var button = _modelWidgetButtonMenu.AddButton(Models[index]);
- button.ButtonClicked += _ => SelectedModelIndex = index;
- button.Checked = SelectedModelIndex == index;
- button.Tag = index;
- }
+ ResetModelContextMenu();
};
new ViewportWidgetButton("Model", SpriteHandle.Invalid, _modelWidgetButtonMenu)
{
diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
index 481bc3f1b..5ff5cdb6d 100644
--- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
+++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
@@ -19,6 +19,7 @@ namespace FlaxEditor.Viewport.Widgets
private bool _checked;
private bool _autoCheck;
private bool _isMosueDown;
+ private float _forcedTextWidth;
///
/// Event fired when user toggles checked state.
@@ -63,14 +64,16 @@ namespace FlaxEditor.Viewport.Widgets
/// The text.
/// The icon.
/// The context menu.
- /// if set to true will be automatic checked on mouse click.
- public ViewportWidgetButton(string text, SpriteHandle icon, ContextMenu contextMenu = null, bool autoCheck = false)
- : base(0, 0, CalculateButtonWidth(0, icon.IsValid), ViewportWidgetsContainer.WidgetsHeight)
+ /// If set to true will be automatic checked on mouse click.
+ /// Forces the text to be drawn with the specified width.
+ public ViewportWidgetButton(string text, SpriteHandle icon, ContextMenu contextMenu = null, bool autoCheck = false, float textWidth = 0.0f)
+ : base(0, 0, CalculateButtonWidth(textWidth, icon.IsValid), ViewportWidgetsContainer.WidgetsHeight)
{
_text = text;
Icon = icon;
_cm = contextMenu;
_autoCheck = autoCheck;
+ _forcedTextWidth = textWidth;
if (_cm != null)
_cm.VisibleChanged += CmOnVisibleChanged;
@@ -160,7 +163,7 @@ namespace FlaxEditor.Viewport.Widgets
var style = Style.Current;
if (style != null && style.FontMedium)
- Width = CalculateButtonWidth(style.FontMedium.MeasureText(_text).X, Icon.IsValid);
+ Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.MeasureText(_text).X, Icon.IsValid);
}
}
}
diff --git a/Source/Editor/Windows/AssetReferencesGraphWindow.cs b/Source/Editor/Windows/AssetReferencesGraphWindow.cs
index d49896e2e..b9e0e7257 100644
--- a/Source/Editor/Windows/AssetReferencesGraphWindow.cs
+++ b/Source/Editor/Windows/AssetReferencesGraphWindow.cs
@@ -46,14 +46,14 @@ namespace FlaxEditor.Windows
if (asset != null)
{
var path = asset.Path;
- picker.SelectedAsset = asset;
+ picker.Validator.SelectedAsset = asset;
Title = System.IO.Path.GetFileNameWithoutExtension(path);
TooltipText = asset.TypeName + '\n' + path;
}
else
{
- picker.SelectedID = AssetId;
- var assetItem = picker.SelectedItem as AssetItem;
+ picker.Validator.SelectedID = AssetId;
+ var assetItem = picker.Validator.SelectedItem as AssetItem;
if (assetItem != null)
{
Title = assetItem.ShortName;
diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs
index a765c2faa..8f88d93f6 100644
--- a/Source/Editor/Windows/Assets/AnimationWindow.cs
+++ b/Source/Editor/Windows/Assets/AnimationWindow.cs
@@ -230,6 +230,8 @@ namespace FlaxEditor.Windows.Assets
public AnimationWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -265,8 +267,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/animation/index.html")).LinkTooltip("See documentation to learn more");
diff --git a/Source/Editor/Windows/Assets/AssetEditorWindow.cs b/Source/Editor/Windows/Assets/AssetEditorWindow.cs
index 0d244479c..2e048b924 100644
--- a/Source/Editor/Windows/Assets/AssetEditorWindow.cs
+++ b/Source/Editor/Windows/Assets/AssetEditorWindow.cs
@@ -388,14 +388,16 @@ namespace FlaxEditor.Windows.Assets
protected override void OnShow()
{
// Check if has no asset (but has item linked)
- if (_asset == null && _item != null)
+ var item = _item;
+ if (_asset == null && item != null)
{
// Load asset
_asset = LoadAsset();
if (_asset == null)
{
- Editor.LogError(string.Format("Cannot load asset \'{0}\' ({1})", _item.Path, typeof(T)));
+ Editor.LogError(string.Format("Cannot load asset \'{0}\' ({1})", item.Path, typeof(T)));
Close();
+ Editor.ContentDatabase.RefreshFolder(item, false);
return;
}
diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
index 8cbf6cf75..01ee9cb8a 100644
--- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
+++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
@@ -130,6 +130,8 @@ namespace FlaxEditor.Windows.Assets
public BehaviorTreeWindow(Editor editor, BinaryAssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -172,10 +174,10 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)");
+ _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
_toolstrip.AddButton(editor.Icons.CenterView64, _surface.ShowWholeGraph).LinkTooltip("Show whole graph");
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/ai/behavior-trees/index.html")).LinkTooltip("See documentation to learn more");
diff --git a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
index 623c4ef5b..a8121162a 100644
--- a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
+++ b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
@@ -395,6 +395,8 @@ namespace FlaxEditor.Windows.Assets
public GameplayGlobalsWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
_undo = new Undo();
_undo.ActionDone += OnUndo;
_undo.UndoDone += OnUndo;
@@ -411,10 +413,10 @@ namespace FlaxEditor.Windows.Assets
_proxy = new PropertiesProxy();
_propertiesEditor.Select(_proxy);
- _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save asset");
+ _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip($"Save asset ({inputOptions.Save})");
_toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_resetButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Rotate32, Reset).LinkTooltip("Resets the variables values to the default values");
diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs
index 097d4992a..a1178fb68 100644
--- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs
+++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs
@@ -34,6 +34,8 @@ namespace FlaxEditor.Windows.Assets
public JsonAssetWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -43,8 +45,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
// Panel
var panel = new Panel(ScrollBars.Vertical)
diff --git a/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs b/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs
index 0de5ce315..85f351fef 100644
--- a/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs
+++ b/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs
@@ -126,6 +126,8 @@ namespace FlaxEditor.Windows.Assets
public LocalizedStringTableWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -135,8 +137,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(Editor.Icons.Up64, OnExport).LinkTooltip("Export localization table entries for translation to .pot file");
diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
index 5f1273999..775e0c0dc 100644
--- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
+++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
@@ -375,6 +375,8 @@ namespace FlaxEditor.Windows.Assets
public MaterialInstanceWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -384,8 +386,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(Editor.Icons.Rotate64, OnRevertAllParameters).LinkTooltip("Revert all the parameters to the default values");
_toolstrip.AddSeparator();
@@ -521,8 +523,11 @@ namespace FlaxEditor.Windows.Assets
///
protected override void OnClose()
{
- // Discard unsaved changes
- _properties.DiscardChanges();
+ if (Asset)
+ {
+ // Discard unsaved changes
+ _properties.DiscardChanges();
+ }
// Cleanup
_undo.Clear();
diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs
index 6fbe32d7e..5aa77dbc3 100644
--- a/Source/Editor/Windows/Assets/MaterialWindow.cs
+++ b/Source/Editor/Windows/Assets/MaterialWindow.cs
@@ -41,8 +41,6 @@ namespace FlaxEditor.Windows.Assets
new ScriptType(typeof(Vector3)),
new ScriptType(typeof(Vector4)),
new ScriptType(typeof(Color)),
- new ScriptType(typeof(Quaternion)),
- new ScriptType(typeof(Transform)),
new ScriptType(typeof(Matrix)),
};
diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
index 17eda1358..4a5e02e6e 100644
--- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
+++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
@@ -306,6 +306,8 @@ namespace FlaxEditor.Windows.Assets
public ParticleSystemWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -359,8 +361,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more");
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
index 70aa1dca3..a8d9ae1be 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
@@ -339,7 +339,7 @@ namespace FlaxEditor.Windows.Assets
{
if (selection.Count != 0)
Select(actor);
- actor.TreeNode.StartRenaming(this);
+ actor.TreeNode.StartRenaming(this, _treePanel);
}
}
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs
index 4face0dc0..f50a832a1 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.cs
@@ -94,6 +94,8 @@ namespace FlaxEditor.Windows.Assets
public PrefabWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoEvent;
@@ -149,6 +151,7 @@ namespace FlaxEditor.Windows.Assets
// Prefab structure tree
Graph = new LocalSceneGraph(new CustomRootNode(this));
+ Graph.Root.TreeNode.Expand(true);
_tree = new PrefabTree
{
Margin = new Margin(0.0f, 0.0f, -16.0f, _treePanel.ScrollBarsSize), // Hide root node
@@ -175,12 +178,12 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _toolStripUndo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _toolStripRedo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _toolStripUndo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _toolStripRedo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
- _toolStripTranslate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Translate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip("Change Gizmo tool mode to Translate (1)");
- _toolStripRotate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Rotate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)");
- _toolStripScale = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Scale32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)");
+ _toolStripTranslate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Translate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip($"Change Gizmo tool mode to Translate ({inputOptions.TranslateMode})");
+ _toolStripRotate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Rotate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip($"Change Gizmo tool mode to Rotate ({inputOptions.RotateMode})");
+ _toolStripScale = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Scale32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})");
_toolstrip.AddSeparator();
_toolStripLiveReload = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Refresh64, () => LiveReload = !LiveReload).SetChecked(true).SetAutoCheck(true).LinkTooltip("Live changes preview (applies prefab changes on modification by auto)");
@@ -317,7 +320,7 @@ namespace FlaxEditor.Windows.Assets
Graph.MainActor = _viewport.Instance;
Selection.Clear();
Select(Graph.Main);
- Graph.Root.TreeNode.ExpandAll(true);
+ Graph.Root.TreeNode.Expand(true);
_undo.Clear();
ClearEditedFlag();
}
@@ -413,7 +416,7 @@ namespace FlaxEditor.Windows.Assets
_focusCamera = true;
Selection.Clear();
Select(Graph.Main);
- Graph.Root.TreeNode.ExpandAll(true);
+ Graph.Root.TreeNode.Expand(true);
_undo.Clear();
ClearEditedFlag();
diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
index 162944144..05435cc77 100644
--- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
+++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
@@ -627,6 +627,8 @@ namespace FlaxEditor.Windows.Assets
public SceneAnimationWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -652,8 +654,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_previewButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Refresh64, OnPreviewButtonClicked).SetAutoCheck(true).LinkTooltip("If checked, enables live-preview of the animation on a scene while editing");
_renderButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Build64, OnRenderButtonClicked).LinkTooltip("Open the scene animation rendering utility...");
diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
index d8790172b..ccfd5233c 100644
--- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
+++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
@@ -837,7 +837,7 @@ namespace FlaxEditor.Windows.Assets
sourceAssetPicker.CheckValid = CheckSourceAssetValid;
sourceAssetPicker.SelectedItemChanged += () =>
{
- proxy.Setups.Add(sourceAssetPicker.SelectedAsset, new SetupProxy());
+ proxy.Setups.Add(sourceAssetPicker.Validator.SelectedAsset, new SetupProxy());
proxy.Window.MarkAsEdited();
RebuildLayout();
};
@@ -856,7 +856,7 @@ namespace FlaxEditor.Windows.Assets
// Source asset picker
var sourceAssetPicker = setupGroup.AddPropertyItem("Source Asset").Custom().CustomControl;
- sourceAssetPicker.SelectedAsset = sourceAsset;
+ sourceAssetPicker.Validator.SelectedAsset = sourceAsset;
sourceAssetPicker.CanEdit = false;
sourceAssetPicker.Height = 48;
@@ -916,12 +916,12 @@ namespace FlaxEditor.Windows.Assets
{
// Show skeleton asset picker
var sourceSkeletonPicker = setupGroup.AddPropertyItem("Skeleton", "Skinned model that contains a skeleton for this animation retargeting.").Custom().CustomControl;
- sourceSkeletonPicker.AssetType = new ScriptType(typeof(SkinnedModel));
- sourceSkeletonPicker.SelectedAsset = setup.Value.Skeleton;
+ sourceSkeletonPicker.Validator.AssetType = new ScriptType(typeof(SkinnedModel));
+ sourceSkeletonPicker.Validator.SelectedAsset = setup.Value.Skeleton;
sourceSkeletonPicker.Height = 48;
sourceSkeletonPicker.SelectedItemChanged += () =>
{
- setup.Value.Skeleton = (SkinnedModel)sourceSkeletonPicker.SelectedAsset;
+ setup.Value.Skeleton = (SkinnedModel)sourceSkeletonPicker.Validator.SelectedAsset;
proxy.Window.MarkAsEdited();
};
}
diff --git a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
index 9750fa3e6..b285ddbed 100644
--- a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
+++ b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
@@ -62,6 +62,8 @@ namespace FlaxEditor.Windows.Assets
protected VisjectFunctionSurfaceWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs
index 74187652e..d6e3098a5 100644
--- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs
+++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs
@@ -562,6 +562,7 @@ namespace FlaxEditor.Windows.Assets
: base(editor, item)
{
var isPlayMode = Editor.IsPlayMode;
+ var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
@@ -607,11 +608,11 @@ namespace FlaxEditor.Windows.Assets
_debugToolstripControls = new[]
{
_toolstrip.AddSeparator(),
- _toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip("Continue (F5)"),
+ _toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip($"Continue ({inputOptions.DebuggerContinue})"),
_toolstrip.AddButton(editor.Icons.Search64, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"),
- _toolstrip.AddButton(editor.Icons.Right64, OnDebuggerStepOver).LinkTooltip("Step Over (F10)"),
- _toolstrip.AddButton(editor.Icons.Down64, OnDebuggerStepInto).LinkTooltip("Step Into (F11)"),
- _toolstrip.AddButton(editor.Icons.Up64, OnDebuggerStepOut).LinkTooltip("Step Out (Shift+F11)"),
+ _toolstrip.AddButton(editor.Icons.Right64, OnDebuggerStepOver).LinkTooltip($"Step Over ({inputOptions.DebuggerStepOver})"),
+ _toolstrip.AddButton(editor.Icons.Down64, OnDebuggerStepInto).LinkTooltip($"Step Into ({inputOptions.DebuggerStepInto})"),
+ _toolstrip.AddButton(editor.Icons.Up64, OnDebuggerStepOut).LinkTooltip($"Step Out ({inputOptions.DebuggerStepOut})"),
_toolstrip.AddButton(editor.Icons.Stop64, OnDebuggerStop).LinkTooltip("Stop debugging"),
};
foreach (var control in _debugToolstripControls)
diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
index 03873df57..168067977 100644
--- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
@@ -2,6 +2,7 @@
using System;
using System.IO;
+using System.Linq;
using FlaxEditor.Content;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
@@ -186,12 +187,12 @@ namespace FlaxEditor.Windows
continue;
// Get context proxy
- ContentProxy p;
+ ContentProxy p = null;
if (type.Type.IsSubclassOf(typeof(ContentProxy)))
{
p = Editor.ContentDatabase.Proxy.Find(x => x.GetType() == type.Type);
}
- else
+ else if (type.CanCreateInstance)
{
// User can use attribute to put their own assets into the content context menu
var generic = typeof(SpawnableJsonAssetProxy<>).MakeGenericType(type.Type);
@@ -249,6 +250,10 @@ namespace FlaxEditor.Windows
});
}
+ // Remove any leftover separator
+ if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator)
+ cm.ItemsContainer.Children.Last().Dispose();
+
// Show it
cm.Show(this, location);
}
@@ -364,7 +369,7 @@ namespace FlaxEditor.Windows
}
var pluginPath = Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text);
- if (Directory.Exists(pluginPath))
+ if (!IsValidModuleName(nameTextBox.Text) || Directory.Exists(pluginPath))
{
nameTextBox.BorderColor = Color.Red;
nameTextBox.BorderSelectedColor = Color.Red;
@@ -424,6 +429,12 @@ namespace FlaxEditor.Windows
submitButton.Clicked += () =>
{
// TODO: Check all modules in project including plugins
+ if (!IsValidModuleName(nameTextBox.Text))
+ {
+ Editor.LogWarning("Invalid module name. Module names cannot contain spaces, start with a number or contain non-alphanumeric characters.");
+ return;
+ }
+
if (Directory.Exists(Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text)))
{
Editor.LogWarning("Cannot create module due to name conflict.");
@@ -455,5 +466,16 @@ namespace FlaxEditor.Windows
button.ParentContextMenu.Hide();
};
}
+
+ private static bool IsValidModuleName(string text)
+ {
+ if (text.Contains(' '))
+ return false;
+ if (char.IsDigit(text[0]))
+ return false;
+ if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_'))
+ return false;
+ return true;
+ }
}
}
diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs
index a06ec839d..d43174cf3 100644
--- a/Source/Editor/Windows/ContentWindow.cs
+++ b/Source/Editor/Windows/ContentWindow.cs
@@ -629,8 +629,9 @@ namespace FlaxEditor.Windows
if (items.Count == 0)
return;
- // TODO: remove items that depend on different items in the list: use wants to remove `folderA` and `folderA/asset.x`, we should just remove `folderA`
+ // Sort items to remove files first, then folders
var toDelete = new List(items);
+ toDelete.Sort((a, b) => a.IsFolder ? 1 : b.IsFolder ? -1 : a.Compare(b));
string msg = toDelete.Count == 1
? string.Format("Are you sure to delete \'{0}\'?\nThis action cannot be undone. Files will be deleted permanently.", items[0].Path)
diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs
index 5ece067a0..2ef9c05cf 100644
--- a/Source/Editor/Windows/GameCookerWindow.cs
+++ b/Source/Editor/Windows/GameCookerWindow.cs
@@ -155,29 +155,42 @@ namespace FlaxEditor.Windows
public virtual void OnNotAvailableLayout(LayoutElementsContainer layout)
{
- layout.Label("Missing platform data tools for the target platform.", TextAlignment.Center);
+ string text = "Missing platform data tools for the target platform.";
if (FlaxEditor.Editor.IsOfficialBuild())
{
switch (BuildPlatform)
{
+#if PLATFORM_WINDOWS
case BuildPlatform.Windows32:
case BuildPlatform.Windows64:
case BuildPlatform.UWPx86:
case BuildPlatform.UWPx64:
case BuildPlatform.LinuxX64:
case BuildPlatform.AndroidARM64:
- layout.Label("Use Flax Launcher and download the required package.", TextAlignment.Center);
+ text += "\nUse Flax Launcher and download the required package.";
break;
+#endif
default:
- layout.Label("Engine source is required to target this platform.", TextAlignment.Center);
+ text += "\nEngine source is required to target this platform.";
break;
}
}
else
{
- var label = layout.Label("To target this platform separate engine source package is required.\nTo get access please contact via https://flaxengine.com/contact", TextAlignment.Center);
- label.Label.AutoHeight = true;
+ text += "\nTo target this platform separate engine source package is required.";
+ switch (BuildPlatform)
+ {
+ case BuildPlatform.XboxOne:
+ case BuildPlatform.XboxScarlett:
+ case BuildPlatform.PS4:
+ case BuildPlatform.PS5:
+ case BuildPlatform.Switch:
+ text += "\nTo get access please contact via https://flaxengine.com/contact";
+ break;
+ }
}
+ var label = layout.Label(text, TextAlignment.Center);
+ label.Label.AutoHeight = true;
}
public virtual void Build()
diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index fdedbb3c2..8c6f5dc06 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -271,8 +271,6 @@ namespace FlaxEditor.Windows
Title = "Game";
AutoFocus = true;
- FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
-
var task = MainRenderTask.Instance;
// Setup viewport
@@ -304,6 +302,12 @@ namespace FlaxEditor.Windows
// Link editor options
Editor.Options.OptionsChanged += OnOptionsChanged;
OnOptionsChanged(Editor.Options.Options);
+
+ InputActions.Add(options => options.TakeScreenshot, () => Screenshot.Capture(string.Empty));
+ InputActions.Add(options => options.DebuggerUnlockMouse, UnlockMouseInPlay);
+ InputActions.Add(options => options.ToggleFullscreen, () => { if (Editor.IsPlayMode) IsMaximized = !IsMaximized; });
+
+ FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
}
private void ChangeViewportRatio(ViewportScaleOptions v)
@@ -945,27 +949,6 @@ namespace FlaxEditor.Windows
///
public override bool OnKeyDown(KeyboardKeys key)
{
- switch (key)
- {
- case KeyboardKeys.F12:
- Screenshot.Capture(string.Empty);
- return true;
- case KeyboardKeys.F11:
- if (Root.GetKey(KeyboardKeys.Shift))
- {
- // Unlock mouse in game mode
- UnlockMouseInPlay();
- return true;
- }
- else if (Editor.IsPlayMode)
- {
- // Maximized game window toggle
- IsMaximized = !IsMaximized;
- return true;
- }
- break;
- }
-
// Prevent closing the game window tab during a play session
if (Editor.StateMachine.IsPlayMode && Editor.Options.Options.Input.CloseTab.Process(this, key))
{
diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs
index 3665b7073..6526d7c8a 100644
--- a/Source/Editor/Windows/OutputLogWindow.cs
+++ b/Source/Editor/Windows/OutputLogWindow.cs
@@ -467,6 +467,7 @@ namespace FlaxEditor.Windows
if (_isDirty)
{
_isDirty = false;
+ var wasEmpty = _output.TextLength == 0;
// Cache fonts
_output.DefaultStyle.Font.GetFont();
@@ -589,7 +590,7 @@ namespace FlaxEditor.Windows
// Update the output
var cachedScrollValue = _vScroll.Value;
var cachedSelection = _output.SelectionRange;
- var isBottomScroll = _vScroll.Value >= _vScroll.Maximum - 20.0f;
+ var isBottomScroll = _vScroll.Value >= _vScroll.Maximum - 20.0f || wasEmpty;
_output.Text = _textBuffer.ToString();
_textBufferCount = _entries.Count;
if (!_vScroll.IsThumbClicked)
diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs
index 78d84ad9d..d29000d27 100644
--- a/Source/Editor/Windows/Profiler/Network.cs
+++ b/Source/Editor/Windows/Profiler/Network.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using FlaxEditor.GUI;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Networking;
namespace FlaxEngine
{
@@ -37,11 +38,14 @@ namespace FlaxEditor.Windows.Profiler
{
private readonly SingleChart _dataSentChart;
private readonly SingleChart _dataReceivedChart;
+ private readonly SingleChart _dataSentRateChart;
+ private readonly SingleChart _dataReceivedRateChart;
private readonly Table _tableRpc;
private readonly Table _tableRep;
- private SamplesBuffer _events;
private List _tableRowsCache;
- private FlaxEngine.Networking.NetworkDriverStats _prevStats;
+ private SamplesBuffer _events;
+ private NetworkDriverStats _prevStats;
+ private List _stats;
public Network()
: base("Network")
@@ -76,6 +80,20 @@ namespace FlaxEditor.Windows.Profiler
Parent = layout,
};
_dataReceivedChart.SelectedSampleChanged += OnSelectedSampleChanged;
+ _dataSentRateChart = new SingleChart
+ {
+ Title = "Data Sent Rate",
+ FormatSample = FormatSampleBytesRate,
+ Parent = layout,
+ };
+ _dataSentRateChart.SelectedSampleChanged += OnSelectedSampleChanged;
+ _dataReceivedRateChart = new SingleChart
+ {
+ Title = "Data Received Rate",
+ FormatSample = FormatSampleBytesRate,
+ Parent = layout,
+ };
+ _dataReceivedRateChart.SelectedSampleChanged += OnSelectedSampleChanged;
// Tables
_tableRpc = InitTable(layout, "RPC Name");
@@ -87,24 +105,52 @@ namespace FlaxEditor.Windows.Profiler
{
_dataSentChart.Clear();
_dataReceivedChart.Clear();
+ _dataSentRateChart.Clear();
+ _dataReceivedRateChart.Clear();
_events?.Clear();
+ _stats?.Clear();
+ _prevStats = new NetworkDriverStats();
}
///
public override void Update(ref SharedUpdateData sharedData)
{
// Gather peer stats
- var peers = FlaxEngine.Networking.NetworkPeer.Peers;
- var stats = new FlaxEngine.Networking.NetworkDriverStats();
+ var peers = NetworkPeer.Peers;
+ var thisStats = new NetworkDriverStats();
+ thisStats.RTT = Time.UnscaledGameTime; // Store sample time in RTT
foreach (var peer in peers)
{
var peerStats = peer.NetworkDriver.GetStats();
- stats.TotalDataSent += peerStats.TotalDataSent;
- stats.TotalDataReceived += peerStats.TotalDataReceived;
+ thisStats.TotalDataSent += peerStats.TotalDataSent;
+ thisStats.TotalDataReceived += peerStats.TotalDataReceived;
}
- _dataSentChart.AddSample(Mathf.Max((long)stats.TotalDataSent - (long)_prevStats.TotalDataSent, 0));
- _dataReceivedChart.AddSample(Mathf.Max((long)stats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0));
- _prevStats = stats;
+ var stats = thisStats;
+ stats.TotalDataSent = (uint)Mathf.Max((long)thisStats.TotalDataSent - (long)_prevStats.TotalDataSent, 0);
+ stats.TotalDataReceived = (uint)Mathf.Max((long)thisStats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0);
+ _dataSentChart.AddSample(stats.TotalDataSent);
+ _dataReceivedChart.AddSample(stats.TotalDataReceived);
+ _prevStats = thisStats;
+ if (_stats == null)
+ _stats = new List();
+ _stats.Add(stats);
+
+ // Remove all stats older than 1 second
+ while (_stats.Count > 0 && thisStats.RTT - _stats[0].RTT >= 1.0f)
+ _stats.RemoveAt(0);
+
+ // Calculate average data rates (from last second)
+ var avgStats = new NetworkDriverStats();
+ foreach (var e in _stats)
+ {
+ avgStats.TotalDataSent += e.TotalDataSent;
+ avgStats.TotalDataReceived += e.TotalDataReceived;
+ }
+ avgStats.TotalDataSent /= (uint)_stats.Count;
+ avgStats.TotalDataReceived /= (uint)_stats.Count;
+ _dataSentRateChart.AddSample(avgStats.TotalDataSent);
+ _dataReceivedRateChart.AddSample(avgStats.TotalDataReceived);
+
// Gather network events
var events = ProfilingTools.EventsNetwork;
@@ -118,6 +164,8 @@ namespace FlaxEditor.Windows.Profiler
{
_dataSentChart.SelectedSampleIndex = selectedFrame;
_dataReceivedChart.SelectedSampleIndex = selectedFrame;
+ _dataSentRateChart.SelectedSampleIndex = selectedFrame;
+ _dataReceivedRateChart.SelectedSampleIndex = selectedFrame;
// Update events tables
if (_events != null)
@@ -257,6 +305,11 @@ namespace FlaxEditor.Windows.Profiler
return Utilities.Utils.FormatBytesCount((ulong)v);
}
+ private static string FormatSampleBytesRate(float v)
+ {
+ return Utilities.Utils.FormatBytesCount((ulong)v) + "/s";
+ }
+
private static string FormatCellBytes(object x)
{
return Utilities.Utils.FormatBytesCount((int)x);
diff --git a/Source/Editor/Windows/Profiler/SamplesBuffer.cs b/Source/Editor/Windows/Profiler/SamplesBuffer.cs
index 999156dca..2e0169d47 100644
--- a/Source/Editor/Windows/Profiler/SamplesBuffer.cs
+++ b/Source/Editor/Windows/Profiler/SamplesBuffer.cs
@@ -49,7 +49,7 @@ namespace FlaxEditor.Windows.Profiler
/// The sample value
public T Get(int index)
{
- if (index >= _data.Length || _data.Length == 0)
+ if (_count == 0 || index >= _data.Length || _data.Length == 0)
return default;
return index == -1 ? _data[_count - 1] : _data[index];
}
diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs
index cbaa27371..250bd2301 100644
--- a/Source/Editor/Windows/SceneTreeWindow.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.cs
@@ -142,7 +142,7 @@ namespace FlaxEditor.Windows
{
if (selection.Count != 0)
Editor.SceneEditing.Select(actor);
- actor.TreeNode.StartRenaming(this);
+ actor.TreeNode.StartRenaming(this, _sceneTreePanel);
}
}
diff --git a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs
index c84a9ba33..57f10f2f4 100644
--- a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs
+++ b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs
@@ -399,6 +399,8 @@ namespace FlaxEditor.Windows
{
Title = "Visual Script Debugger";
+ var inputOptions = editor.Options.Options.Input;
+
var toolstrip = new ToolStrip
{
Parent = this
@@ -407,7 +409,7 @@ namespace FlaxEditor.Windows
_debugToolstripControls = new[]
{
toolstrip.AddSeparator(),
- toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip("Continue (F5)"),
+ toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip($"Continue ({inputOptions.DebuggerContinue})"),
toolstrip.AddButton(editor.Icons.Search64, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"),
toolstrip.AddButton(editor.Icons.Stop64, OnDebuggerStop).LinkTooltip("Stop debugging"),
};
diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
index 0518fe248..5cceb31e3 100644
--- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
+++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
@@ -2109,7 +2109,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
bucket.LoopsLeft--;
bucket.LoopsDone++;
}
- value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, slot.Speed);
+ // Speed is accounted for in the new time pos, so keep sample speed at 1
+ value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, 1);
bucket.TimePosition = newTimePos;
if (bucket.LoopsLeft == 0 && slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition)
{
diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp
index d04da7274..689f38d12 100644
--- a/Source/Engine/Audio/AudioClip.cpp
+++ b/Source/Engine/Audio/AudioClip.cpp
@@ -225,6 +225,7 @@ bool AudioClip::ExtractDataRaw(Array& resultData, AudioDataInfo& resultDat
void AudioClip::CancelStreaming()
{
+ Asset::CancelStreaming();
CancelStreamingTasks();
}
diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
index 2f65a4b6c..ace8b6591 100644
--- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
+++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
@@ -33,7 +33,8 @@
int alError = alGetError(); \
if (alError != 0) \
{ \
- LOG(Error, "OpenAL method {0} failed with error 0x{1:X} (at line {2})", TEXT(#method), alError, __LINE__ - 1); \
+ const Char* errorStr = GetOpenALErrorString(alError); \
+ LOG(Error, "OpenAL method {0} failed with error 0x{1:X}:{2} (at line {3})", TEXT(#method), alError, errorStr, __LINE__ - 1); \
} \
}
#endif
@@ -290,6 +291,28 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
return 0;
}
+const Char* GetOpenALErrorString(int error)
+{
+ switch (error)
+ {
+ case AL_NO_ERROR:
+ return TEXT("AL_NO_ERROR");
+ case AL_INVALID_NAME:
+ return TEXT("AL_INVALID_NAME");
+ case AL_INVALID_ENUM:
+ return TEXT("AL_INVALID_ENUM");
+ case AL_INVALID_VALUE:
+ return TEXT("AL_INVALID_VALUE");
+ case AL_INVALID_OPERATION:
+ return TEXT("AL_INVALID_OPERATION");
+ case AL_OUT_OF_MEMORY:
+ return TEXT("AL_OUT_OF_MEMORY");
+ default:
+ break;
+ }
+ return TEXT("???");
+}
+
void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
{
#if ALC_MULTIPLE_LISTENERS
@@ -838,7 +861,11 @@ bool AudioBackendOAL::Base_Init()
// Init
Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model
- ALC::RebuildContexts(true);
+ int32 clampedIndex = Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1);
+ if (clampedIndex == Audio::GetActiveDeviceIndex())
+ {
+ ALC::RebuildContexts(true);
+ }
Audio::SetActiveDeviceIndex(activeDeviceIndex);
#ifdef AL_SOFT_source_spatialize
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))
diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp
index 309a82e04..93d904d5e 100644
--- a/Source/Engine/Content/Asset.cpp
+++ b/Source/Engine/Content/Asset.cpp
@@ -16,11 +16,13 @@
AssetReferenceBase::~AssetReferenceBase()
{
- if (_asset)
+ Asset* asset = _asset;
+ if (asset)
{
- _asset->OnLoaded.Unbind(this);
- _asset->OnUnloaded.Unbind(this);
- _asset->RemoveReference();
+ _asset = nullptr;
+ asset->OnLoaded.Unbind(this);
+ asset->OnUnloaded.Unbind(this);
+ asset->RemoveReference();
}
}
@@ -70,8 +72,12 @@ void AssetReferenceBase::OnUnloaded(Asset* asset)
WeakAssetReferenceBase::~WeakAssetReferenceBase()
{
- if (_asset)
- _asset->OnUnloaded.Unbind(this);
+ Asset* asset = _asset;
+ if (asset)
+ {
+ _asset = nullptr;
+ asset->OnUnloaded.Unbind(this);
+ }
}
String WeakAssetReferenceBase::ToString() const
@@ -101,6 +107,20 @@ void WeakAssetReferenceBase::OnUnloaded(Asset* asset)
_asset = nullptr;
}
+SoftAssetReferenceBase::~SoftAssetReferenceBase()
+{
+ Asset* asset = _asset;
+ if (asset)
+ {
+ _asset = nullptr;
+ asset->OnUnloaded.Unbind(this);
+ asset->RemoveReference();
+ }
+#if !BUILD_RELEASE
+ _id = Guid::Empty;
+#endif
+}
+
String SoftAssetReferenceBase::ToString() const
{
return _asset ? _asset->ToString() : (_id.IsValid() ? _id.ToString() : TEXT(""));
@@ -502,6 +522,14 @@ void Asset::InitAsVirtual()
void Asset::CancelStreaming()
{
+ // Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
+ Locker.Lock();
+ ContentLoadTask* loadTask = _loadingTask;
+ Locker.Unlock();
+ if (loadTask)
+ {
+ loadTask->Cancel();
+ }
}
#if USE_EDITOR
@@ -538,11 +566,7 @@ ContentLoadTask* Asset::createLoadingTask()
void Asset::startLoading()
{
- // Check if is already loaded
- if (IsLoaded())
- return;
-
- // Start loading (using async tasks)
+ ASSERT(!IsLoaded());
ASSERT(_loadingTask == nullptr);
_loadingTask = createLoadingTask();
ASSERT(_loadingTask != nullptr);
diff --git a/Source/Engine/Content/AssetReference.h b/Source/Engine/Content/AssetReference.h
index 6aee12246..b9d54f30a 100644
--- a/Source/Engine/Content/AssetReference.h
+++ b/Source/Engine/Content/AssetReference.h
@@ -9,9 +9,6 @@
///
class FLAXENGINE_API AssetReferenceBase
{
-public:
- typedef Delegate<> EventType;
-
protected:
Asset* _asset = nullptr;
@@ -19,17 +16,17 @@ public:
///
/// The asset loaded event (fired when asset gets loaded or is already loaded after change).
///
- EventType Loaded;
+ Action Loaded;
///
/// The asset unloading event (should cleanup refs to it).
///
- EventType Unload;
+ Action Unload;
///
/// Action fired when field gets changed (link a new asset or change to the another value).
///
- EventType Changed;
+ Action Changed;
public:
NON_COPYABLE(AssetReferenceBase);
diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp
index 76ed34b80..276b6b1f7 100644
--- a/Source/Engine/Content/Assets/Material.cpp
+++ b/Source/Engine/Content/Assets/Material.cpp
@@ -20,6 +20,7 @@
#include "Engine/ShadersCompilation/Config.h"
#if BUILD_DEBUG
#include "Engine/Engine/Globals.h"
+#include "Engine/Scripting/BinaryModule.h"
#endif
#endif
@@ -256,7 +257,9 @@ Asset::LoadResult Material::load()
#if BUILD_DEBUG && USE_EDITOR
// Dump generated material source to the temporary file
+ BinaryModule::Locker.Lock();
source.SaveToFile(Globals::ProjectCacheFolder / TEXT("material.txt"));
+ BinaryModule::Locker.Unlock();
#endif
// Encrypt source code
diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp
index 5a008645d..691b00a50 100644
--- a/Source/Engine/Content/Assets/Model.cpp
+++ b/Source/Engine/Content/Assets/Model.cpp
@@ -783,6 +783,7 @@ void Model::InitAsVirtual()
void Model::CancelStreaming()
{
+ Asset::CancelStreaming();
CancelStreamingTasks();
}
diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp
index 5871087d9..b823db5a3 100644
--- a/Source/Engine/Content/Assets/SkinnedModel.cpp
+++ b/Source/Engine/Content/Assets/SkinnedModel.cpp
@@ -969,6 +969,7 @@ void SkinnedModel::InitAsVirtual()
void SkinnedModel::CancelStreaming()
{
+ Asset::CancelStreaming();
CancelStreamingTasks();
}
diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp
index 105d4ad2d..9748ba60c 100644
--- a/Source/Engine/Content/Assets/VisualScript.cpp
+++ b/Source/Engine/Content/Assets/VisualScript.cpp
@@ -1428,6 +1428,10 @@ Asset::LoadResult VisualScript::load()
#if USE_EDITOR
if (_instances.HasItems())
{
+ // Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
+ _loadFailed = false;
+ _isLoaded = true;
+
// Setup scripting type
CacheScriptingType();
@@ -1512,7 +1516,7 @@ void VisualScript::unload(bool isReloading)
// Note: preserve the registered scripting type but invalidate the locally cached handle
if (_scriptingTypeHandle)
{
- VisualScriptingModule.Locker.Lock();
+ VisualScriptingBinaryModule::Locker.Lock();
auto& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex];
if (type.Script.DefaultInstance)
{
@@ -1523,7 +1527,7 @@ void VisualScript::unload(bool isReloading)
VisualScriptingModule.Scripts[_scriptingTypeHandle.TypeIndex] = nullptr;
_scriptingTypeHandleCached = _scriptingTypeHandle;
_scriptingTypeHandle = ScriptingTypeHandle();
- VisualScriptingModule.Locker.Unlock();
+ VisualScriptingBinaryModule::Locker.Unlock();
}
}
@@ -1534,8 +1538,8 @@ AssetChunksFlag VisualScript::getChunksToPreload() const
void VisualScript::CacheScriptingType()
{
+ ScopeLock lock(VisualScriptingBinaryModule::Locker);
auto& binaryModule = VisualScriptingModule;
- ScopeLock lock(binaryModule.Locker);
// Find base type
const StringAnsi baseTypename(Meta.BaseTypename);
diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp
index ea627f2c7..bd27abc5a 100644
--- a/Source/Engine/Content/Content.cpp
+++ b/Source/Engine/Content/Content.cpp
@@ -154,7 +154,7 @@ void ContentService::LateUpdate()
// Unload marked assets
for (int32 i = 0; i < ToUnload.Count(); i++)
{
- Asset* asset = ToUnload[i];
+ Asset* asset = ToUnload[i];
// Check if has no references
if (asset->GetReferencesCount() <= 0)
@@ -521,37 +521,33 @@ Asset* Content::GetAsset(const Guid& id)
void Content::DeleteAsset(Asset* asset)
{
- ScopeLock locker(AssetsLocker);
-
- // Validate
if (asset == nullptr || asset->_deleteFileOnUnload)
- {
- // Back
return;
- }
LOG(Info, "Deleting asset {0}...", asset->ToString());
+ // Ensure that asset is loaded (easier than cancel in-flight loading)
+ asset->WaitForLoaded();
+
// Mark asset for delete queue (delete it after auto unload)
asset->_deleteFileOnUnload = true;
// Unload
- UnloadAsset(asset);
+ asset->DeleteObject();
}
void Content::DeleteAsset(const StringView& path)
{
- ScopeLock locker(AssetsLocker);
-
- // Check if is loaded
+ // Try to delete already loaded asset
Asset* asset = GetAsset(path);
if (asset != nullptr)
{
- // Delete asset
DeleteAsset(asset);
return;
}
+ ScopeLock locker(AssetsLocker);
+
// Remove from registry
AssetInfo info;
if (Cache.DeleteAsset(path, &info))
@@ -573,7 +569,6 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id)
// Check if given id is invalid
if (!id.IsValid())
{
- // Cancel operation
LOG(Warning, "Cannot remove file \'{0}\'. Given ID is invalid.", path);
return;
}
@@ -585,7 +580,6 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id)
storage->CloseFileHandles(); // Close file handle to allow removing it
if (!storage->HasAsset(id))
{
- // Skip removing
LOG(Warning, "Cannot remove file \'{0}\'. It doesn\'t contain asset {1}.", path, id);
return;
}
@@ -703,13 +697,11 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
LOG(Warning, "Cannot copy file to destination.");
return true;
}
-
if (JsonStorageProxy::ChangeId(dstPath, dstId))
{
LOG(Warning, "Cannot change asset ID.");
return true;
}
-
return false;
}
@@ -774,12 +766,9 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
FileSystem::DeleteFile(tmpPath);
// Reload storage
+ if (auto storage = ContentStorageManager::GetStorage(dstPath))
{
- auto storage = ContentStorageManager::GetStorage(dstPath);
- if (storage)
- {
- storage->Reload();
- }
+ storage->Reload();
}
}
@@ -790,10 +779,8 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
void Content::UnloadAsset(Asset* asset)
{
- // Check input
if (asset == nullptr)
return;
-
asset->DeleteObject();
}
@@ -919,12 +906,8 @@ bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const Script
Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
{
- // Early out
if (!id.IsValid())
- {
- // Back
return nullptr;
- }
// Check if asset has been already loaded
Asset* result = GetAsset(id);
@@ -936,7 +919,6 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
LOG(Warning, "Different loaded asset type! Asset: \'{0}\'. Expected type: {1}", result->ToString(), type.ToString());
return nullptr;
}
-
return result;
}
@@ -954,12 +936,8 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
LoadCallAssetsLocker.Lock();
const bool contains = LoadCallAssets.Contains(id);
LoadCallAssetsLocker.Unlock();
-
if (!contains)
- {
return GetAsset(id);
- }
-
Platform::Sleep(1);
}
}
@@ -967,7 +945,6 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
{
// Mark asset as loading
LoadCallAssets.Add(id);
-
LoadCallAssetsLocker.Unlock();
}
@@ -988,7 +965,7 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
// Get cached asset info (from registry)
if (!GetAssetInfo(id, assetInfo))
{
- LOG(Warning, "Invalid or missing asset ({0}, {1}).", id.ToString(Guid::FormatType::N), type.ToString());
+ LOG(Warning, "Invalid or missing asset ({0}, {1}).", id, type.ToString());
return nullptr;
}
@@ -1032,11 +1009,13 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
ASSERT(!Assets.ContainsKey(id));
#endif
Assets.Add(id, result);
- AssetsLocker.Unlock();
// Start asset loading
+ // TODO: refactor this to create asset loading task-chain before AssetsLocker.Lock() to allow better parallelization
result->startLoading();
+ AssetsLocker.Unlock();
+
return result;
}
diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h
index 23cf5f787..4a7bbb2bb 100644
--- a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h
+++ b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h
@@ -48,6 +48,8 @@ protected:
// [ContentLoadTask]
Result run() override
{
+ if (IsCancelRequested())
+ return Result::Ok;
PROFILE_CPU();
AssetReference ref = _asset.Get();
@@ -67,8 +69,6 @@ protected:
{
if (IsCancelRequested())
return Result::Ok;
-
- // Load it
#if TRACY_ENABLE
ZoneScoped;
ZoneName(*name, name.Length());
diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
index 5ee384769..19d6fdd31 100644
--- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
+++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h
@@ -31,10 +31,13 @@ public:
if (Asset)
{
Asset->Locker.Lock();
- Asset->_loadFailed = true;
- Asset->_isLoaded = false;
- LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
- Asset->_loadingTask = nullptr;
+ if (Asset->_loadingTask == this)
+ {
+ Asset->_loadFailed = true;
+ Asset->_isLoaded = false;
+ LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
+ Asset->_loadingTask = nullptr;
+ }
Asset->Locker.Unlock();
}
}
@@ -73,7 +76,10 @@ protected:
{
if (Asset)
{
- Asset->_loadingTask = nullptr;
+ Asset->Locker.Lock();
+ if (Asset->_loadingTask == this)
+ Asset->_loadingTask = nullptr;
+ Asset->Locker.Unlock();
Asset = nullptr;
}
@@ -84,7 +90,10 @@ protected:
{
if (Asset)
{
- Asset->_loadingTask = nullptr;
+ Asset->Locker.Lock();
+ if (Asset->_loadingTask == this)
+ Asset->_loadingTask = nullptr;
+ Asset->Locker.Unlock();
Asset = nullptr;
}
diff --git a/Source/Engine/Content/SoftAssetReference.h b/Source/Engine/Content/SoftAssetReference.h
index fe1cde8c2..d237b5fd7 100644
--- a/Source/Engine/Content/SoftAssetReference.h
+++ b/Source/Engine/Content/SoftAssetReference.h
@@ -30,9 +30,7 @@ public:
///
/// Finalizes an instance of the class.
///
- ~SoftAssetReferenceBase()
- {
- }
+ ~SoftAssetReferenceBase();
public:
///
diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp
index d530e5456..a47e0bd0e 100644
--- a/Source/Engine/Content/Storage/FlaxStorage.cpp
+++ b/Source/Engine/Content/Storage/FlaxStorage.cpp
@@ -1302,15 +1302,15 @@ void FlaxStorage::CloseFileHandles()
// In those situations all the async tasks using this storage should be cancelled externally
// Ensure that no one is using this resource
- int32 waitTime = 10;
+ int32 waitTime = 100;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
- Platform::Sleep(10);
+ Platform::Sleep(1);
if (Platform::AtomicRead(&_chunksLock) != 0)
{
// File can be locked by some streaming tasks (eg. AudioClip::StreamingTask or StreamModelLODTask)
+ Entry e;
for (int32 i = 0; i < GetEntriesCount(); i++)
{
- Entry e;
GetEntry(i, e);
Asset* asset = Content::GetAsset(e.ID);
if (asset)
@@ -1320,8 +1320,12 @@ void FlaxStorage::CloseFileHandles()
}
}
}
+ waitTime = 100;
+ while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
+ Platform::Sleep(1);
ASSERT(_chunksLock == 0);
+ // Close file handles (from all threads)
_file.DeleteAll();
}
diff --git a/Source/Engine/ContentImporters/ImportModelFile.cpp b/Source/Engine/ContentImporters/ImportModelFile.cpp
index b9a4f5675..b34bbfaaf 100644
--- a/Source/Engine/ContentImporters/ImportModelFile.cpp
+++ b/Source/Engine/ContentImporters/ImportModelFile.cpp
@@ -124,7 +124,8 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
// Import model file
ModelData modelData;
String errorMsg;
- String autoImportOutput = String(StringUtils::GetDirectoryName(context.TargetAssetPath)) / StringUtils::GetFileNameWithoutExtension(context.InputPath);
+ String autoImportOutput(StringUtils::GetDirectoryName(context.TargetAssetPath));
+ autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath));
if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, autoImportOutput))
{
LOG(Error, "Cannot import model file. {0}", errorMsg);
diff --git a/Source/Engine/Core/Collections/BitArray.h b/Source/Engine/Core/Collections/BitArray.h
index 01238d434..eeadc82e9 100644
--- a/Source/Engine/Core/Collections/BitArray.h
+++ b/Source/Engine/Core/Collections/BitArray.h
@@ -22,6 +22,16 @@ private:
int32 _capacity;
AllocationData _allocation;
+ FORCE_INLINE static int32 ToItemCount(int32 size)
+ {
+ return Math::DivideAndRoundUp(size, sizeof(ItemType));
+ }
+
+ FORCE_INLINE static int32 ToItemCapacity(int32 size)
+ {
+ return Math::Max(Math::DivideAndRoundUp(size, sizeof(ItemType)), 1);
+ }
+
public:
///
/// Initializes a new instance of the class.
@@ -41,7 +51,7 @@ public:
, _capacity(capacity)
{
if (capacity > 0)
- _allocation.Allocate(Math::Max(capacity / sizeof(ItemType), 1));
+ _allocation.Allocate(ToItemCapacity(capacity));
}
///
@@ -53,7 +63,7 @@ public:
_count = _capacity = other.Count();
if (_capacity > 0)
{
- const uint64 itemsCapacity = Math::Max(_capacity / sizeof(ItemType), 1);
+ const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -69,7 +79,7 @@ public:
_count = _capacity = other.Count();
if (_capacity > 0)
{
- const uint64 itemsCapacity = Math::Max(_capacity / sizeof(ItemType), 1);
+ const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -101,7 +111,7 @@ public:
{
_allocation.Free();
_capacity = other._count;
- const uint64 itemsCapacity = Math::Max(_capacity / sizeof(ItemType), 1);
+ const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -246,7 +256,7 @@ public:
return;
ASSERT(capacity >= 0);
const int32 count = preserveContents ? (_count < capacity ? _count : capacity) : 0;
- _allocation.Relocate(Math::Max(capacity / sizeof(ItemType), 1), _count / sizeof(ItemType), count / sizeof(ItemType));
+ _allocation.Relocate(ToItemCapacity(capacity), ToItemCount(_count), ToItemCount(count));
_capacity = capacity;
_count = count;
}
@@ -272,7 +282,7 @@ public:
{
if (_capacity < minCapacity)
{
- const int32 capacity = _allocation.CalculateCapacityGrow(Math::Max(_capacity / sizeof(ItemType), 1), minCapacity);
+ const int32 capacity = _allocation.CalculateCapacityGrow(ToItemCapacity(_capacity), minCapacity);
SetCapacity(capacity, preserveContents);
}
}
@@ -284,7 +294,7 @@ public:
void SetAll(const bool value)
{
if (_count != 0)
- Platform::MemorySet(_allocation.Get(), Math::Max(_count / sizeof(ItemType), 1), value ? MAX_int32 : 0);
+ Platform::MemorySet(_allocation.Get(), ToItemCount(_count) * sizeof(ItemType), value ? MAX_uint32 : 0);
}
///
diff --git a/Source/Engine/Core/Collections/ChunkedArray.h b/Source/Engine/Core/Collections/ChunkedArray.h
index 38bf92fb8..b3765a9fe 100644
--- a/Source/Engine/Core/Collections/ChunkedArray.h
+++ b/Source/Engine/Core/Collections/ChunkedArray.h
@@ -100,7 +100,7 @@ public:
int32 _chunkIndex;
int32 _index;
- Iterator(ChunkedArray const* collection, const int32 index)
+ Iterator(const ChunkedArray* collection, const int32 index)
: _collection(const_cast(collection))
, _chunkIndex(index / ChunkSize)
, _index(index % ChunkSize)
@@ -122,29 +122,29 @@ public:
{
}
- public:
- FORCE_INLINE ChunkedArray* GetChunkedArray() const
+ Iterator(Iterator&& i)
+ : _collection(i._collection)
+ , _chunkIndex(i._chunkIndex)
+ , _index(i._index)
{
- return _collection;
}
+ public:
FORCE_INLINE int32 Index() const
{
return _chunkIndex * ChunkSize + _index;
}
- public:
- bool IsEnd() const
+ FORCE_INLINE bool IsEnd() const
{
- return Index() == _collection->Count();
+ return (_chunkIndex * ChunkSize + _index) == _collection->_count;
}
- bool IsNotEnd() const
+ FORCE_INLINE bool IsNotEnd() const
{
- return Index() != _collection->Count();
+ return (_chunkIndex * ChunkSize + _index) != _collection->_count;
}
- public:
FORCE_INLINE T& operator*() const
{
return _collection->_chunks[_chunkIndex]->At(_index);
@@ -155,7 +155,6 @@ public:
return &_collection->_chunks[_chunkIndex]->At(_index);
}
- public:
FORCE_INLINE bool operator==(const Iterator& v) const
{
return _collection == v._collection && _chunkIndex == v._chunkIndex && _index == v._index;
@@ -166,17 +165,22 @@ public:
return _collection != v._collection || _chunkIndex != v._chunkIndex || _index != v._index;
}
- public:
+ Iterator& operator=(const Iterator& v)
+ {
+ _collection = v._collection;
+ _chunkIndex = v._chunkIndex;
+ _index = v._index;
+ return *this;
+ }
+
Iterator& operator++()
{
// Check if it is not at end
- const int32 end = _collection->Count();
- if (Index() != end)
+ if ((_chunkIndex * ChunkSize + _index) != _collection->_count)
{
// Move forward within chunk
_index++;
- // Check if need to change chunk
if (_index == ChunkSize && _chunkIndex < _collection->_chunks.Count() - 1)
{
// Move to next chunk
@@ -189,9 +193,9 @@ public:
Iterator operator++(int)
{
- Iterator temp = *this;
- ++temp;
- return temp;
+ Iterator i = *this;
+ ++i;
+ return i;
}
Iterator& operator--()
@@ -199,7 +203,6 @@ public:
// Check if it's not at beginning
if (_index != 0 || _chunkIndex != 0)
{
- // Check if need to change chunk
if (_index == 0)
{
// Move to previous chunk
@@ -217,9 +220,9 @@ public:
Iterator operator--(int)
{
- Iterator temp = *this;
- --temp;
- return temp;
+ Iterator i = *this;
+ --i;
+ return i;
}
};
@@ -294,7 +297,7 @@ public:
{
if (IsEmpty())
return;
- ASSERT(i.GetChunkedArray() == this);
+ ASSERT(i._collection == this);
ASSERT(i._chunkIndex < _chunks.Count() && i._index < ChunkSize);
ASSERT(i.Index() < Count());
@@ -432,11 +435,31 @@ public:
Iterator End() const
{
- return Iterator(this, Count());
+ return Iterator(this, _count);
}
Iterator IteratorAt(int32 index) const
{
return Iterator(this, index);
}
+
+ FORCE_INLINE Iterator begin()
+ {
+ return Iterator(this, 0);
+ }
+
+ FORCE_INLINE Iterator end()
+ {
+ return Iterator(this, _count);
+ }
+
+ FORCE_INLINE const Iterator begin() const
+ {
+ return Iterator(this, 0);
+ }
+
+ FORCE_INLINE const Iterator end() const
+ {
+ return Iterator(this, _count);
+ }
};
diff --git a/Source/Engine/Core/Collections/Config.h b/Source/Engine/Core/Collections/Config.h
index 792ae57c8..ce7656dcd 100644
--- a/Source/Engine/Core/Collections/Config.h
+++ b/Source/Engine/Core/Collections/Config.h
@@ -2,13 +2,26 @@
#pragma once
-///
-/// Default capacity for the dictionaries (amount of space for the elements)
-///
-#define DICTIONARY_DEFAULT_CAPACITY 256
+#include "Engine/Platform/Defines.h"
///
-/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size)
+/// Default capacity for the dictionaries (amount of space for the elements).
+///
+#ifndef DICTIONARY_DEFAULT_CAPACITY
+#if PLATFORM_DESKTOP
+#define DICTIONARY_DEFAULT_CAPACITY 256
+#else
+#define DICTIONARY_DEFAULT_CAPACITY 64
+#endif
+#endif
+
+///
+/// Default slack space divider for the dictionaries.
+///
+#define DICTIONARY_DEFAULT_SLACK_SCALE 3
+
+///
+/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size).
///
#define DICTIONARY_PROB_FUNC(size, numChecks) (numChecks)
//#define DICTIONARY_PROB_FUNC(size, numChecks) (1)
diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h
index 575863dc9..4d65a4123 100644
--- a/Source/Engine/Core/Collections/Dictionary.h
+++ b/Source/Engine/Core/Collections/Dictionary.h
@@ -40,7 +40,7 @@ public:
private:
State _state;
- void Free()
+ FORCE_INLINE void Free()
{
if (_state == Occupied)
{
@@ -50,7 +50,7 @@ public:
_state = Empty;
}
- void Delete()
+ FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Key);
@@ -58,7 +58,7 @@ public:
}
template
- void Occupy(const KeyComparableType& key)
+ FORCE_INLINE void Occupy(const KeyComparableType& key)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItem(&Value);
@@ -66,7 +66,7 @@ public:
}
template
- void Occupy(const KeyComparableType& key, const ValueType& value)
+ FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItems(&Value, &value, 1);
@@ -74,7 +74,7 @@ public:
}
template
- void Occupy(const KeyComparableType& key, ValueType&& value)
+ FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::MoveItems(&Value, &value, 1);
@@ -132,9 +132,6 @@ public:
///
/// The other collection to move.
Dictionary(Dictionary&& other) noexcept
- : _elementsCount(other._elementsCount)
- , _deletedCount(other._deletedCount)
- , _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
@@ -375,8 +372,12 @@ public:
template
ValueType& At(const KeyComparableType& key)
{
+ // Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
+ if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
+ Compact();
+
// Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + _deletedCount + 1);
+ EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
@@ -388,9 +389,9 @@ public:
// Insert
ASSERT(pos.FreeSlotIndex != -1);
+ _elementsCount++;
Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex];
bucket.Occupy(key);
- _elementsCount++;
return bucket.Value;
}
@@ -493,7 +494,7 @@ public:
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
if (i->Value)
- Delete(i->Value);
+ ::Delete(i->Value);
}
Clear();
}
@@ -533,13 +534,22 @@ public:
}
_size = capacity;
Bucket* oldData = oldAllocation.Get();
- if (oldElementsCount != 0 && preserveContents)
+ if (oldElementsCount != 0 && capacity != 0 && preserveContents)
{
- // TODO; move keys and values on realloc
+ FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
- if (oldData[i].IsOccupied())
- Add(oldData[i].Key, MoveTemp(oldData[i].Value));
+ Bucket& oldBucket = oldData[i];
+ if (oldBucket.IsOccupied())
+ {
+ FindPosition(oldBucket.Key, pos);
+ ASSERT(pos.FreeSlotIndex != -1);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
+ Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
+ bucket->_state = Bucket::Occupied;
+ _elementsCount++;
+ }
}
}
if (oldElementsCount != 0)
@@ -558,9 +568,9 @@ public:
{
if (_size >= minCapacity)
return;
- if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
- minCapacity = DICTIONARY_DEFAULT_CAPACITY;
- const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
+ int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
+ if (capacity < DICTIONARY_DEFAULT_CAPACITY)
+ capacity = DICTIONARY_DEFAULT_CAPACITY;
SetCapacity(capacity, preserveContents);
}
@@ -584,24 +594,10 @@ public:
/// The value.
/// Weak reference to the stored bucket.
template
- Bucket* Add(const KeyComparableType& key, const ValueType& value)
+ FORCE_INLINE Bucket* Add(const KeyComparableType& key, const ValueType& value)
{
- // Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + _deletedCount + 1);
-
- // Find location of the item or place to insert it
- FindPositionResult pos;
- FindPosition(key, pos);
-
- // Ensure key is unknown
- ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
-
- // Insert
- ASSERT(pos.FreeSlotIndex != -1);
- Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Bucket* bucket = OnAdd(key);
bucket->Occupy(key, value);
- _elementsCount++;
-
return bucket;
}
@@ -612,24 +608,10 @@ public:
/// The value.
/// Weak reference to the stored bucket.
template
- Bucket* Add(const KeyComparableType& key, ValueType&& value)
+ FORCE_INLINE Bucket* Add(const KeyComparableType& key, ValueType&& value)
{
- // Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + _deletedCount + 1);
-
- // Find location of the item or place to insert it
- FindPositionResult pos;
- FindPosition(key, pos);
-
- // Ensure key is unknown
- ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
-
- // Insert
- ASSERT(pos.FreeSlotIndex != -1);
- Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Bucket* bucket = OnAdd(key);
bucket->Occupy(key, MoveTemp(value));
- _elementsCount++;
-
return bucket;
}
@@ -851,7 +833,7 @@ public:
return Iterator(this, _size);
}
-protected:
+private:
///
/// The result container of the dictionary item lookup searching.
///
@@ -911,4 +893,66 @@ protected:
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos;
}
+
+ template
+ Bucket* OnAdd(const KeyComparableType& key)
+ {
+ // Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
+ if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
+ Compact();
+
+ // Ensure to have enough memory for the next item (in case of new element insertion)
+ EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
+
+ // Find location of the item or place to insert it
+ FindPositionResult pos;
+ FindPosition(key, pos);
+
+ // Ensure key is unknown
+ ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
+
+ // Insert
+ ASSERT(pos.FreeSlotIndex != -1);
+ _elementsCount++;
+ return &_allocation.Get()[pos.FreeSlotIndex];
+ }
+
+ void Compact()
+ {
+ if (_elementsCount == 0)
+ {
+ // Fast path if it's empty
+ Bucket* data = _allocation.Get();
+ for (int32 i = 0; i < _size; i++)
+ data[i]._state = Bucket::Empty;
+ }
+ else
+ {
+ // Rebuild entire table completely
+ AllocationData oldAllocation;
+ oldAllocation.Swap(_allocation);
+ _allocation.Allocate(_size);
+ Bucket* data = _allocation.Get();
+ for (int32 i = 0; i < _size; i++)
+ data[i]._state = Bucket::Empty;
+ Bucket* oldData = oldAllocation.Get();
+ FindPositionResult pos;
+ for (int32 i = 0; i < _size; i++)
+ {
+ Bucket& oldBucket = oldData[i];
+ if (oldBucket.IsOccupied())
+ {
+ FindPosition(oldBucket.Key, pos);
+ ASSERT(pos.FreeSlotIndex != -1);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
+ Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
+ bucket->_state = Bucket::Occupied;
+ }
+ }
+ for (int32 i = 0; i < _size; i++)
+ oldData[i].Free();
+ }
+ _deletedCount = 0;
+ }
};
diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h
index 107e42e65..4a4f3e924 100644
--- a/Source/Engine/Core/Collections/HashSet.h
+++ b/Source/Engine/Core/Collections/HashSet.h
@@ -37,26 +37,33 @@ public:
private:
State _state;
- void Free()
+ FORCE_INLINE void Free()
{
if (_state == Occupied)
Memory::DestructItem(&Item);
_state = Empty;
}
- void Delete()
+ FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Item);
}
template
- void Occupy(const ItemType& item)
+ FORCE_INLINE void Occupy(const ItemType& item)
{
Memory::ConstructItems(&Item, &item, 1);
_state = Occupied;
}
+ template
+ FORCE_INLINE void Occupy(ItemType& item)
+ {
+ Memory::MoveItems(&Item, &item, 1);
+ _state = Occupied;
+ }
+
FORCE_INLINE bool IsEmpty() const
{
return _state == Empty;
@@ -108,9 +115,6 @@ public:
///
/// The other collection to move.
HashSet(HashSet&& other) noexcept
- : _elementsCount(other._elementsCount)
- , _deletedCount(other._deletedCount)
- , _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
@@ -169,7 +173,7 @@ public:
///
~HashSet()
{
- SetCapacity(0, false);
+ Clear();
}
public:
@@ -216,6 +220,7 @@ public:
HashSet* _collection;
int32 _index;
+ public:
Iterator(HashSet* collection, const int32 index)
: _collection(collection)
, _index(index)
@@ -228,7 +233,12 @@ public:
{
}
- public:
+ Iterator()
+ : _collection(nullptr)
+ , _index(-1)
+ {
+ }
+
Iterator(const Iterator& i)
: _collection(i._collection)
, _index(i._index)
@@ -242,6 +252,11 @@ public:
}
public:
+ FORCE_INLINE int32 Index() const
+ {
+ return _index;
+ }
+
FORCE_INLINE bool IsEnd() const
{
return _index == _collection->_size;
@@ -398,13 +413,21 @@ public:
}
_size = capacity;
Bucket* oldData = oldAllocation.Get();
- if (oldElementsCount != 0 && preserveContents)
+ if (oldElementsCount != 0 && capacity != 0 && preserveContents)
{
- // TODO; move keys and values on realloc
+ FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
- if (oldData[i].IsOccupied())
- Add(oldData[i].Item);
+ Bucket& oldBucket = oldData[i];
+ if (oldBucket.IsOccupied())
+ {
+ FindPosition(oldBucket.Item, pos);
+ ASSERT(pos.FreeSlotIndex != -1);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
+ bucket->_state = Bucket::Occupied;
+ _elementsCount++;
+ }
}
}
if (oldElementsCount != 0)
@@ -421,14 +444,26 @@ public:
/// True if preserve collection data when changing its size, otherwise collection after resize will be empty.
void EnsureCapacity(int32 minCapacity, bool preserveContents = true)
{
- if (Capacity() >= minCapacity)
+ if (_size >= minCapacity)
return;
- if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
- minCapacity = DICTIONARY_DEFAULT_CAPACITY;
- const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
+ int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
+ if (capacity < DICTIONARY_DEFAULT_CAPACITY)
+ capacity = DICTIONARY_DEFAULT_CAPACITY;
SetCapacity(capacity, preserveContents);
}
+ ///
+ /// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
+ ///
+ /// The other collection.
+ void Swap(HashSet& other)
+ {
+ ::Swap(_elementsCount, other._elementsCount);
+ ::Swap(_deletedCount, other._deletedCount);
+ ::Swap(_size, other._size);
+ _allocation.Swap(other._allocation);
+ }
+
public:
///
/// Add element to the collection.
@@ -438,24 +473,23 @@ public:
template
bool Add(const ItemType& item)
{
- // Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + _deletedCount + 1);
+ Bucket* bucket = OnAdd(item);
+ if (bucket)
+ bucket->Occupy(item);
+ return bucket != nullptr;
+ }
- // Find location of the item or place to insert it
- FindPositionResult pos;
- FindPosition(item, pos);
-
- // Check if object has been already added
- if (pos.ObjectIndex != -1)
- return false;
-
- // Insert
- ASSERT(pos.FreeSlotIndex != -1);
- Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
- bucket->Occupy(item);
- _elementsCount++;
-
- return true;
+ ///
+ /// Add element to the collection.
+ ///
+ /// The element to add to the set.
+ /// True if element has been added to the collection, otherwise false if the element is already present.
+ bool Add(T&& item)
+ {
+ Bucket* bucket = OnAdd(item);
+ if (bucket)
+ bucket->Occupy(MoveTemp(item));
+ return bucket != nullptr;
}
///
@@ -593,7 +627,7 @@ public:
return Iterator(this, _size);
}
-protected:
+private:
///
/// The result container of the set item lookup searching.
///
@@ -654,4 +688,66 @@ protected:
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos;
}
+
+ template
+ Bucket* OnAdd(const ItemType& key)
+ {
+ // Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
+ if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
+ Compact();
+
+ // Ensure to have enough memory for the next item (in case of new element insertion)
+ EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
+
+ // Find location of the item or place to insert it
+ FindPositionResult pos;
+ FindPosition(key, pos);
+
+ // Check if object has been already added
+ if (pos.ObjectIndex != -1)
+ return nullptr;
+
+ // Insert
+ ASSERT(pos.FreeSlotIndex != -1);
+ _elementsCount++;
+ return &_allocation.Get()[pos.FreeSlotIndex];
+ }
+
+ void Compact()
+ {
+ if (_elementsCount == 0)
+ {
+ // Fast path if it's empty
+ Bucket* data = _allocation.Get();
+ for (int32 i = 0; i < _size; i++)
+ data[i]._state = Bucket::Empty;
+ }
+ else
+ {
+ // Rebuild entire table completely
+ AllocationData oldAllocation;
+ oldAllocation.Swap(_allocation);
+ _allocation.Allocate(_size);
+ Bucket* data = _allocation.Get();
+ for (int32 i = 0; i < _size; i++)
+ data[i]._state = Bucket::Empty;
+ Bucket* oldData = oldAllocation.Get();
+ FindPositionResult pos;
+ for (int32 i = 0; i < _size; i++)
+ {
+ Bucket& oldBucket = oldData[i];
+ if (oldBucket.IsOccupied())
+ {
+ FindPosition(oldBucket.Item, pos);
+ ASSERT(pos.FreeSlotIndex != -1);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
+ bucket->_state = Bucket::Occupied;
+ }
+ }
+ for (int32 i = 0; i < _size; i++)
+ oldData[i].Free();
+ }
+ _deletedCount = 0;
+ }
};
diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h
index 318c304e0..3232c6b02 100644
--- a/Source/Engine/Core/Config/BuildSettings.h
+++ b/Source/Engine/Core/Config/BuildSettings.h
@@ -4,6 +4,7 @@
#include "Engine/Core/Config/Settings.h"
#include "Engine/Serialization/Serialization.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Content/Asset.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Content/SceneReference.h"
@@ -76,6 +77,12 @@ public:
API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Content\")")
bool ShadersGenerateDebugData = false;
+ ///
+ /// If checked, skips bundling default engine fonts for UI. Use if to reduce build size if you don't use default engine fonts but custom ones only.
+ ///
+ API_FIELD(Attributes="EditorOrder(2100), EditorDisplay(\"Content\")")
+ bool SkipDefaultFonts = false;
+
///
/// If checked, .NET Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS.
///
@@ -106,6 +113,7 @@ public:
DESERIALIZE(AdditionalAssetFolders);
DESERIALIZE(ShadersNoOptimize);
DESERIALIZE(ShadersGenerateDebugData);
+ DESERIALIZE(SkipDefaultFonts);
DESERIALIZE(SkipDotnetPackaging);
DESERIALIZE(SkipUnusedDotnetLibsPackaging);
}
diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h
index 4e081faef..f0f8fdef0 100644
--- a/Source/Engine/Core/Delegate.h
+++ b/Source/Engine/Core/Delegate.h
@@ -226,7 +226,7 @@ public:
/// Function result
FORCE_INLINE ReturnType operator()(Params... params) const
{
- ASSERT(_function);
+ ASSERT_LOW_LAYER(_function);
return _function(_callee, Forward(params)...);
}
@@ -289,8 +289,13 @@ protected:
intptr volatile _ptr = 0;
intptr volatile _size = 0;
#else
- HashSet* _functions = nullptr;
- CriticalSection* _locker = nullptr;
+ struct Data
+ {
+ HashSet Functions;
+ CriticalSection Locker;
+ };
+ // Holds pointer to Data with Functions and Locker. Thread-safe access via atomic operations.
+ intptr volatile _data = 0;
#endif
typedef void (*StubSignature)(void*, Params...);
@@ -314,15 +319,12 @@ public:
_ptr = (intptr)newBindings;
_size = newSize;
#else
- if (other._functions == nullptr)
+ Data* otherData = (Data*)Platform::AtomicRead(&_data);
+ if (otherData == nullptr)
return;
- _functions = New>(*other._functions);
- for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
- {
- if (i->Item._function && i->Item._lambda)
- i->Item.LambdaCtor();
- }
- _locker = other._locker;
+ ScopeLock lock(otherData->Locker);
+ for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
+ Bind(i->Item);
#endif
}
@@ -334,10 +336,8 @@ public:
other._ptr = 0;
other._size = 0;
#else
- _functions = other._functions;
- _locker = other._locker;
- other._functions = nullptr;
- other._locker = nullptr;
+ _data = other._data;
+ other._data = 0;
#endif
}
@@ -356,20 +356,11 @@ public:
Allocator::Free((void*)_ptr);
}
#else
- if (_locker != nullptr)
+ Data* data = (Data*)_data;
+ if (data)
{
- Allocator::Free(_locker);
- _locker = nullptr;
- }
- if (_functions != nullptr)
- {
- for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
- {
- if (i->Item._lambda)
- i->Item.LambdaCtor();
- }
- Allocator::Free(_functions);
- _functions = nullptr;
+ _data = 0;
+ Delete(data);
}
#endif
}
@@ -385,8 +376,13 @@ public:
for (intptr i = 0; i < size; i++)
Bind(bindings[i]);
#else
- for (auto i = other._functions->Begin(); i.IsNotEnd(); ++i)
- Bind(i->Item);
+ Data* otherData = (Data*)Platform::AtomicRead(&_data);
+ if (otherData != nullptr)
+ {
+ ScopeLock lock(otherData->Locker);
+ for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
+ Bind(i->Item);
+ }
#endif
}
return *this;
@@ -402,10 +398,8 @@ public:
other._ptr = 0;
other._size = 0;
#else
- _functions = other._functions;
- _locker = other._locker;
- other._functions = nullptr;
- other._locker = nullptr;
+ _data = other._data;
+ other._data = 0;
#endif
}
return *this;
@@ -507,12 +501,20 @@ public:
Allocator::Free(bindings);
}
#else
- if (_locker == nullptr)
- _locker = New();
- ScopeLock lock(*_locker);
- if (_functions == nullptr)
- _functions = New>(32);
- _functions->Add(f);
+ Data* data = (Data*)Platform::AtomicRead(&_data);
+ while (!data)
+ {
+ Data* newData = New();
+ Data* oldData = (Data*)Platform::InterlockedCompareExchange(&_data, (intptr)newData, (intptr)data);
+ if (oldData != data)
+ {
+ // Other thread already set the new data so free it and try again
+ Delete(newData);
+ }
+ data = (Data*)Platform::AtomicRead(&_data);
+ }
+ ScopeLock lock(data->Locker);
+ data->Functions.Add(f);
#endif
}
@@ -568,13 +570,22 @@ public:
}
}
#else
- if (_locker == nullptr)
- _locker = New();
- ScopeLock lock(*_locker);
- if (_functions && _functions->Contains(f))
- return;
+ Data* data = (Data*)Platform::AtomicRead(&_data);
+ if (data)
+ {
+ data->Locker.Lock();
+ if (data->Functions.Contains(f))
+ {
+ data->Locker.Unlock();
+ return;
+ }
+ }
#endif
Bind(f);
+#if !DELEGATE_USE_ATOMIC
+ if (data)
+ data->Locker.Unlock();
+#endif
}
///
@@ -583,18 +594,9 @@ public:
template
void Unbind()
{
-#if DELEGATE_USE_ATOMIC
FunctionType f;
f.template Bind();
Unbind(f);
-#else
- if (_functions == nullptr)
- return;
- FunctionType f;
- f.template Bind();
- ScopeLock lock(*_locker);
- _functions->Remove(f);
-#endif
}
///
@@ -604,18 +606,9 @@ public:
template
void Unbind(T* callee)
{
-#if DELEGATE_USE_ATOMIC
FunctionType f;
f.template Bind(callee);
Unbind(f);
-#else
- if (_functions == nullptr)
- return;
- FunctionType f;
- f.template Bind(callee);
- ScopeLock lock(*_locker);
- _functions->Remove(f);
-#endif
}
///
@@ -624,16 +617,8 @@ public:
/// The method.
void Unbind(Signature method)
{
-#if DELEGATE_USE_ATOMIC
FunctionType f(method);
Unbind(f);
-#else
- if (_functions == nullptr)
- return;
- FunctionType f(method);
- ScopeLock lock(*_locker);
- _functions->Remove(f);
-#endif
}
///
@@ -666,10 +651,11 @@ public:
Unbind(f);
}
#else
- if (_functions == nullptr)
+ Data* data = (Data*)Platform::AtomicRead(&_data);
+ if (!data)
return;
- ScopeLock lock(*_locker);
- _functions->Remove(f);
+ ScopeLock lock(data->Locker);
+ data->Functions.Remove(f);
#endif
}
@@ -692,15 +678,11 @@ public:
Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, 0);
}
#else
- if (_functions == nullptr)
+ Data* data = (Data*)Platform::AtomicRead(&_data);
+ if (!data)
return;
- ScopeLock lock(*_locker);
- for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
- {
- if (i->Item._lambda)
- i->Item.LambdaDtor();
- }
- _functions->Clear();
+ ScopeLock lock(data->Locker);
+ data->Functions.Clear();
#endif
}
@@ -710,22 +692,24 @@ public:
/// The bound functions count.
int32 Count() const
{
+ int32 result = 0;
#if DELEGATE_USE_ATOMIC
- int32 count = 0;
const intptr size = Platform::AtomicRead((intptr volatile*)&_size);
FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr);
for (intptr i = 0; i < size; i++)
{
if (Platform::AtomicRead((intptr volatile*)&bindings[i]._function) != 0)
- count++;
+ result++;
}
- return count;
#else
- if (_functions == nullptr)
- return 0;
- ScopeLock lock(*_locker);
- return _functions->Count();
+ Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
+ if (data)
+ {
+ ScopeLock lock(data->Locker);
+ result = data->Functions.Count();
+ }
#endif
+ return result;
}
///
@@ -736,10 +720,14 @@ public:
#if DELEGATE_USE_ATOMIC
return (int32)Platform::AtomicRead((intptr volatile*)&_size);
#else
- if (_functions == nullptr)
- return 0;
- ScopeLock lock(*_locker);
- return _functions->Capacity();
+ int32 result = 0;
+ Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
+ if (data)
+ {
+ ScopeLock lock(data->Locker);
+ result = data->Functions.Capacity();
+ }
+ return result;
#endif
}
@@ -759,10 +747,14 @@ public:
}
return false;
#else
- if (_functions == nullptr)
- return false;
- ScopeLock lock(*_locker);
- return _functions->Count() > 0;
+ bool result = false;
+ Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
+ if (data)
+ {
+ ScopeLock lock(data->Locker);
+ result = data->Functions.Count() != 0;
+ }
+ return result;
#endif
}
@@ -791,18 +783,13 @@ public:
}
}
#else
- if (_functions == nullptr)
- return 0;
- ScopeLock lock(*_locker);
- for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
+ Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
+ if (data)
{
- if (i->Item._function != nullptr)
+ ScopeLock lock(data->Locker);
+ for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
{
- buffer[count]._function = (StubSignature)i->Item._function;
- buffer[count]._callee = (void*)i->Item._callee;
- buffer[count]._lambda = (typename FunctionType::Lambda*)i->Item._lambda;
- if (buffer[count]._lambda)
- buffer[count].LambdaCtor();
+ new(buffer + count) FunctionType((const FunctionType&)i->Item);
count++;
}
}
@@ -828,15 +815,15 @@ public:
++bindings;
}
#else
- if (_functions == nullptr)
+ Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
+ if (!data)
return;
- ScopeLock lock(*_locker);
- for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
+ ScopeLock lock(data->Locker);
+ for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
{
- auto function = (StubSignature)(i->Item._function);
- auto callee = (void*)(i->Item._callee);
- if (function != nullptr)
- function(callee, Forward(params)...);
+ const FunctionType& item = i->Item;
+ ASSERT_LOW_LAYER(item._function);
+ item._function(item._callee, Forward(params)...);
}
#endif
}
diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp
index e3d97e149..bb8d42aba 100644
--- a/Source/Engine/Core/Log.cpp
+++ b/Source/Engine/Core/Log.cpp
@@ -3,16 +3,15 @@
#include "Log.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Core/Types/DateTime.h"
-#include "Engine/Core/Collections/Sorting.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/CriticalSection.h"
#include "Engine/Serialization/FileWriteStream.h"
-#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Debug/Exceptions/Exceptions.h"
#if USE_EDITOR
-#include "Engine/Core/Collections/Array.h"
+#include "Engine/Core/Collections/Sorting.h"
#endif
#include
@@ -199,35 +198,36 @@ void Log::Logger::WriteFloor()
void Log::Logger::ProcessLogMessage(LogType type, const StringView& msg, fmt_flax::memory_buffer& w)
{
const TimeSpan time = DateTime::Now() - LogStartTime;
+ const int32 msgLength = msg.Length();
fmt_flax::format(w, TEXT("[ {0} ]: [{1}] "), *time.ToString('a'), ToString(type));
// On Windows convert all '\n' into '\r\n'
#if PLATFORM_WINDOWS
- const int32 msgLength = msg.Length();
- if (msgLength > 1)
+ bool hasWindowsNewLine = false;
+ for (int32 i = 1; i < msgLength && !hasWindowsNewLine; i++)
+ hasWindowsNewLine |= msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n';
+ if (hasWindowsNewLine)
{
- MemoryWriteStream msgStream(msgLength * sizeof(Char));
- msgStream.WriteChar(msg[0]);
+ Array msgStream;
+ msgStream.EnsureCapacity(msgLength);
+ msgStream.Add(msg.Get()[0]);
for (int32 i = 1; i < msgLength; i++)
{
- if (msg[i - 1] != '\r' && msg[i] == '\n')
- msgStream.WriteChar(TEXT('\r'));
- msgStream.WriteChar(msg[i]);
+ if (msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n')
+ msgStream.Add(TEXT('\r'));
+ msgStream.Add(msg.Get()[i]);
}
- msgStream.WriteChar(msg[msgLength]);
- msgStream.WriteChar(TEXT('\0'));
- fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.GetHandle());
- //w.append(msgStream.GetHandle(), msgStream.GetHandle() + msgStream.GetPosition());
+ msgStream.Add(TEXT('\0'));
+ w.append(msgStream.Get(), (const Char*)(msgStream.Get() + msgStream.Count()));
+ //fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.Get());
+ return;
}
- else
- {
- //w.append(msg.Get(), msg.Get() + msg.Length());
- fmt_flax::format(w, TEXT("{}"), msg);
- }
-#else
- fmt_flax::format(w, TEXT("{}"), msg);
#endif
+
+ // Output raw message to the log
+ w.append(msg.Get(), msg.Get() + msg.Length());
+ //fmt_flax::format(w, TEXT("{}"), msg);
}
void Log::Logger::Write(LogType type, const StringView& msg)
diff --git a/Source/Engine/Core/Math/Quaternion.cpp b/Source/Engine/Core/Math/Quaternion.cpp
index bdbe3037d..1ca05a9c3 100644
--- a/Source/Engine/Core/Math/Quaternion.cpp
+++ b/Source/Engine/Core/Math/Quaternion.cpp
@@ -382,9 +382,8 @@ void Quaternion::GetRotationFromTo(const Float3& from, const Float3& to, Quatern
v0.Normalize();
v1.Normalize();
- const float d = Float3::Dot(v0, v1);
-
// If dot == 1, vectors are the same
+ const float d = Float3::Dot(v0, v1);
if (d >= 1.0f)
{
result = Identity;
diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs
index ff50a98bf..34ee160f0 100644
--- a/Source/Engine/Core/Math/Quaternion.cs
+++ b/Source/Engine/Core/Math/Quaternion.cs
@@ -1077,6 +1077,115 @@ namespace FlaxEngine
}
}
+ ///
+ /// Gets the shortest arc quaternion to rotate this vector to the destination vector.
+ ///
+ /// The source vector.
+ /// The destination vector.
+ /// The result.
+ /// The fallback axis.
+ public static void GetRotationFromTo(ref Float3 from, ref Float3 to, out Quaternion result, ref Float3 fallbackAxis)
+ {
+ // Based on Stan Melax's article in Game Programming Gems
+
+ Float3 v0 = from;
+ Float3 v1 = to;
+ v0.Normalize();
+ v1.Normalize();
+
+ // If dot == 1, vectors are the same
+ float d = Float3.Dot(ref v0, ref v1);
+ if (d >= 1.0f)
+ {
+ result = Identity;
+ return;
+ }
+
+ if (d < 1e-6f - 1.0f)
+ {
+ if (fallbackAxis != Float3.Zero)
+ {
+ // Rotate 180 degrees about the fallback axis
+ RotationAxis(ref fallbackAxis, Mathf.Pi, out result);
+ }
+ else
+ {
+ // Generate an axis
+ Float3 axis = Float3.Cross(Float3.UnitX, from);
+ if (axis.LengthSquared < Mathf.Epsilon) // Pick another if colinear
+ axis = Float3.Cross(Float3.UnitY, from);
+ axis.Normalize();
+ RotationAxis(ref axis, Mathf.Pi, out result);
+ }
+ }
+ else
+ {
+ float s = Mathf.Sqrt((1 + d) * 2);
+ float invS = 1 / s;
+ Float3.Cross(ref v0, ref v1, out var c);
+ result.X = c.X * invS;
+ result.Y = c.Y * invS;
+ result.Z = c.Z * invS;
+ result.W = s * 0.5f;
+ result.Normalize();
+ }
+ }
+
+ ///
+ /// Gets the shortest arc quaternion to rotate this vector to the destination vector.
+ ///
+ /// The source vector.
+ /// The destination vector.
+ /// The fallback axis.
+ /// The rotation.
+ public static Quaternion GetRotationFromTo(Float3 from, Float3 to, Float3 fallbackAxis)
+ {
+ GetRotationFromTo(ref from, ref to, out var result, ref fallbackAxis);
+ return result;
+ }
+
+ ///
+ /// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
+ ///
+ /// The source vector.
+ /// The destination vector.
+ /// The result.
+ public static void FindBetween(ref Float3 from, ref Float3 to, out Quaternion result)
+ {
+ // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final
+ float normFromNormTo = Mathf.Sqrt(from.LengthSquared * to.LengthSquared);
+ if (normFromNormTo < Mathf.Epsilon)
+ {
+ result = Identity;
+ return;
+ }
+ float w = normFromNormTo + Float3.Dot(from, to);
+ if (w < 1.0-6f * normFromNormTo)
+ {
+ result = Mathf.Abs(from.X) > Mathf.Abs(from.Z)
+ ? new Quaternion(-from.Y, from.X, 0.0f, 0.0f)
+ : new Quaternion(0.0f, -from.Z, from.Y, 0.0f);
+ }
+ else
+ {
+ Float3 cross = Float3.Cross(from, to);
+ result = new Quaternion(cross.X, cross.Y, cross.Z, w);
+ }
+ result.Normalize();
+ }
+
+ ///
+ /// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
+ ///
+ /// The source vector.
+ /// The destination vector.
+ /// The rotation.
+ public static Quaternion FindBetween(Float3 from, Float3 to)
+ {
+ FindBetween(ref from, ref to, out var result);
+ return result;
+ }
+
///
/// Creates a left-handed spherical billboard that rotates around a specified object position.
///
diff --git a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
index afd34bbfd..65377acaa 100644
--- a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
@@ -13,9 +13,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
- {
return true;
- }
return base.CanConvertFrom(context, sourceType);
}
@@ -23,9 +21,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
- {
return false;
- }
return base.CanConvertTo(context, destinationType);
}
diff --git a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
index 4eebbfce4..e0670df05 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Double2Converter : TypeConverter
+ internal class Double2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
index 420e0016c..a66892ecb 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Double3Converter : TypeConverter
+ internal class Double3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
index fc1d9a7fe..d085217ef 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Double4Converter : TypeConverter
+ internal class Double4Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Double4(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture), double.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
index a41a0f4d5..4b2ffadf5 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Float2Converter : TypeConverter
+ internal class Float2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
index aded4117e..3739c44ef 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Float3Converter : TypeConverter
+ internal class Float3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
index 58c76ac65..620f2c838 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
@@ -7,15 +7,13 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Float4Converter : TypeConverter
+ internal class VectorConverter : TypeConverter
{
///
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
- {
return true;
- }
return base.CanConvertFrom(context, sourceType);
}
@@ -23,18 +21,32 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
- {
return false;
- }
return base.CanConvertTo(context, destinationType);
}
+ internal static string[] GetParts(string str)
+ {
+ string[] v = str.Split(',');
+ if (v.Length == 1)
+ {
+ // When converting from ToString()
+ v = str.Split(' ');
+ for (int i = 0; i < v.Length; i++)
+ v[i] = v[i].Substring(v[i].IndexOf(':') + 1);
+ }
+ return v;
+ }
+ }
+
+ internal class Float4Converter : VectorConverter
+ {
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Float4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
index c4989c085..f528aa46b 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Int2Converter : TypeConverter
+ internal class Int2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
index fe01f91fd..520f806d0 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Int3Converter : TypeConverter
+ internal class Int3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
index 2ce0fc202..e9a27dfda 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Int4Converter : TypeConverter
+ internal class Int4Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Int4(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture), int.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
index 23bb901be..5d9aa206b 100644
--- a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class QuaternionConverter : TypeConverter
+ internal class QuaternionConverter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Quaternion(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
index 96d6beadc..acb5b5817 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Vector2Converter : TypeConverter
+ internal class Vector2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
index 23ee4df11..66ec831f0 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Vector3Converter : TypeConverter
+ internal class Vector3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
index c3b4d074b..f4781f45b 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Vector4Converter : TypeConverter
+ internal class Vector4Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Vector4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/Vector2.h b/Source/Engine/Core/Math/Vector2.h
index 83deb009a..cea03ec10 100644
--- a/Source/Engine/Core/Math/Vector2.h
+++ b/Source/Engine/Core/Math/Vector2.h
@@ -646,6 +646,12 @@ inline Vector2Base operator/(typename TOtherFloat::Type a, const Vector2Ba
return Vector2Base(a) / b;
}
+template
+inline uint32 GetHash(const Vector2Base& key)
+{
+ return (*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y;
+}
+
namespace Math
{
template
diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h
index c0ebdf01d..01b55e9bd 100644
--- a/Source/Engine/Core/Math/Vector3.h
+++ b/Source/Engine/Core/Math/Vector3.h
@@ -977,6 +977,12 @@ inline Vector3Base operator/(typename TOtherFloat::Type a, const Vector3Ba
return Vector3Base(a) / b;
}
+template
+inline uint32 GetHash(const Vector3Base& key)
+{
+ return (((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z;
+}
+
namespace Math
{
template
diff --git a/Source/Engine/Core/Math/Vector4.h b/Source/Engine/Core/Math/Vector4.h
index 5c7b24c4a..1cc6d4db8 100644
--- a/Source/Engine/Core/Math/Vector4.h
+++ b/Source/Engine/Core/Math/Vector4.h
@@ -552,6 +552,12 @@ inline Vector4Base operator/(typename TOtherFloat::Type a, const Vector4Ba
return Vector4Base(a) / b;
}
+template
+inline uint32 GetHash(const Vector4Base& key)
+{
+ return (((((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z) * 397) ^*(uint32*)&key.W;
+}
+
namespace Math
{
template
diff --git a/Source/Engine/Core/Memory/Allocation.h b/Source/Engine/Core/Memory/Allocation.h
index 89d2f2003..8d7188d91 100644
--- a/Source/Engine/Core/Memory/Allocation.h
+++ b/Source/Engine/Core/Memory/Allocation.h
@@ -43,14 +43,14 @@ public:
return Capacity;
}
- FORCE_INLINE void Allocate(uint64 capacity)
+ FORCE_INLINE void Allocate(int32 capacity)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(capacity <= Capacity);
#endif
}
- FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
+ FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(capacity <= Capacity);
@@ -120,12 +120,15 @@ public:
capacity |= capacity >> 4;
capacity |= capacity >> 8;
capacity |= capacity >> 16;
- capacity = (capacity + 1) * 2;
+ uint64 capacity64 = (uint64)(capacity + 1) * 2;
+ if (capacity64 > MAX_int32)
+ capacity64 = MAX_int32;
+ capacity = (int32)capacity64;
}
return capacity;
}
- FORCE_INLINE void Allocate(uint64 capacity)
+ FORCE_INLINE void Allocate(int32 capacity)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(!_data);
@@ -137,7 +140,7 @@ public:
#endif
}
- FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
+ FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
T* newData = capacity != 0 ? (T*)Allocator::Allocate(capacity * sizeof(T)) : nullptr;
#if !BUILD_RELEASE
@@ -210,7 +213,7 @@ public:
return minCapacity <= Capacity ? Capacity : _other.CalculateCapacityGrow(capacity, minCapacity);
}
- FORCE_INLINE void Allocate(uint64 capacity)
+ FORCE_INLINE void Allocate(int32 capacity)
{
if (capacity > Capacity)
{
@@ -219,7 +222,7 @@ public:
}
}
- FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
+ FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
// Check if the new allocation will fit into inlined storage
if (capacity <= Capacity)
diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp
index a020f7844..a185ce8b3 100644
--- a/Source/Engine/Core/ObjectsRemovalService.cpp
+++ b/Source/Engine/Core/ObjectsRemovalService.cpp
@@ -5,6 +5,7 @@
#include "Collections/Dictionary.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
+#include "Engine/Platform/CriticalSection.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ScriptingObject.h"
diff --git a/Source/Engine/Core/Types/Span.h b/Source/Engine/Core/Types/Span.h
index e0518ec77..b51c858ec 100644
--- a/Source/Engine/Core/Types/Span.h
+++ b/Source/Engine/Core/Types/Span.h
@@ -107,6 +107,26 @@ public:
ASSERT(index >= 0 && index < _length);
return _data[index];
}
+
+ FORCE_INLINE T* begin()
+ {
+ return _data;
+ }
+
+ FORCE_INLINE T* end()
+ {
+ return _data + _length;
+ }
+
+ FORCE_INLINE const T* begin() const
+ {
+ return _data;
+ }
+
+ FORCE_INLINE const T* end() const
+ {
+ return _data + _length;
+ }
};
template
diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h
index 27c63c999..3e2bbd5f3 100644
--- a/Source/Engine/Core/Types/StringView.h
+++ b/Source/Engine/Core/Types/StringView.h
@@ -327,6 +327,18 @@ public:
bool operator!=(const String& other) const;
public:
+ using StringViewBase::StartsWith;
+ FORCE_INLINE bool StartsWith(const StringView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
+ {
+ return StringViewBase::StartsWith(prefix, searchCase);
+ }
+
+ using StringViewBase::EndsWith;
+ FORCE_INLINE bool EndsWith(const StringView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
+ {
+ return StringViewBase::EndsWith(suffix, searchCase);
+ }
+
///
/// Gets the left most given number of characters.
///
@@ -511,6 +523,18 @@ public:
bool operator!=(const StringAnsi& other) const;
public:
+ using StringViewBase::StartsWith;
+ FORCE_INLINE bool StartsWith(const StringAnsiView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
+ {
+ return StringViewBase::StartsWith(prefix, searchCase);
+ }
+
+ using StringViewBase::EndsWith;
+ FORCE_INLINE bool EndsWith(const StringAnsiView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
+ {
+ return StringViewBase::EndsWith(suffix, searchCase);
+ }
+
///
/// Retrieves substring created from characters starting from startIndex to the String end.
///
diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp
index df044b299..952e648e6 100644
--- a/Source/Engine/Core/Types/Variant.cpp
+++ b/Source/Engine/Core/Types/Variant.cpp
@@ -2821,7 +2821,10 @@ void Variant::Inline()
type = VariantType::Types::Vector4;
}
if (type != VariantType::Null)
+ {
+ ASSERT(sizeof(data) >= AsBlob.Length);
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
+ }
}
if (type != VariantType::Null)
{
@@ -2912,6 +2915,60 @@ void Variant::Inline()
}
}
+void Variant::InvertInline()
+{
+ byte data[sizeof(Matrix)];
+ switch (Type.Type)
+ {
+ case VariantType::Bool:
+ case VariantType::Int:
+ case VariantType::Uint:
+ case VariantType::Int64:
+ case VariantType::Uint64:
+ case VariantType::Float:
+ case VariantType::Double:
+ case VariantType::Pointer:
+ case VariantType::String:
+ case VariantType::Float2:
+ case VariantType::Float3:
+ case VariantType::Float4:
+ case VariantType::Color:
+#if !USE_LARGE_WORLDS
+ case VariantType::BoundingSphere:
+ case VariantType::BoundingBox:
+ case VariantType::Ray:
+#endif
+ case VariantType::Guid:
+ case VariantType::Quaternion:
+ case VariantType::Rectangle:
+ case VariantType::Int2:
+ case VariantType::Int3:
+ case VariantType::Int4:
+ case VariantType::Int16:
+ case VariantType::Uint16:
+ case VariantType::Double2:
+ case VariantType::Double3:
+ case VariantType::Double4:
+ static_assert(sizeof(data) >= sizeof(AsData), "Invalid memory size.");
+ Platform::MemoryCopy(data, AsData, sizeof(AsData));
+ break;
+#if USE_LARGE_WORLDS
+ case VariantType::BoundingSphere:
+ case VariantType::BoundingBox:
+ case VariantType::Ray:
+#endif
+ case VariantType::Transform:
+ case VariantType::Matrix:
+ ASSERT(sizeof(data) >= AsBlob.Length);
+ Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
+ break;
+ default:
+ return; // Not used
+ }
+ SetType(VariantType(VariantType::Structure, InBuiltTypesTypeNames[Type.Type]));
+ CopyStructure(data);
+}
+
Variant Variant::NewValue(const StringAnsiView& typeName)
{
Variant v;
diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h
index 8cc1e133b..adb49249f 100644
--- a/Source/Engine/Core/Types/Variant.h
+++ b/Source/Engine/Core/Types/Variant.h
@@ -372,6 +372,9 @@ public:
// Inlines potential value type into in-built format (eg. Vector3 stored as Structure, or String stored as ManagedObject).
void Inline();
+ // Inverts the inlined value from in-built format into generic storage (eg. Float3 from inlined format into Structure).
+ void InvertInline();
+
// Allocates the Variant of the specific type (eg. structure or object or value).
static Variant NewValue(const StringAnsiView& typeName);
diff --git a/Source/Engine/Core/Types/Version.cpp b/Source/Engine/Core/Types/Version.cpp
index 4a11a0af6..c0410d1cb 100644
--- a/Source/Engine/Core/Types/Version.cpp
+++ b/Source/Engine/Core/Types/Version.cpp
@@ -7,15 +7,15 @@ Version::Version(int32 major, int32 minor, int32 build, int32 revision)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
- _build = Math::Max(build, 0);
- _revision = Math::Max(revision, 0);
+ _build = Math::Max(build, -1);
+ _revision = Math::Max(revision, -1);
}
Version::Version(int32 major, int32 minor, int32 build)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
- _build = Math::Max(build, 0);
+ _build = Math::Max(build, -1);
_revision = -1;
}
diff --git a/Source/Engine/Core/Utilities.h b/Source/Engine/Core/Utilities.h
index 36339baf5..737e423b2 100644
--- a/Source/Engine/Core/Utilities.h
+++ b/Source/Engine/Core/Utilities.h
@@ -51,10 +51,14 @@ namespace Utilities
int32 i = 0;
double dblSUnits = static_cast(units);
for (; static_cast(units / static_cast(divider)) > 0; i++, units /= divider)
- dblSUnits = units / static_cast(divider);
+ dblSUnits = (double)units / (double)divider;
if (i >= sizes.Length())
i = 0;
- return String::Format(TEXT("{0} {1}"), RoundTo2DecimalPlaces(dblSUnits), sizes[i]);
+ String text = String::Format(TEXT("{}"), RoundTo2DecimalPlaces(dblSUnits));
+ const int32 dot = text.FindLast('.');
+ if (dot != -1)
+ text = text.Left(dot + 3);
+ return String::Format(TEXT("{0} {1}"), text, sizes[i]);
}
// Converts size of the file (in bytes) to the best fitting string
diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp
index 48fa31c91..059ebbd5d 100644
--- a/Source/Engine/Debug/DebugDraw.cpp
+++ b/Source/Engine/Debug/DebugDraw.cpp
@@ -696,12 +696,15 @@ void* DebugDraw::AllocateContext()
void DebugDraw::FreeContext(void* context)
{
+ ASSERT(context);
Memory::DestructItem((DebugDrawContext*)context);
Allocator::Free(context);
}
void DebugDraw::UpdateContext(void* context, float deltaTime)
{
+ if (!context)
+ context = &GlobalContext;
((DebugDrawContext*)context)->DebugDrawDefault.Update(deltaTime);
((DebugDrawContext*)context)->DebugDrawDepthTest.Update(deltaTime);
}
diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp
index 5b93ab588..c0cfad34c 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -20,7 +20,6 @@
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Threading/ThreadRegistry.h"
#include "Engine/Graphics/GPUDevice.h"
-#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/JsonAsset.h"
@@ -327,14 +326,6 @@ void Engine::OnUpdate()
// Update services
EngineService::OnUpdate();
-
-#ifdef USE_NETCORE
- // Force GC to run in background periodically to avoid large blocking collections causing hitches
- if (Time::Update.TicksCount % 60 == 0)
- {
- MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false);
- }
-#endif
}
void Engine::OnLateUpdate()
@@ -596,11 +587,13 @@ void EngineImpl::InitPaths()
Globals::ProjectCacheFolder = Globals::ProjectFolder / TEXT("Cache");
#endif
+#if USE_MONO
// We must ensure that engine is located in folder which path contains only ANSI characters
// Why? Mono lib must have etc and lib folders at ANSI path
// But project can be located on Unicode path
if (!Globals::StartupFolder.IsANSI())
Platform::Fatal(TEXT("Cannot start application in directory which name contains non-ANSI characters."));
+#endif
#if !PLATFORM_SWITCH && !FLAX_TESTS
// Setup directories
diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
index 2094a528f..0391974b6 100644
--- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs
+++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
@@ -188,7 +188,7 @@ namespace FlaxEngine.Interop
internal static void RegisterNativeLibrary(IntPtr moduleNamePtr, IntPtr modulePathPtr)
{
string moduleName = Marshal.PtrToStringAnsi(moduleNamePtr);
- string modulePath = Marshal.PtrToStringAnsi(modulePathPtr);
+ string modulePath = Marshal.PtrToStringUni(modulePathPtr);
libraryPaths[moduleName] = modulePath;
}
diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs
index 2f74e421c..fea1fadda 100644
--- a/Source/Engine/Engine/NativeInterop.cs
+++ b/Source/Engine/Engine/NativeInterop.cs
@@ -1302,7 +1302,8 @@ namespace FlaxEngine.Interop
#if !USE_AOT
internal bool TryGetDelegate(out Invoker.MarshalAndInvokeDelegate outDeleg, out object outDelegInvoke)
{
- if (invokeDelegate == null)
+ // Skip using in-built delegate for value types (eg. Transform) to properly handle instance value passing to method
+ if (invokeDelegate == null && !method.DeclaringType.IsValueType)
{
List methodTypes = new List();
if (!method.IsStatic)
diff --git a/Source/Engine/Engine/Screen.cpp b/Source/Engine/Engine/Screen.cpp
index fc7e8a022..ede49cf92 100644
--- a/Source/Engine/Engine/Screen.cpp
+++ b/Source/Engine/Engine/Screen.cpp
@@ -22,7 +22,7 @@ class ScreenService : public EngineService
{
public:
ScreenService()
- : EngineService(TEXT("Screen"), 120)
+ : EngineService(TEXT("Screen"), 500)
{
}
diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp
index 82765f151..898139ce7 100644
--- a/Source/Engine/Foliage/Foliage.cpp
+++ b/Source/Engine/Foliage/Foliage.cpp
@@ -634,15 +634,12 @@ void Foliage::RemoveFoliageType(int32 index)
int32 Foliage::GetFoliageTypeInstancesCount(int32 index) const
{
PROFILE_CPU();
-
int32 result = 0;
-
- for (auto i = Instances.Begin(); i.IsNotEnd(); i++)
+ for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
{
if (i->Type == index)
result++;
}
-
return result;
}
diff --git a/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h b/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h
index 86c723530..f57327fa4 100644
--- a/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h
+++ b/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h
@@ -26,12 +26,12 @@ public:
, _srcResource(src)
, _dstResource(dst)
{
- _srcResource.OnUnload.Bind(this);
- _dstResource.OnUnload.Bind(this);
+ _srcResource.Released.Bind(this);
+ _dstResource.Released.Bind(this);
}
private:
- void OnResourceUnload(GPUResourceReference* ref)
+ void OnResourceReleased()
{
Cancel();
}
@@ -47,14 +47,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
- if (_srcResource.IsMissing() || _dstResource.IsMissing())
+ if (!_srcResource || !_dstResource)
return Result::MissingResources;
-
context->GPU->CopyResource(_dstResource, _srcResource);
-
return Result::Ok;
}
-
void OnEnd() override
{
_srcResource.Unlink();
diff --git a/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h b/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h
index ab8f1ffad..193eb965d 100644
--- a/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h
+++ b/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h
@@ -31,12 +31,12 @@ public:
, _srcSubresource(srcSubresource)
, _dstSubresource(dstSubresource)
{
- _srcResource.OnUnload.Bind(this);
- _dstResource.OnUnload.Bind(this);
+ _srcResource.Released.Bind(this);
+ _dstResource.Released.Bind(this);
}
private:
- void OnResourceUnload(GPUResourceReference* ref)
+ void OnResourceReleased()
{
Cancel();
}
@@ -52,14 +52,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
- if (_srcResource.IsMissing() || _dstResource.IsMissing())
+ if (!_srcResource || !_dstResource)
return Result::MissingResources;
-
context->GPU->CopySubresource(_dstResource, _dstSubresource, _srcResource, _srcSubresource);
-
return Result::Ok;
}
-
void OnEnd() override
{
_srcResource.Unlink();
diff --git a/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h b/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h
index d2f20449c..3d38ce58b 100644
--- a/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h
+++ b/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h
@@ -31,7 +31,7 @@ public:
, _buffer(buffer)
, _offset(offset)
{
- _buffer.OnUnload.Bind(this);
+ _buffer.Released.Bind(this);
if (copyData)
_data.Copy(data);
@@ -40,7 +40,7 @@ public:
}
private:
- void OnResourceUnload(BufferReference* ref)
+ void OnResourceReleased()
{
Cancel();
}
@@ -56,14 +56,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
- if (_buffer.IsMissing())
+ if (!_buffer)
return Result::MissingResources;
-
context->GPU->UpdateBuffer(_buffer, _data.Get(), _data.Length(), _offset);
-
return Result::Ok;
}
-
void OnEnd() override
{
_buffer.Unlink();
diff --git a/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h b/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h
index 6e9cca7fd..2aff3511b 100644
--- a/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h
+++ b/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h
@@ -35,7 +35,7 @@ public:
, _rowPitch(rowPitch)
, _slicePitch(slicePitch)
{
- _texture.OnUnload.Bind(this);
+ _texture.Released.Bind(this);
if (copyData)
_data.Copy(data);
@@ -44,7 +44,7 @@ public:
}
private:
- void OnResourceUnload(GPUTextureReference* ref)
+ void OnResourceReleased()
{
Cancel();
}
diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp
index 019cfad5a..2a54c7c4d 100644
--- a/Source/Engine/Graphics/GPUDevice.cpp
+++ b/Source/Engine/Graphics/GPUDevice.cpp
@@ -3,6 +3,7 @@
#include "GPUDevice.h"
#include "RenderTargetPool.h"
#include "GPUPipelineState.h"
+#include "GPUResourceProperty.h"
#include "GPUSwapChain.h"
#include "RenderTask.h"
#include "RenderTools.h"
@@ -25,6 +26,39 @@
#include "Engine/Renderer/RenderList.h"
#include "Engine/Scripting/Enums.h"
+GPUResourcePropertyBase::~GPUResourcePropertyBase()
+{
+ const auto e = _resource;
+ if (e)
+ {
+ _resource = nullptr;
+ e->Releasing.Unbind(this);
+ }
+}
+
+void GPUResourcePropertyBase::OnSet(GPUResource* resource)
+{
+ auto e = _resource;
+ if (e != resource)
+ {
+ if (e)
+ e->Releasing.Unbind(this);
+ _resource = e = resource;
+ if (e)
+ e->Releasing.Bind(this);
+ }
+}
+
+void GPUResourcePropertyBase::OnReleased()
+{
+ auto e = _resource;
+ if (e)
+ {
+ _resource = nullptr;
+ e->Releasing.Unbind(this);
+ }
+}
+
GPUPipelineState* GPUPipelineState::Spawn(const SpawnParams& params)
{
return GPUDevice::Instance->CreatePipelineState();
@@ -313,6 +347,8 @@ bool GPUDevice::Init()
_res->TasksManager.SetExecutor(CreateTasksExecutor());
LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory));
+ if (!Limits.HasCompute)
+ LOG(Warning, "Compute Shaders are not supported");
return false;
}
@@ -503,6 +539,9 @@ void GPUDevice::DrawEnd()
// Call present on all used tasks
int32 presentCount = 0;
bool anyVSync = false;
+#if COMPILE_WITH_PROFILER
+ const double presentStart = Platform::GetTimeSeconds();
+#endif
for (int32 i = 0; i < RenderTask::Tasks.Count(); i++)
{
const auto task = RenderTask::Tasks[i];
@@ -537,6 +576,10 @@ void GPUDevice::DrawEnd()
#endif
GetMainContext()->Flush();
}
+#if COMPILE_WITH_PROFILER
+ const double presentEnd = Platform::GetTimeSeconds();
+ ProfilerGPU::OnPresentTime((float)((presentEnd - presentStart) * 1000.0));
+#endif
_wasVSyncUsed = anyVSync;
_isRendering = false;
diff --git a/Source/Engine/Graphics/GPUResourceProperty.h b/Source/Engine/Graphics/GPUResourceProperty.h
index b3c56007d..0b5d73c40 100644
--- a/Source/Engine/Graphics/GPUResourceProperty.h
+++ b/Source/Engine/Graphics/GPUResourceProperty.h
@@ -8,28 +8,39 @@
///
/// GPU Resource container utility object.
///
-template
-class GPUResourceProperty
+class FLAXENGINE_API GPUResourcePropertyBase
{
-private:
- T* _resource;
+protected:
+ GPUResource* _resource = nullptr;
-private:
- // Disable copy actions
- GPUResourceProperty(const GPUResourceProperty& other) = delete;
+public:
+ NON_COPYABLE(GPUResourcePropertyBase);
+
+ GPUResourcePropertyBase() = default;
+ ~GPUResourcePropertyBase();
public:
///
- /// Action fired when resource gets unloaded (reference gets cleared bu async tasks should stop execution).
+ /// Action fired when resource gets released (reference gets cleared bu async tasks should stop execution).
///
- Delegate OnUnload;
+ Action Released;
+protected:
+ void OnSet(GPUResource* resource);
+ void OnReleased();
+};
+
+///
+/// GPU Resource container utility object.
+///
+template
+class GPUResourceProperty : public GPUResourcePropertyBase
+{
public:
///
/// Initializes a new instance of the class.
///
GPUResourceProperty()
- : _resource(nullptr)
{
}
@@ -38,9 +49,37 @@ public:
///
/// The resource.
GPUResourceProperty(T* resource)
- : _resource(nullptr)
{
- Set(resource);
+ OnSet(resource);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The other value.
+ GPUResourceProperty(const GPUResourceProperty& other)
+ {
+ OnSet(other.Get());
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The other value.
+ GPUResourceProperty(GPUResourceProperty&& other)
+ {
+ OnSet(other.Get());
+ other.OnSet(nullptr);
+ }
+
+ GPUResourceProperty& operator=(GPUResourceProperty&& other)
+ {
+ if (&other != this)
+ {
+ OnSet(other._resource);
+ other.OnSet(nullptr);
+ }
+ return *this;
}
///
@@ -48,13 +87,6 @@ public:
///
~GPUResourceProperty()
{
- // Check if object has been binded
- if (_resource)
- {
- // Unlink
- _resource->Releasing.template Unbind(this);
- _resource = nullptr;
- }
}
public:
@@ -63,43 +95,34 @@ public:
return Get() == other;
}
- FORCE_INLINE bool operator==(GPUResourceProperty& other) const
+ FORCE_INLINE bool operator==(const GPUResourceProperty& other) const
{
return Get() == other.Get();
}
- GPUResourceProperty& operator=(const GPUResourceProperty& other)
- {
- if (this != &other)
- Set(other.Get());
- return *this;
- }
-
FORCE_INLINE GPUResourceProperty& operator=(T& other)
{
- Set(&other);
+ OnSet(&other);
return *this;
}
FORCE_INLINE GPUResourceProperty& operator=(T* other)
{
- Set(other);
+ OnSet(other);
return *this;
}
///
/// Implicit conversion to GPU Resource
///
- /// Resource
FORCE_INLINE operator T*() const
{
- return _resource;
+ return (T*)_resource;
}
///
/// Implicit conversion to resource
///
- /// True if resource has been binded, otherwise false
FORCE_INLINE operator bool() const
{
return _resource != nullptr;
@@ -108,37 +131,17 @@ public:
///
/// Implicit conversion to resource
///
- /// Resource
FORCE_INLINE T* operator->() const
{
- return _resource;
+ return (T*)_resource;
}
///
/// Gets linked resource
///
- /// Resource
FORCE_INLINE T* Get() const
{
- return _resource;
- }
-
- ///
- /// Checks if resource has been binded
- ///
- /// True if resource has been binded, otherwise false
- FORCE_INLINE bool IsBinded() const
- {
- return _resource != nullptr;
- }
-
- ///
- /// Checks if resource is missing
- ///
- /// True if resource is missing, otherwise false
- FORCE_INLINE bool IsMissing() const
- {
- return _resource == nullptr;
+ return (T*)_resource;
}
public:
@@ -148,19 +151,7 @@ public:
/// Value to assign
void Set(T* value)
{
- if (_resource != value)
- {
- // Remove reference from the old one
- if (_resource)
- _resource->Releasing.template Unbind(this);
-
- // Change referenced object
- _resource = value;
-
- // Add reference to the new one
- if (_resource)
- _resource->Releasing.template Bind(this);
- }
+ OnSet(value);
}
///
@@ -168,22 +159,7 @@ public:
///
void Unlink()
{
- if (_resource)
- {
- // Remove reference from the old one
- _resource->Releasing.template Unbind(this);
- _resource = nullptr;
- }
- }
-
-private:
- void onResourceUnload()
- {
- if (_resource)
- {
- _resource = nullptr;
- OnUnload(this);
- }
+ OnSet(nullptr);
}
};
diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp
index 9ba2975b2..36dfc4615 100644
--- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp
@@ -92,6 +92,8 @@ bool DecalMaterialShader::Load()
{
GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultNoDepth;
psDesc0.VS = _shader->GetVS("VS_Decal");
+ if (psDesc0.VS == nullptr)
+ return true;
psDesc0.PS = _shader->GetPS("PS_Decal");
psDesc0.CullMode = CullMode::Normal;
diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp
index 52a0bd551..a899dbc12 100644
--- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp
@@ -136,6 +136,7 @@ void DeferredMaterialShader::Unload()
bool DeferredMaterialShader::Load()
{
+ bool failed = false;
auto psDesc = GPUPipelineState::Description::Default;
psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None;
if (EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::DisableDepthTest))
@@ -155,16 +156,20 @@ bool DeferredMaterialShader::Load()
// GBuffer Pass
psDesc.VS = _shader->GetVS("VS");
+ failed |= psDesc.VS == nullptr;
psDesc.PS = _shader->GetPS("PS_GBuffer");
_cache.Default.Init(psDesc);
psDesc.VS = _shader->GetVS("VS", 1);
+ failed |= psDesc.VS == nullptr;
_cacheInstanced.Default.Init(psDesc);
// GBuffer Pass with lightmap (pixel shader permutation for USE_LIGHTMAP=1)
psDesc.VS = _shader->GetVS("VS");
+ failed |= psDesc.VS == nullptr;
psDesc.PS = _shader->GetPS("PS_GBuffer", 1);
_cache.DefaultLightmap.Init(psDesc);
psDesc.VS = _shader->GetVS("VS", 1);
+ failed |= psDesc.VS == nullptr;
_cacheInstanced.DefaultLightmap.Init(psDesc);
// GBuffer Pass with skinning
@@ -233,5 +238,5 @@ bool DeferredMaterialShader::Load()
psDesc.VS = _shader->GetVS("VS_Skinned");
_cache.DepthSkinned.Init(psDesc);
- return false;
+ return failed;
}
diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp
index d391f3295..9fb532b4e 100644
--- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp
@@ -174,6 +174,8 @@ bool ForwardMaterialShader::Load()
// Forward Pass
psDesc.VS = _shader->GetVS("VS");
+ if (psDesc.VS == nullptr)
+ return true;
psDesc.PS = _shader->GetPS("PS_Forward");
psDesc.DepthWriteEnable = false;
psDesc.BlendMode = BlendingMode::AlphaBlend;
diff --git a/Source/Engine/Graphics/Materials/MaterialInfo.h b/Source/Engine/Graphics/Materials/MaterialInfo.h
index 86fab4548..8e4933150 100644
--- a/Source/Engine/Graphics/Materials/MaterialInfo.h
+++ b/Source/Engine/Graphics/Materials/MaterialInfo.h
@@ -86,7 +86,7 @@ API_ENUM() enum class MaterialBlendMode : byte
API_ENUM() enum class MaterialShadingModel : byte
{
///
- /// The unlit material. Emissive channel is used as an output color. Can perform custom lighting operations or just glow. Won't be affected by the lighting pipeline.
+ /// The unlit material. The emissive channel is used as an output color. Can perform custom lighting operations or just glow. Won't be affected by the lighting pipeline.
///
Unlit = 0,
@@ -96,7 +96,7 @@ API_ENUM() enum class MaterialShadingModel : byte
Lit = 1,
///
- /// The subsurface material. Intended for materials like vax or skin that need light scattering to transport simulation through the object.
+ /// The subsurface material. Intended for materials like wax or skin that need light scattering to transport simulation through the object.
///
Subsurface = 2,
@@ -366,12 +366,12 @@ API_ENUM() enum class MaterialDecalBlendingMode : byte
API_ENUM() enum class MaterialTransparentLightingMode : byte
{
///
- /// Default directional lighting evaluated per-pixel at the material surface. Use it for semi-transparent surfaces - with both diffuse and specular lighting component active.
+ /// Default directional lighting evaluated per-pixel at the material surface. Use it for semi-transparent surfaces - with both diffuse and specular lighting components active.
///
Surface = 0,
///
- /// Non-directional lighting evaluated per-pixel at material surface. Use it for volumetric objects such as smoke, rain or dust - only diffuse lighting term is active (no specular highlights).
+ /// Non-directional lighting evaluated per-pixel at material surface. Use it for volumetric objects such as smoke, rain or dust - only the diffuse lighting term is active (no specular highlights).
///
SurfaceNonDirectional = 1,
};
diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp
index f03c013c6..1e531d0e0 100644
--- a/Source/Engine/Graphics/Models/Mesh.cpp
+++ b/Source/Engine/Graphics/Models/Mesh.cpp
@@ -609,7 +609,7 @@ bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& c
ScopeLock lock(model->Locker);
if (model->IsVirtual())
{
- LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download");
+ LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download.");
return true;
}
diff --git a/Source/Engine/Graphics/Models/ModelData.Tool.cpp b/Source/Engine/Graphics/Models/ModelData.Tool.cpp
index d061d0745..727fe7754 100644
--- a/Source/Engine/Graphics/Models/ModelData.Tool.cpp
+++ b/Source/Engine/Graphics/Models/ModelData.Tool.cpp
@@ -318,7 +318,9 @@ void MeshData::BuildIndexBuffer()
}
const auto endTime = Platform::GetTimeSeconds();
- LOG(Info, "Generated index buffer for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), Positions.Count(), Indices.Count());
+ const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
+ if (time > 0.5f) // Don't log if generation was fast enough
+ LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, Indices.Count(), TEXT("indices"));
}
void MeshData::FindPositions(const Float3& position, float epsilon, Array& result)
@@ -449,7 +451,9 @@ bool MeshData::GenerateNormals(float smoothingAngle)
}
const auto endTime = Platform::GetTimeSeconds();
- LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount);
+ const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
+ if (time > 0.5f) // Don't log if generation was fast enough
+ LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("normals"));
return false;
}
@@ -685,7 +689,10 @@ bool MeshData::GenerateTangents(float smoothingAngle)
#endif
const auto endTime = Platform::GetTimeSeconds();
- LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount);
+ const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
+ if (time > 0.5f) // Don't log if generation was fast enough
+ LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("tangents"));
+
return false;
}
@@ -872,7 +879,9 @@ void MeshData::ImproveCacheLocality()
Allocator::Free(piCandidates);
const auto endTime = Platform::GetTimeSeconds();
- LOG(Info, "Cache relevant optimize for {0} vertices and {1} indices. Average output ACMR is {2}. Time: {3}s", vertexCount, indexCount, (float)iCacheMisses / indexCount / 3, Utilities::RoundTo2DecimalPlaces(endTime - startTime));
+ const double time = Utilities::RoundTo2DecimalPlaces(endTime - startTime);
+ if (time > 0.5f) // Don't log if generation was fast enough
+ LOG(Info, "Generated {3} for mesh in {0}s ({1} vertices, {2} indices)", time, vertexCount, indexCount, TEXT("optimized indices"));
}
float MeshData::CalculateTrianglesArea() const
diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h
index 32caa2d01..d7359d905 100644
--- a/Source/Engine/Graphics/Models/ModelData.h
+++ b/Source/Engine/Graphics/Models/ModelData.h
@@ -90,6 +90,21 @@ public:
///
Array BlendShapes;
+ ///
+ /// Global translation for this mesh to be at it's local origin.
+ ///
+ Vector3 OriginTranslation = Vector3::Zero;
+
+ ///
+ /// Orientation for this mesh at it's local origin.
+ ///
+ Quaternion OriginOrientation = Quaternion::Identity;
+
+ ///
+ /// Meshes scaling.
+ ///
+ Vector3 Scaling = Vector3::One;
+
public:
///
/// Determines whether this instance has any mesh data.
diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h
index f3440c4a5..d2ace6cea 100644
--- a/Source/Engine/Graphics/PostProcessSettings.h
+++ b/Source/Engine/Graphics/PostProcessSettings.h
@@ -5,6 +5,7 @@
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Math/Vector4.h"
#include "Engine/Content/AssetReference.h"
+#include "Engine/Content/SoftAssetReference.h"
#include "Engine/Core/ISerializable.h"
#include "Engine/Content/Assets/Texture.h"
#include "Engine/Content/Assets/MaterialBase.h"
@@ -850,7 +851,7 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable
/// The Lookup Table (LUT) used to perform color correction.
///
API_FIELD(Attributes="DefaultValue(null), EditorOrder(22), PostProcessSetting((int)ColorGradingSettingsOverride.LutTexture)")
- AssetReference LutTexture;
+ SoftAssetReference LutTexture;
///
/// The LUT blending weight (normalized to range 0-1). Default is 1.0.
@@ -1277,7 +1278,7 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
/// Fullscreen lens dirt texture.
///
API_FIELD(Attributes="DefaultValue(null), EditorOrder(8), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirt)")
- AssetReference LensDirt;
+ SoftAssetReference LensDirt;
///
/// Fullscreen lens dirt intensity parameter. Allows to tune dirt visibility.
@@ -1289,13 +1290,13 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
/// Custom lens color texture (1D) used for lens color spectrum.
///
API_FIELD(Attributes="DefaultValue(null), EditorOrder(10), PostProcessSetting((int)LensFlaresSettingsOverride.LensColor)")
- AssetReference LensColor;
+ SoftAssetReference LensColor;
///
/// Custom lens star texture sampled by lens flares.
///
API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)LensFlaresSettingsOverride.LensStar)")
- AssetReference LensStar;
+ SoftAssetReference LensStar;
public:
///
@@ -1487,7 +1488,7 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable
/// If BokehShape is set to Custom, then this texture will be used for the bokeh shapes. For best performance, use small, compressed, grayscale textures (for instance 32px).
///
API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShapeCustom)")
- AssetReference BokehShapeCustom;
+ SoftAssetReference BokehShapeCustom;
///
/// The minimum pixel brightness to create bokeh. Pixels with lower brightness will be skipped.
diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp
index e76ee9996..86d983fd8 100644
--- a/Source/Engine/Graphics/Shaders/GPUShader.cpp
+++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp
@@ -87,7 +87,11 @@ bool GPUShader::Create(MemoryReadStream& stream)
GPUShaderProgramInitializer initializer;
#if !BUILD_RELEASE
initializer.Owner = this;
+ const StringView name = GetName();
+#else
+ const StringView name;
#endif
+ const bool hasCompute = GPUDevice::Instance->Limits.HasCompute;
for (int32 i = 0; i < shadersCount; i++)
{
const ShaderStage type = static_cast(stream.ReadByte());
@@ -117,10 +121,15 @@ bool GPUShader::Create(MemoryReadStream& stream)
stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings));
// Create shader program
+ if (type == ShaderStage::Compute && !hasCompute)
+ {
+ LOG(Warning, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
+ continue;
+ }
GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, cache, cacheSize, stream);
if (shader == nullptr)
{
- LOG(Error, "Failed to create {} Shader program '{}'.", ::ToString(type), String(initializer.Name));
+ LOG(Error, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
return true;
}
diff --git a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h
index 09c45506f..31f6638dc 100644
--- a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h
+++ b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h
@@ -122,6 +122,19 @@ public:
///
class GPUShaderProgramVS : public GPUShaderProgram
{
+public:
+ // Input element run-time data (see VertexShaderMeta::InputElement for compile-time data)
+ PACK_STRUCT(struct InputElement
+ {
+ byte Type; // VertexShaderMeta::InputType
+ byte Index;
+ byte Format; // PixelFormat
+ byte InputSlot;
+ uint32 AlignedByteOffset; // Fixed value or INPUT_LAYOUT_ELEMENT_ALIGN if auto
+ byte InputSlotClass; // INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA or INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA
+ uint32 InstanceDataStepRate; // 0 if per-vertex
+ });
+
public:
///
/// Gets input layout description handle (platform dependent).
diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp
index 409125838..173b0ef85 100644
--- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp
+++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp
@@ -22,10 +22,10 @@ TextureHeader::TextureHeader()
TextureGroup = -1;
}
-TextureHeader::TextureHeader(TextureHeader_Deprecated& old)
+TextureHeader::TextureHeader(const TextureHeader_Deprecated& old)
{
Platform::MemoryClear(this, sizeof(*this));
- Width = old.Width;;
+ Width = old.Width;
Height = old.Height;
MipLevels = old.MipLevels;
Format = old.Format;
@@ -49,7 +49,7 @@ StreamingTexture::StreamingTexture(ITextureOwner* parent, const String& name)
, _texture(nullptr)
, _isBlockCompressed(false)
{
- ASSERT(_owner != nullptr);
+ ASSERT(parent != nullptr);
// Always have created texture object
ASSERT(GPUDevice::Instance);
@@ -329,11 +329,11 @@ public:
, _dataLock(_streamingTexture->GetOwner()->LockData())
{
_streamingTexture->_streamingTasks.Add(this);
- _texture.OnUnload.Bind(this);
+ _texture.Released.Bind(this);
}
private:
- void onResourceUnload2(GPUTextureReference* ref)
+ void OnResourceReleased2()
{
// Unlink texture
if (_streamingTexture)
diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp
index 538b15a4a..181955fce 100644
--- a/Source/Engine/Graphics/Textures/TextureBase.cpp
+++ b/Source/Engine/Graphics/Textures/TextureBase.cpp
@@ -660,6 +660,7 @@ uint64 TextureBase::GetMemoryUsage() const
void TextureBase::CancelStreaming()
{
+ Asset::CancelStreaming();
_texture.CancelStreamingTasks();
}
diff --git a/Source/Engine/Graphics/Textures/Types.h b/Source/Engine/Graphics/Textures/Types.h
index 9593cbbc2..31e0b1afc 100644
--- a/Source/Engine/Graphics/Textures/Types.h
+++ b/Source/Engine/Graphics/Textures/Types.h
@@ -106,5 +106,5 @@ struct FLAXENGINE_API TextureHeader
byte CustomData[10];
TextureHeader();
- TextureHeader(TextureHeader_Deprecated& old);
+ TextureHeader(const TextureHeader_Deprecated& old);
};
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp
index b58684a4e..52192c634 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp
@@ -15,32 +15,21 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
{
case ShaderStage::Vertex:
{
- D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
-
- // Temporary variables
- byte Type, Format, Index, InputSlot, InputSlotClass;
- uint32 AlignedByteOffset, InstanceDataStepRate;
-
- // Load Input Layout (it may be empty)
+ // Load Input Layout
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
+ D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
- // TODO: maybe use struct and load at once?
- stream.ReadByte(&Type);
- stream.ReadByte(&Index);
- stream.ReadByte(&Format);
- stream.ReadByte(&InputSlot);
- stream.ReadUint32(&AlignedByteOffset);
- stream.ReadByte(&InputSlotClass);
- stream.ReadUint32(&InstanceDataStepRate);
+ GPUShaderProgramVS::InputElement inputElement;
+ stream.Read(inputElement);
// Get semantic name
const char* semanticName = nullptr;
// TODO: maybe use enum+mapping ?
- switch (Type)
+ switch (inputElement.Type)
{
case 1:
semanticName = "POSITION";
@@ -70,7 +59,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
semanticName = "BLENDWEIGHT";
break;
default:
- LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type);
+ LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
break;
}
@@ -78,12 +67,12 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
inputLayoutDesc[a] =
{
semanticName,
- static_cast(Index),
- static_cast(Format),
- static_cast(InputSlot),
- static_cast(AlignedByteOffset),
- static_cast(InputSlotClass),
- static_cast(InstanceDataStepRate)
+ static_cast(inputElement.Index),
+ static_cast(inputElement.Format),
+ static_cast(inputElement.InputSlot),
+ static_cast(inputElement.AlignedByteOffset),
+ static_cast(inputElement.InputSlotClass),
+ static_cast(inputElement.InstanceDataStepRate)
};
}
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp
index e1e716853..07352b674 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp
@@ -20,32 +20,21 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
{
case ShaderStage::Vertex:
{
- D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
-
- // Temporary variables
- byte Type, Format, Index, InputSlot, InputSlotClass;
- uint32 AlignedByteOffset, InstanceDataStepRate;
-
// Load Input Layout (it may be empty)
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
+ D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
- // TODO: maybe use struct and load at once?
- stream.ReadByte(&Type);
- stream.ReadByte(&Index);
- stream.ReadByte(&Format);
- stream.ReadByte(&InputSlot);
- stream.ReadUint32(&AlignedByteOffset);
- stream.ReadByte(&InputSlotClass);
- stream.ReadUint32(&InstanceDataStepRate);
+ GPUShaderProgramVS::InputElement inputElement;
+ stream.Read(inputElement);
// Get semantic name
const char* semanticName = nullptr;
// TODO: maybe use enum+mapping ?
- switch (Type)
+ switch (inputElement.Type)
{
case 1:
semanticName = "POSITION";
@@ -75,7 +64,7 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
semanticName = "BLENDWEIGHT";
break;
default:
- LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type);
+ LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
break;
}
@@ -83,12 +72,12 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
inputLayout[a] =
{
semanticName,
- static_cast(Index),
- static_cast(Format),
- static_cast(InputSlot),
- static_cast(AlignedByteOffset),
- static_cast(InputSlotClass),
- static_cast(InstanceDataStepRate)
+ static_cast(inputElement.Index),
+ static_cast(inputElement.Format),
+ static_cast(inputElement.InputSlot),
+ static_cast(inputElement.AlignedByteOffset),
+ static_cast(inputElement.InputSlotClass),
+ static_cast(inputElement.InstanceDataStepRate)
};
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp
index 5e583d713..852ce5bad 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp
@@ -14,9 +14,9 @@
#include "Engine/Graphics/PixelFormatExtensions.h"
#if PLATFORM_DESKTOP
-#define VULKAN_UNIFORM_RING_BUFFER_SIZE 24 * 1024 * 1024
+#define VULKAN_UNIFORM_RING_BUFFER_SIZE (24 * 1024 * 1024)
#else
-#define VULKAN_UNIFORM_RING_BUFFER_SIZE 8 * 1024 * 1024
+#define VULKAN_UNIFORM_RING_BUFFER_SIZE (8 * 1024 * 1024)
#endif
UniformBufferUploaderVulkan::UniformBufferUploaderVulkan(GPUDeviceVulkan* device)
@@ -153,10 +153,6 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
vertexBindingDescriptions[i].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
}
- // Temporary variables
- byte Type, Format, Index, InputSlot, InputSlotClass;
- uint32 AlignedByteOffset, InstanceDataStepRate;
-
// Load Input Layout (it may be empty)
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
@@ -167,32 +163,26 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
- // TODO: maybe use struct and load at once?
- stream.ReadByte(&Type);
- stream.ReadByte(&Index);
- stream.ReadByte(&Format);
- stream.ReadByte(&InputSlot);
- stream.ReadUint32(&AlignedByteOffset);
- stream.ReadByte(&InputSlotClass);
- stream.ReadUint32(&InstanceDataStepRate);
+ GPUShaderProgramVS::InputElement inputElement;
+ stream.Read(inputElement);
- const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)Format);
- if (AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN)
- offset = AlignedByteOffset;
+ const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)inputElement.Format);
+ if (inputElement.AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN)
+ offset = inputElement.AlignedByteOffset;
- auto& vertexBindingDescription = vertexBindingDescriptions[InputSlot];
- vertexBindingDescription.binding = InputSlot;
+ auto& vertexBindingDescription = vertexBindingDescriptions[inputElement.InputSlot];
+ vertexBindingDescription.binding = inputElement.InputSlot;
vertexBindingDescription.stride = Math::Max(vertexBindingDescription.stride, (uint32_t)(offset + size));
- vertexBindingDescription.inputRate = InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
- ASSERT(InstanceDataStepRate == 0 || InstanceDataStepRate == 1);
+ vertexBindingDescription.inputRate = inputElement.InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
+ ASSERT(inputElement.InstanceDataStepRate == 0 || inputElement.InstanceDataStepRate == 1);
auto& vertexAttributeDescription = vertexAttributeDescriptions[a];
vertexAttributeDescription.location = a;
- vertexAttributeDescription.binding = InputSlot;
- vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)Format);
+ vertexAttributeDescription.binding = inputElement.InputSlot;
+ vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)inputElement.Format);
vertexAttributeDescription.offset = offset;
- bindingsCount = Math::Max(bindingsCount, (uint32)InputSlot + 1);
+ bindingsCount = Math::Max(bindingsCount, (uint32)inputElement.InputSlot + 1);
offset += size;
}
diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp
index 75e3c12d4..addb4861e 100644
--- a/Source/Engine/Level/Actor.cpp
+++ b/Source/Engine/Level/Actor.cpp
@@ -1406,7 +1406,7 @@ Script* Actor::FindScript(const MClass* type) const
CHECK_RETURN(type, nullptr);
for (auto script : Scripts)
{
- if (script->GetClass()->IsSubClassOf(type))
+ if (script->GetClass()->IsSubClassOf(type) || script->GetClass()->HasInterface(type))
return script;
}
for (auto child : Children)
diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp
index 459e1fb6b..88f7e6876 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.cpp
+++ b/Source/Engine/Level/Actors/AnimatedModel.cpp
@@ -706,13 +706,12 @@ void AnimatedModel::UpdateBounds()
const int32 bonesCount = skeleton.Bones.Count();
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
box.Merge(_transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones.Get()[boneIndex].NodeIndex].GetTranslation()));
- _box = box;
}
// Apply margin based on model dimensions
const Vector3 modelBoxSize = modelBox.GetSize();
- const Vector3 center = _box.GetCenter();
- const Vector3 sizeHalf = Vector3::Max(_box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f;
+ const Vector3 center = box.GetCenter();
+ const Vector3 sizeHalf = Vector3::Max(box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f;
_box = BoundingBox(center - sizeHalf, center + sizeHalf);
}
else
diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h
index d070d99a2..0c5c4d73b 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.h
+++ b/Source/Engine/Level/Actors/AnimatedModel.h
@@ -12,7 +12,7 @@
///
/// Performs an animation and renders a skinned model.
///
-API_CLASS(Attributes="ActorContextMenu(\"New/Other/Animated Model\"), ActorToolbox(\"Other\")")
+API_CLASS(Attributes="ActorContextMenu(\"New/Other/Animated Model\"), ActorToolbox(\"Visuals\")")
class FLAXENGINE_API AnimatedModel : public ModelInstanceActor
{
DECLARE_SCENE_OBJECT(AnimatedModel);
diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h
index 22d950dd4..c63a5dcf6 100644
--- a/Source/Engine/Level/Actors/Camera.h
+++ b/Source/Engine/Level/Actors/Camera.h
@@ -66,7 +66,7 @@ public:
///
/// Gets the value indicating if camera should use perspective rendering mode, otherwise it will use orthographic projection.
///
- API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Camera\"), Tooltip(\"Enables perspective projection mode, otherwise uses orthographic.\")")
+ API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(true), EditorDisplay(\"Camera\")")
bool GetUsePerspective() const;
///
@@ -77,7 +77,7 @@ public:
///
/// Gets the camera's field of view (in degrees).
///
- API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), Tooltip(\"Field of view angle in degrees.\")")
+ API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective))")
float GetFieldOfView() const;
///
@@ -88,7 +88,7 @@ public:
///
/// Gets the custom aspect ratio. 0 if not use custom value.
///
- API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Custom aspect ratio to use. Set to 0 to disable.\")")
+ API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective))")
float GetCustomAspectRatio() const;
///
@@ -99,7 +99,7 @@ public:
///
/// Gets camera's near plane distance.
///
- API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\"), Tooltip(\"Near clipping plane distance\")")
+ API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\")")
float GetNearPlane() const;
///
@@ -110,7 +110,7 @@ public:
///
/// Gets camera's far plane distance.
///
- API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\"), Tooltip(\"Far clipping plane distance\")")
+ API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\")")
float GetFarPlane() const;
///
@@ -121,7 +121,7 @@ public:
///
/// Gets the orthographic projection scale.
///
- API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Orthographic projection scale\")")
+ API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective), true)")
float GetOrthographicScale() const;
///
diff --git a/Source/Engine/Level/Actors/SpotLight.h b/Source/Engine/Level/Actors/SpotLight.h
index 37735692a..2c16d38e4 100644
--- a/Source/Engine/Level/Actors/SpotLight.h
+++ b/Source/Engine/Level/Actors/SpotLight.h
@@ -24,7 +24,7 @@ private:
public:
///
- /// Light source bulb radius
+ /// Light source bulb radius.
///
API_FIELD(Attributes="EditorOrder(2), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)")
float SourceRadius = 0.0f;
@@ -42,54 +42,51 @@ public:
float FallOffExponent = 8.0f;
///
- /// IES texture (light profiles from real world measured data)
+ /// IES texture (light profiles from real world measured data).
///
API_FIELD(Attributes="EditorOrder(211), DefaultValue(null), EditorDisplay(\"IES Profile\", \"IES Texture\")")
AssetReference IESTexture;
///
- /// Enable/disable using light brightness from IES profile
+ /// Enable/disable using light brightness from IES profile.
///
API_FIELD(Attributes="EditorOrder(212), DefaultValue(false), EditorDisplay(\"IES Profile\", \"Use IES Brightness\")")
bool UseIESBrightness = false;
///
- /// Global scale for IES brightness contribution
+ /// Global scale for IES brightness contribution.
///
API_FIELD(Attributes="EditorOrder(213), DefaultValue(1.0f), Limit(0, 10000, 0.01f), EditorDisplay(\"IES Profile\", \"Brightness Scale\")")
float IESBrightnessScale = 1.0f;
public:
///
- /// Computes light brightness value
+ /// Computes light brightness value.
///
- /// Brightness
float ComputeBrightness() const;
///
- /// Gets scaled light radius
+ /// Gets scaled light radius.
///
float GetScaledRadius() const;
///
- /// Gets light radius
+ /// Gets light radius.
///
- API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Tooltip(\"Light radius\"), Limit(0, 10000, 0.1f)")
+ API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Limit(0, 10000, 0.1f)")
FORCE_INLINE float GetRadius() const
{
return _radius;
}
///
- /// Sets light radius
+ /// Sets light radius.
///
- /// New radius
API_PROPERTY() void SetRadius(float value);
///
- /// Gets the spot light's outer cone angle (in degrees)
+ /// Gets the spot light's outer cone angle (in degrees).
///
- /// Outer angle (in degrees)
API_PROPERTY(Attributes="EditorOrder(22), DefaultValue(43.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)")
FORCE_INLINE float GetOuterConeAngle() const
{
@@ -97,15 +94,13 @@ public:
}
///
- /// Sets the spot light's outer cone angle (in degrees)
+ /// Sets the spot light's outer cone angle (in degrees).
///
- /// Value to assign
API_PROPERTY() void SetOuterConeAngle(float value);
///
- /// Sets the spot light's inner cone angle (in degrees)
+ /// Sets the spot light's inner cone angle (in degrees).
///
- /// Inner angle (in degrees)
API_PROPERTY(Attributes="EditorOrder(21), DefaultValue(10.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)")
FORCE_INLINE float GetInnerConeAngle() const
{
@@ -113,9 +108,8 @@ public:
}
///
- /// Sets the spot light's inner cone angle (in degrees)
+ /// Sets the spot light's inner cone angle (in degrees).
///
- /// Value to assign
API_PROPERTY() void SetInnerConeAngle(float value);
private:
diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h
index 2e932096e..2f064eb8a 100644
--- a/Source/Engine/Level/Actors/StaticModel.h
+++ b/Source/Engine/Level/Actors/StaticModel.h
@@ -10,7 +10,8 @@
///
/// Renders model on the screen.
///
-API_CLASS(Attributes="ActorContextMenu(\"New/Model\")") class FLAXENGINE_API StaticModel : public ModelInstanceActor
+API_CLASS(Attributes="ActorContextMenu(\"New/Model\"), ActorToolbox(\"Visuals\")")
+class FLAXENGINE_API StaticModel : public ModelInstanceActor
{
DECLARE_SCENE_OBJECT(StaticModel);
private:
diff --git a/Source/Engine/Level/LargeWorlds.h b/Source/Engine/Level/LargeWorlds.h
index 514c1d5ac..2d92b7228 100644
--- a/Source/Engine/Level/LargeWorlds.h
+++ b/Source/Engine/Level/LargeWorlds.h
@@ -19,7 +19,7 @@ API_CLASS(Static) class FLAXENGINE_API LargeWorlds
///
/// Defines the size of a single chunk. Large world (64-bit) gets divided into smaller chunks so all the math operations (32-bit) can be performed relative to the chunk origin without precision loss.
///
- API_FIELD() static constexpr Real ChunkSize = 262144;
+ API_FIELD() static constexpr Real ChunkSize = 8192;
///
/// Updates the large world origin to match the input position. The origin is snapped to the best matching chunk location.
diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp
index df65fec3e..cfdf11a6f 100644
--- a/Source/Engine/Level/Level.cpp
+++ b/Source/Engine/Level/Level.cpp
@@ -132,7 +132,7 @@ class LevelService : public EngineService
{
public:
LevelService()
- : EngineService(TEXT("Scene Manager"), 30)
+ : EngineService(TEXT("Scene Manager"), 200)
{
}
@@ -416,7 +416,7 @@ public:
}
// Load scene
- if (Level::loadScene(SceneAsset.Get()))
+ if (Level::loadScene(SceneAsset))
{
LOG(Error, "Failed to deserialize scene {0}", SceneId);
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
@@ -816,40 +816,10 @@ bool LevelImpl::unloadScenes()
return false;
}
-bool Level::loadScene(const Guid& sceneId)
-{
- const auto sceneAsset = Content::LoadAsync(sceneId);
- return loadScene(sceneAsset);
-}
-
-bool Level::loadScene(const String& scenePath)
-{
- LOG(Info, "Loading scene from file. Path: \'{0}\'", scenePath);
-
- // Check for missing file
- if (!FileSystem::FileExists(scenePath))
- {
- LOG(Error, "Missing scene file.");
- return true;
- }
-
- // Load file
- BytesContainer sceneData;
- if (File::ReadAllBytes(scenePath, sceneData))
- {
- LOG(Error, "Cannot load data from file.");
- return true;
- }
-
- return loadScene(sceneData);
-}
-
bool Level::loadScene(JsonAsset* sceneAsset)
{
// Keep reference to the asset (prevent unloading during action)
AssetReference ref = sceneAsset;
-
- // Wait for loaded
if (sceneAsset == nullptr || sceneAsset->WaitForLoaded())
{
LOG(Error, "Cannot load scene asset.");
@@ -879,6 +849,7 @@ bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene)
return true;
}
+ ScopeLock lock(ScenesLock);
return loadScene(document, outScene);
}
@@ -975,6 +946,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
SceneObject** objects = sceneObjects->Get();
if (context.Async)
{
+ ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize)
JobSystem::Execute([&](int32 i)
{
i++; // Start from 1. at index [0] was scene
@@ -992,6 +964,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
else
SceneObjectsFactory::HandleObjectDeserializationError(stream);
}, objectsCount - 1);
+ ScenesLock.Lock();
}
else
{
@@ -1330,6 +1303,7 @@ bool Level::LoadScene(const Guid& id)
}
// Load scene
+ ScopeLock lock(ScenesLock);
if (loadScene(sceneAsset))
{
LOG(Error, "Failed to deserialize scene {0}", id);
diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h
index 1f0acda2d..e09f8e376 100644
--- a/Source/Engine/Level/Level.h
+++ b/Source/Engine/Level/Level.h
@@ -541,8 +541,8 @@ private:
};
static void callActorEvent(ActorEventType eventType, Actor* a, Actor* b);
- static bool loadScene(const Guid& sceneId);
- static bool loadScene(const String& scenePath);
+
+ // All loadScene assume that ScenesLock has been taken by the calling thread
static bool loadScene(JsonAsset* sceneAsset);
static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr);
static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr);
diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp
index 123013064..de164343b 100644
--- a/Source/Engine/Level/Prefabs/PrefabManager.cpp
+++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp
@@ -29,7 +29,7 @@ class PrefabManagerService : public EngineService
{
public:
PrefabManagerService()
- : EngineService(TEXT("Prefab Manager"), 110)
+ : EngineService(TEXT("Prefab Manager"))
{
}
};
@@ -39,7 +39,7 @@ PrefabManagerService PrefabManagerServiceInstance;
Actor* PrefabManager::SpawnPrefab(Prefab* prefab)
{
Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr;
- return SpawnPrefab(prefab, Transform::Identity, parent, nullptr);
+ return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr);
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position)
@@ -73,12 +73,12 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, const Transform
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent)
{
- return SpawnPrefab(prefab, Transform::Identity, parent, nullptr);
+ return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr);
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary* objectsCache, bool withSynchronization)
{
- return SpawnPrefab(prefab, Transform::Identity, parent, objectsCache, withSynchronization);
+ return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, objectsCache, withSynchronization);
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary* objectsCache, bool withSynchronization)
@@ -191,7 +191,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
parent->Children.Add(root);
// Move root to the right location
- if (transform != Transform::Identity)
+ if (transform.Translation != Vector3::Minimum)
root->SetTransform(transform);
// Link actors hierarchy
diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp
index 7f62d943b..2936da3da 100644
--- a/Source/Engine/Level/SceneObjectsFactory.cpp
+++ b/Source/Engine/Level/SceneObjectsFactory.cpp
@@ -14,6 +14,10 @@
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/ThreadLocal.h"
+#if !BUILD_RELEASE || USE_EDITOR
+#include "Engine/Level/Level.h"
+#include "Engine/Threading/Threading.h"
+#endif
SceneObjectsFactory::Context::Context(ISerializeModifier* modifier)
: Modifier(modifier)
@@ -254,6 +258,10 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria
void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::DeserializeStream& value)
{
+#if !BUILD_RELEASE || USE_EDITOR
+ // Prevent race-conditions when logging missing objects (especially when adding dummy MissingScript)
+ ScopeLock lock(Level::ScenesLock);
+
// Print invalid object data contents
rapidjson_flax::StringBuffer buffer;
PrettyJsonWriter writer(buffer);
@@ -272,14 +280,15 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::
#if USE_EDITOR
// Add dummy script
auto* dummyScript = parent->AddScript();
- const auto parentIdMember = value.FindMember("TypeName");
- if (parentIdMember != value.MemberEnd() && parentIdMember->value.IsString())
- dummyScript->MissingTypeName = parentIdMember->value.GetString();
+ const auto typeNameMember = value.FindMember("TypeName");
+ if (typeNameMember != value.MemberEnd() && typeNameMember->value.IsString())
+ dummyScript->MissingTypeName = typeNameMember->value.GetString();
dummyScript->Data = MoveTemp(bufferStr);
#endif
LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName());
}
}
+#endif
}
Actor* SceneObjectsFactory::CreateActor(int32 typeId, const Guid& id)
@@ -361,14 +370,14 @@ SceneObjectsFactory::PrefabSyncData::PrefabSyncData(Array& sceneOb
{
}
-void SceneObjectsFactory::SetupPrefabInstances(Context& context, PrefabSyncData& data)
+void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyncData& data)
{
PROFILE_CPU_NAMED("SetupPrefabInstances");
const int32 count = data.Data.Size();
ASSERT(count <= data.SceneObjects.Count());
for (int32 i = 0; i < count; i++)
{
- SceneObject* obj = data.SceneObjects[i];
+ const SceneObject* obj = data.SceneObjects[i];
if (!obj)
continue;
const auto& stream = data.Data[i];
@@ -409,6 +418,21 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, PrefabSyncData&
// Add to the prefab instance IDs mapping
auto& prefabInstance = context.Instances[index];
prefabInstance.IdsMapping[prefabObjectId] = id;
+
+ // Walk over nested prefabs to link any subobjects into this object (eg. if nested prefab uses cross-object references to link them correctly)
+ NESTED_PREFAB_WALK:
+ const ISerializable::DeserializeStream* prefabData;
+ if (prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData) && JsonTools::GetGuidIfValid(prefabObjectId, *prefabData, "PrefabObjectID"))
+ {
+ prefabId = JsonTools::GetGuid(stream, "PrefabID");
+ prefab = Content::LoadAsync(prefabId);
+ if (prefab && !prefab->WaitForLoaded())
+ {
+ // Map prefab object ID to the deserialized instance ID
+ prefabInstance.IdsMapping[prefabObjectId] = id;
+ goto NESTED_PREFAB_WALK;
+ }
+ }
}
}
diff --git a/Source/Engine/Level/SceneObjectsFactory.h b/Source/Engine/Level/SceneObjectsFactory.h
index f7f495449..97cf6b9ce 100644
--- a/Source/Engine/Level/SceneObjectsFactory.h
+++ b/Source/Engine/Level/SceneObjectsFactory.h
@@ -100,7 +100,7 @@ public:
///
/// The serialization context.
/// The sync data.
- static void SetupPrefabInstances(Context& context, PrefabSyncData& data);
+ static void SetupPrefabInstances(Context& context, const PrefabSyncData& data);
///
/// Synchronizes the new prefab instances by spawning missing objects that were added to prefab but were not saved with scene objects collection.
diff --git a/Source/Engine/Localization/CultureInfo.cpp b/Source/Engine/Localization/CultureInfo.cpp
index 3e02ccf6e..fc678d419 100644
--- a/Source/Engine/Localization/CultureInfo.cpp
+++ b/Source/Engine/Localization/CultureInfo.cpp
@@ -30,6 +30,20 @@ typedef struct
} MonoCultureInfo;
#endif
+namespace
+{
+ const CultureInfoEntry* FindEntry(const StringAnsiView& name)
+ {
+ for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++)
+ {
+ const CultureInfoEntry& e = culture_entries[i];
+ if (name == idx2string(e.name))
+ return &e;
+ }
+ return nullptr;
+ }
+};
+
CultureInfo::CultureInfo(int32 lcid)
{
_lcid = lcid;
@@ -80,23 +94,24 @@ CultureInfo::CultureInfo(const StringAnsiView& name)
_englishName = TEXT("Invariant Culture");
return;
}
- for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++)
+ const CultureInfoEntry* e = FindEntry(name);
+ if (!e && name.Find('-') != -1)
{
- auto& e = culture_entries[i];
- if (name == idx2string(e.name))
- {
- _data = (void*)&e;
- _lcid = (int32)e.lcid;
- _lcidParent = (int32)e.parent_lcid;
- _name.SetUTF8(name.Get(), name.Length());
- const char* nativename = idx2string(e.nativename);
- _nativeName.SetUTF8(nativename, StringUtils::Length(nativename));
- const char* englishname = idx2string(e.englishname);
- _englishName.SetUTF8(englishname, StringUtils::Length(englishname));
- break;
- }
+ e = FindEntry(name.Substring(0, name.Find('-')));
}
- if (!_data)
+ if (e)
+ {
+ _data = (void*)e;
+ _lcid = (int32)e->lcid;
+ _lcidParent = (int32)e->parent_lcid;
+ const char* ename = idx2string(e->name);
+ _name.SetUTF8(ename, StringUtils::Length(ename));
+ const char* nativename = idx2string(e->nativename);
+ _nativeName.SetUTF8(nativename, StringUtils::Length(nativename));
+ const char* englishname = idx2string(e->englishname);
+ _englishName.SetUTF8(englishname, StringUtils::Length(englishname));
+ }
+ else
{
_lcid = 127;
_lcidParent = 0;
diff --git a/Source/Engine/Navigation/NavCrowd.cpp b/Source/Engine/Navigation/NavCrowd.cpp
index 80055c3a4..fe1c2f728 100644
--- a/Source/Engine/Navigation/NavCrowd.cpp
+++ b/Source/Engine/Navigation/NavCrowd.cpp
@@ -3,7 +3,11 @@
#include "NavCrowd.h"
#include "NavMesh.h"
#include "NavMeshRuntime.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Level/Level.h"
+#include "Engine/Level/Scene/Scene.h"
#include "Engine/Profiler/ProfilerCPU.h"
+#include "Engine/Threading/Threading.h"
#include
NavCrowd::NavCrowd(const SpawnParams& params)
@@ -26,6 +30,15 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMesh* navMesh)
bool NavCrowd::Init(const NavAgentProperties& agentProperties, int32 maxAgents)
{
NavMeshRuntime* navMeshRuntime = NavMeshRuntime::Get(agentProperties);
+#if !BUILD_RELEASE
+ if (!navMeshRuntime)
+ {
+ if (NavMeshRuntime::Get())
+ LOG(Error, "Cannot create crowd. Failed to find a navmesh that matches a given agent properties.");
+ else
+ LOG(Error, "Cannot create crowd. No navmesh is loaded.");
+ }
+#endif
return Init(agentProperties.Radius * 3.0f, maxAgents, navMeshRuntime);
}
@@ -33,6 +46,41 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMeshRuntime* navMe
{
if (!_crowd || !navMesh)
return true;
+
+ // This can happen on game start when no navmesh is loaded yet (eg. navmesh tiles data is during streaming) so wait for navmesh
+ if (navMesh->GetNavMesh() == nullptr)
+ {
+ PROFILE_CPU_NAMED("WaitForNavMesh");
+ if (IsInMainThread())
+ {
+ // Wait for any navmesh data load
+ for (const Scene* scene : Level::Scenes)
+ {
+ for (const NavMesh* actor : scene->Navigation.Meshes)
+ {
+ if (actor->DataAsset)
+ {
+ actor->DataAsset->WaitForLoaded();
+ if (navMesh->GetNavMesh())
+ break;
+ }
+ }
+ if (navMesh->GetNavMesh())
+ break;
+ }
+ }
+ else
+ {
+ while (navMesh->GetNavMesh() == nullptr)
+ Platform::Sleep(1);
+ }
+ if (navMesh->GetNavMesh() == nullptr)
+ {
+ LOG(Error, "Cannot create crowd. Navmesh is not yet laoded.");
+ return true;
+ }
+ }
+
return !_crowd->init(maxAgents, maxAgentRadius, navMesh->GetNavMesh());
}
@@ -48,7 +96,7 @@ Vector3 NavCrowd::GetAgentPosition(int32 id) const
{
Vector3 result = Vector3::Zero;
const dtCrowdAgent* agent = _crowd->getAgent(id);
- if (agent && agent->state != DT_CROWDAGENT_STATE_INVALID)
+ if (agent)
{
result = Float3(agent->npos);
}
@@ -59,7 +107,7 @@ Vector3 NavCrowd::GetAgentVelocity(int32 id) const
{
Vector3 result = Vector3::Zero;
const dtCrowdAgent* agent = _crowd->getAgent(id);
- if (agent && agent->state != DT_CROWDAGENT_STATE_INVALID)
+ if (agent)
{
result = Float3(agent->vel);
}
diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp
index 8bc94eaaa..73e11e630 100644
--- a/Source/Engine/Navigation/Navigation.cpp
+++ b/Source/Engine/Navigation/Navigation.cpp
@@ -9,6 +9,8 @@
#include "Engine/Content/JsonAsset.h"
#include "Engine/Threading/Threading.h"
#if USE_EDITOR
+#include "Editor/Editor.h"
+#include "Editor/Managed/ManagedEditor.h"
#include "Engine/Level/Level.h"
#include "Engine/Level/Scene/Scene.h"
#endif
@@ -220,6 +222,17 @@ void NavigationSettings::Apply()
#endif
}
}
+
+#if USE_EDITOR
+ if (!Editor::IsPlayMode && Editor::Managed && Editor::Managed->CanAutoBuildNavMesh())
+ {
+ // Rebuild all navmeshs after apply changes on navigation
+ for (auto scene : Level::Scenes)
+ {
+ Navigation::BuildNavMesh(scene);
+ }
+ }
+#endif
}
void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
diff --git a/Source/Engine/Networking/Components/NetworkTransform.cpp b/Source/Engine/Networking/Components/NetworkTransform.cpp
index af802649b..6f5943ff1 100644
--- a/Source/Engine/Networking/Components/NetworkTransform.cpp
+++ b/Source/Engine/Networking/Components/NetworkTransform.cpp
@@ -301,11 +301,8 @@ void NetworkTransform::Deserialize(NetworkStream* stream)
_buffer.Clear();
_bufferHasDeltas = true;
}
- // TODO: items are added in order to do batch removal
- for (int32 i = 0; i < _buffer.Count() && _buffer[i].SequenceIndex < sequenceIndex; i++)
- {
- _buffer.RemoveAtKeepOrder(i);
- }
+ while (_buffer.Count() != 0 && _buffer[0].SequenceIndex < sequenceIndex)
+ _buffer.RemoveAtKeepOrder(0);
// Use received authoritative actor transformation but re-apply all deltas not yet processed by the server due to lag (reconciliation)
for (auto& e : _buffer)
diff --git a/Source/Engine/Online/Online.cpp b/Source/Engine/Online/Online.cpp
index 313497f98..d8f85baea 100644
--- a/Source/Engine/Online/Online.cpp
+++ b/Source/Engine/Online/Online.cpp
@@ -4,13 +4,16 @@
#include "IOnlinePlatform.h"
#include "Engine/Core/Log.h"
#include "Engine/Engine/EngineService.h"
+#if USE_EDITOR
+#include "Engine/Scripting/Scripting.h"
+#endif
#include "Engine/Scripting/ScriptingObject.h"
class OnlineService : public EngineService
{
public:
OnlineService()
- : EngineService(TEXT("Online"), 100)
+ : EngineService(TEXT("Online"), 500)
{
}
@@ -25,6 +28,16 @@ IOnlinePlatform* Online::Platform = nullptr;
Action Online::PlatformChanged;
OnlineService OnlineServiceInstance;
+#if USE_EDITOR
+
+void OnOnlineScriptsReloading()
+{
+ // Dispose any active platform
+ Online::Initialize(nullptr);
+}
+
+#endif
+
bool Online::Initialize(IOnlinePlatform* platform)
{
if (Platform == platform)
@@ -34,6 +47,9 @@ bool Online::Initialize(IOnlinePlatform* platform)
if (Platform)
{
+#if USE_EDITOR
+ Scripting::ScriptsReloading.Unbind(OnOnlineScriptsReloading);
+#endif
Platform->Deinitialize();
}
Platform = platform;
@@ -45,6 +61,9 @@ bool Online::Initialize(IOnlinePlatform* platform)
LOG(Error, "Failed to initialize online platform.");
return true;
}
+#if USE_EDITOR
+ Scripting::ScriptsReloading.Bind(OnOnlineScriptsReloading);
+#endif
}
return false;
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp
index 9d91d0013..cb2d7004e 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp
@@ -7,7 +7,7 @@
#include "Engine/Graphics/RenderTask.h"
#define GET_VIEW() auto mainViewTask = MainRenderTask::Instance && MainRenderTask::Instance->LastUsedFrame != 0 ? MainRenderTask::Instance : nullptr
-#define ACCESS_PARTICLE_ATTRIBUTE(index) (context.Data->Buffer->GetParticleCPU(context.ParticleIndex) + context.Data->Buffer->Layout->Attributes[node->Attributes[index]].Offset)
+#define ACCESS_PARTICLE_ATTRIBUTE(index) (context.Data->Buffer->GetParticleCPU(context.ParticleIndex) + context.Data->Buffer->Layout->Attributes[context.AttributesRemappingTable[node->Attributes[index]]].Offset)
#define GET_PARTICLE_ATTRIBUTE(index, type) *(type*)ACCESS_PARTICLE_ATTRIBUTE(index)
void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value)
@@ -436,9 +436,19 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node
auto* functionOutputNode = &graph->Nodes[function->Outputs[outputIndex]];
Box* functionOutputBox = functionOutputNode->TryGetBox(0);
+ // Setup particle attributes remapping (so particle data access nodes inside the function will read data at proper offset, see macro ACCESS_PARTICLE_ATTRIBUTE)
+ byte attributesRemappingTable[PARTICLE_ATTRIBUTES_MAX_COUNT];
+ Platform::MemoryCopy(attributesRemappingTable, context.AttributesRemappingTable, sizeof(attributesRemappingTable));
+ for (int32 i = 0; i < graph->Layout.Attributes.Count(); i++)
+ {
+ const ParticleAttribute& e = graph->Layout.Attributes[i];
+ context.AttributesRemappingTable[i] = context.Data->Buffer->Layout->FindAttribute(e.Name, e.ValueType);
+ }
+
// Evaluate the function output
context.GraphStack.Push(graph);
value = functionOutputBox && functionOutputBox->HasConnection() ? eatBox(nodeBase, functionOutputBox->FirstConnection()) : Value::Zero;
+ Platform::MemoryCopy(context.AttributesRemappingTable, attributesRemappingTable, sizeof(attributesRemappingTable));
context.GraphStack.Pop();
break;
}
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
index d86bc6d54..a8f7898e1 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
@@ -133,6 +133,8 @@ void ParticleEmitterGraphCPUExecutor::Init(ParticleEmitter* emitter, ParticleEff
context.ViewTask = effect->GetRenderTask();
context.CallStackSize = 0;
context.Functions.Clear();
+ for (int32 i = 0; i < PARTICLE_ATTRIBUTES_MAX_COUNT; i++)
+ context.AttributesRemappingTable[i] = i;
}
bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, BoundingBox& result)
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
index 9d1d48c81..4f55da6e2 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
@@ -119,6 +119,7 @@ struct ParticleEmitterGraphCPUContext
class SceneRenderTask* ViewTask;
Array> GraphStack;
Dictionary Functions;
+ byte AttributesRemappingTable[PARTICLE_ATTRIBUTES_MAX_COUNT]; // Maps node attribute indices to the current particle layout (used to support accessing particle data from function graph which has different layout).
int32 CallStackSize = 0;
VisjectExecutor::Node* CallStack[PARTICLE_EMITTER_MAX_CALL_STACK];
};
diff --git a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp
index 32f6b1e23..57ffa251a 100644
--- a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp
+++ b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp
@@ -6,6 +6,7 @@
#include "Engine/Particles/ParticleEmitter.h"
#include "Engine/Particles/ParticleEffect.h"
#include "Engine/Graphics/RenderTask.h"
+#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUBuffer.h"
#include "Engine/Graphics/GPUContext.h"
@@ -168,7 +169,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic
if (viewTask)
{
bindMeta.Buffers = viewTask->Buffers;
- bindMeta.CanSampleDepth = bindMeta.CanSampleGBuffer = true;
+ bindMeta.CanSampleDepth = bindMeta.CanSampleGBuffer = bindMeta.Buffers && bindMeta.Buffers->GetWidth() != 0;
}
else
{
@@ -179,7 +180,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic
for (int32 i = 0; i < data.Parameters.Count(); i++)
{
// Copy instance parameters values
- _params[i].SetValue(data.Parameters[i]);
+ _params[i].SetValue(data.Parameters.Get()[i]);
}
MaterialParamsLink link;
link.This = &_params;
diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp
index de142827c..22512481b 100644
--- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp
+++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp
@@ -40,7 +40,7 @@ namespace
ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAttribute(Node* caller, const StringView& name, ParticleAttribute::ValueTypes valueType, AccessMode mode)
{
// Find this attribute
- return AccessParticleAttribute(caller, ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.FindAttribute(name, valueType), mode);
+ return AccessParticleAttribute(caller, GetRootGraph()->Layout.FindAttribute(name, valueType), mode);
}
ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAttribute(Node* caller, int32 index, AccessMode mode)
@@ -55,7 +55,7 @@ ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAt
if (value.Variable.Type != VariantType::Null)
return value.Variable;
- auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[index];
+ auto& attribute = GetRootGraph()->Layout.Attributes[index];
const VariantType::Types type = GetValueType(attribute.ValueType);
// Generate local variable name that matches the attribute name for easier shader source debugging
@@ -77,7 +77,7 @@ ParticleEmitterGPUGenerator::Value ParticleEmitterGPUGenerator::AccessParticleAt
else if (_contextType == ParticleContextType::Initialize)
{
// Initialize with default value
- const Value defaultValue(((ParticleEmitterGraphGPU*)_graphStack.Peek())->AttributesDefaults[index]);
+ const Value defaultValue(GetRootGraph()->AttributesDefaults[index]);
value.Variable = writeLocal(type, defaultValue.Value, caller, localName);
}
else
@@ -251,10 +251,10 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va
{
const Char* format;
auto valueType = static_cast(node->Values[1].AsInt);
- const int32 attributeIndex = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.FindAttribute((StringView)node->Values[0], valueType);
+ const int32 attributeIndex = GetRootGraph()->Layout.FindAttribute((StringView)node->Values[0], valueType);
if (attributeIndex == -1)
return;
- auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[attributeIndex];
+ auto& attribute = GetRootGraph()->Layout.Attributes[attributeIndex];
const auto particleIndex = Value::Cast(tryGetValue(node->GetBox(1), Value(VariantType::Uint, TEXT("context.ParticleIndex"))), VariantType::Uint);
switch (valueType)
{
diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp
index 513252e80..024823293 100644
--- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp
+++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.cpp
@@ -30,10 +30,10 @@ void ParticleEmitterGraphGPU::ClearCache()
{
for (int32 i = 0; i < Nodes.Count(); i++)
{
- auto& node = Nodes[i];
+ ParticleEmitterGraphGPUNode& node = Nodes.Get()[i];
for (int32 j = 0; j < node.Boxes.Count(); j++)
{
- node.Boxes[j].Cache.Clear();
+ node.Boxes.Get()[j].Cache.Clear();
}
}
}
@@ -86,9 +86,7 @@ bool ParticleEmitterGPUGenerator::Generate(WriteStream& source, BytesContainer&
auto& layout = baseGraph->Layout;
_attributeValues.Resize(layout.Attributes.Count());
for (int32 i = 0; i < _attributeValues.Count(); i++)
- {
_attributeValues[i] = AttributeCache();
- }
_contextUsesKill = false;
// Cache attributes
@@ -112,7 +110,7 @@ bool ParticleEmitterGPUGenerator::Generate(WriteStream& source, BytesContainer&
for (int32 i = 0; i < baseGraph->InitModules.Count(); i++)
{
- ProcessModule(baseGraph->InitModules[i]);
+ ProcessModule(baseGraph->InitModules.Get()[i]);
}
WriteParticleAttributesWrites();
@@ -135,7 +133,7 @@ bool ParticleEmitterGPUGenerator::Generate(WriteStream& source, BytesContainer&
for (int32 i = 0; i < baseGraph->UpdateModules.Count(); i++)
{
- ProcessModule(baseGraph->UpdateModules[i]);
+ ProcessModule(baseGraph->UpdateModules.Get()[i]);
}
// Dead particles removal
@@ -321,22 +319,22 @@ void ParticleEmitterGPUGenerator::clearCache()
// Reset cached boxes values
for (int32 i = 0; i < _graphs.Count(); i++)
{
- _graphs[i]->ClearCache();
+ _graphs.Get()[i]->ClearCache();
}
- for (auto& e : _functions)
+ for (const auto& e : _functions)
{
- for (auto& node : e.Value->Nodes)
+ auto& nodes = ((ParticleEmitterGraphGPU*)e.Value)->Nodes;
+ for (int32 i = 0; i < nodes.Count(); i++)
{
+ ParticleEmitterGraphGPUNode& node = nodes.Get()[i];
for (int32 j = 0; j < node.Boxes.Count(); j++)
- node.Boxes[j].Cache.Clear();
+ node.Boxes.Get()[j].Cache.Clear();
}
}
// Reset cached attributes
for (int32 i = 0; i < _attributeValues.Count(); i++)
- {
- _attributeValues[i] = AttributeCache();
- }
+ _attributeValues.Get()[i] = AttributeCache();
_contextUsesKill = false;
}
@@ -344,10 +342,11 @@ void ParticleEmitterGPUGenerator::clearCache()
void ParticleEmitterGPUGenerator::WriteParticleAttributesWrites()
{
bool hadAnyWrite = false;
+ ParticleEmitterGraphGPU* graph = GetRootGraph();
for (int32 i = 0; i < _attributeValues.Count(); i++)
{
auto& value = _attributeValues[i];
- auto& attribute = ((ParticleEmitterGraphGPU*)_graphStack.Peek())->Layout.Attributes[i];
+ auto& attribute = graph->Layout.Attributes[i];
// Skip not used attributes or read-only attributes
if (value.Variable.Type == VariantType::Null || ((int)value.Access & (int)AccessMode::Write) == 0)
diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h
index 82f9c2c93..967dc0e0c 100644
--- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h
+++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h
@@ -5,7 +5,7 @@
///
/// Current GPU particles emitter shader version.
///
-#define PARTICLE_GPU_GRAPH_VERSION 9
+#define PARTICLE_GPU_GRAPH_VERSION 10
#if COMPILE_WITH_PARTICLE_GPU_GRAPH
@@ -94,7 +94,6 @@ public:
///
/// Gets the root graph.
///
- /// The base graph.
FORCE_INLINE ParticleEmitterGraphGPU* GetRootGraph() const
{
return _graphs.First();
@@ -154,12 +153,12 @@ private:
bool IsLocalSimulationSpace() const
{
- return ((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::Local;
+ return GetRootGraph()->SimulationSpace == ParticlesSimulationSpace::Local;
}
bool IsWorldSimulationSpace() const
{
- return ((ParticleEmitterGraphGPU*)_graphStack.Peek())->SimulationSpace == ParticlesSimulationSpace::World;
+ return GetRootGraph()->SimulationSpace == ParticlesSimulationSpace::World;
}
};
diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h
index f5946c226..91dae0ed2 100644
--- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h
+++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h
@@ -37,12 +37,12 @@ public:
///
/// Flag valid for used particle nodes that need per-particle data to evaluate its value (including dependant nodes linked to input boxes). Used to skip per-particle graph evaluation if graph uses the same value for all particles (eg. is not using per-particle seed or position node).
///
- bool UsesParticleData;
+ bool UsesParticleData = false;
///
/// Flag valid for used particle nodes that result in constant data (nothing random nor particle data).
///
- bool IsConstant;
+ bool IsConstant = true;
///
/// The cached particle attribute indices used by the simulation graph to access particle properties.
@@ -50,6 +50,8 @@ public:
int32 Attributes[PARTICLE_EMITTER_MAX_ATTRIBUTES_REFS_PER_NODE];
};
+void InitParticleEmitterFunctionCall(const Guid& assetId, AssetReference& asset, bool& usesParticleData, ParticleLayout& layout);
+
///
/// The Particle Emitter Graph used to simulate particles.
///
@@ -157,8 +159,6 @@ public:
if (node->Used)
return;
node->Used = true;
- node->UsesParticleData = false;
- node->IsConstant = true;
#define USE_ATTRIBUTE(name, valueType, slot) \
{ \
@@ -292,14 +292,11 @@ public:
case GRAPH_NODE_MAKE_TYPE(14, 214):
case GRAPH_NODE_MAKE_TYPE(14, 215):
case GRAPH_NODE_MAKE_TYPE(14, 216):
- {
node->IsConstant = false;
break;
- }
// Particle Emitter Function
case GRAPH_NODE_MAKE_TYPE(14, 300):
- node->Assets[0] = Content::LoadAsync((Guid)node->Values[0]);
- node->UsesParticleData = true; // TODO: analyze emitter function graph to detect if it's actually using any particle data at all (even from inputs after inline)
+ InitParticleEmitterFunctionCall((Guid)node->Values[0], node->Assets[0], node->UsesParticleData, Layout);
break;
// Particle Index
case GRAPH_NODE_MAKE_TYPE(14, 301):
@@ -564,7 +561,7 @@ public:
return true;
// Compute particle data layout and initialize used nodes (for only used nodes, start depth searching rom the modules)
- Layout.AddAttribute(TEXT("Position"), ParticleAttribute::ValueTypes::Float3);
+ //Layout.AddAttribute(TEXT("Position"), ParticleAttribute::ValueTypes::Float3);
#define PROCESS_MODULES(modules) for (int32 i = 0; i < modules.Count(); i++) { modules[i]->Used = false; InitializeNode(modules[i]); }
PROCESS_MODULES(SpawnModules);
PROCESS_MODULES(InitModules);
diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp
index 788a4263f..e461f332e 100644
--- a/Source/Engine/Particles/ParticleEffect.cpp
+++ b/Source/Engine/Particles/ParticleEffect.cpp
@@ -284,6 +284,7 @@ void ParticleEffect::UpdateSimulation(bool singleFrame)
void ParticleEffect::Play()
{
_isPlaying = true;
+ _isStopped = false;
}
void ParticleEffect::Pause()
@@ -293,6 +294,7 @@ void ParticleEffect::Pause()
void ParticleEffect::Stop()
{
+ _isStopped = true;
_isPlaying = false;
ResetSimulation();
}
@@ -448,7 +450,7 @@ void ParticleEffect::Update()
void ParticleEffect::UpdateExecuteInEditor()
{
// Auto-play in Editor
- if (!Editor::IsPlayMode)
+ if (!Editor::IsPlayMode && !_isStopped)
{
_isPlaying = true;
Update();
diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h
index 9e9792a4c..d3f0cc9d0 100644
--- a/Source/Engine/Particles/ParticleEffect.h
+++ b/Source/Engine/Particles/ParticleEffect.h
@@ -133,7 +133,7 @@ public:
///
/// The particle system instance that plays the particles simulation in the game.
///
-API_CLASS(Attributes="ActorContextMenu(\"New/Visuals/Particle Effects\"), ActorToolbox(\"Visuals\")")
+API_CLASS(Attributes="ActorContextMenu(\"New/Visuals/Particle Effect\"), ActorToolbox(\"Visuals\")")
class FLAXENGINE_API ParticleEffect : public Actor
{
DECLARE_SCENE_OBJECT(ParticleEffect);
@@ -185,6 +185,7 @@ private:
Array _parameters; // Cached for scripting API
Array _parametersOverrides; // Cached parameter modifications to be applied to the parameters
bool _isPlaying = false;
+ bool _isStopped = false;
public:
///
diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp
index 20e4c0490..ea1161c6b 100644
--- a/Source/Engine/Particles/ParticleEmitter.cpp
+++ b/Source/Engine/Particles/ParticleEmitter.cpp
@@ -19,6 +19,7 @@
#include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h"
#if BUILD_DEBUG
#include "Engine/Engine/Globals.h"
+#include "Engine/Scripting/BinaryModule.h"
#endif
#endif
#if COMPILE_WITH_GPU_PARTICLES
@@ -185,7 +186,9 @@ Asset::LoadResult ParticleEmitter::load()
#if BUILD_DEBUG && USE_EDITOR
// Dump generated shader source to the temporary file
+ BinaryModule::Locker.Lock();
source.SaveToFile(Globals::ProjectCacheFolder / TEXT("particle_emitter.txt"));
+ BinaryModule::Locker.Unlock();
#endif
// Encrypt source code
diff --git a/Source/Engine/Particles/ParticleEmitterFunction.cpp b/Source/Engine/Particles/ParticleEmitterFunction.cpp
index c91bc0d82..5abb4a434 100644
--- a/Source/Engine/Particles/ParticleEmitterFunction.cpp
+++ b/Source/Engine/Particles/ParticleEmitterFunction.cpp
@@ -9,6 +9,27 @@
#endif
#include "Engine/Content/Factories/BinaryAssetFactory.h"
+void InitParticleEmitterFunctionCall(const Guid& assetId, AssetReference& asset, bool& usesParticleData, ParticleLayout& layout)
+{
+ const auto function = Content::Load(assetId);
+ asset = function;
+ if (function)
+ {
+ // Insert any used particle data into the calling graph
+ for (const ParticleAttribute& e : function->Graph.Layout.Attributes)
+ {
+ if (layout.FindAttribute(e.Name, e.ValueType) == -1)
+ layout.AddAttribute(e.Name, e.ValueType);
+ }
+
+ // Detect if function needs to be evaluated per-particle
+ for (int32 i = 0; i < function->Outputs.Count() && !usesParticleData; i++)
+ {
+ usesParticleData = function->Graph.Nodes[function->Outputs.Get()[i]].UsesParticleData;
+ }
+ }
+}
+
REGISTER_BINARY_ASSET(ParticleEmitterFunction, "FlaxEngine.ParticleEmitterFunction", false);
ParticleEmitterFunction::ParticleEmitterFunction(const SpawnParams& params, const AssetInfo* info)
diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp
index 5850b6736..784f28c98 100644
--- a/Source/Engine/Particles/Particles.cpp
+++ b/Source/Engine/Particles/Particles.cpp
@@ -1318,6 +1318,8 @@ void ParticlesSystem::Job(int32 index)
emitterInstance.Buffer = nullptr;
}
}
+ // Stop playing effect.
+ effect->Stop();
return;
}
}
@@ -1332,7 +1334,8 @@ void ParticlesSystem::Job(int32 index)
auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get();
auto& data = instance.Emitters[track.AsEmitter.Index];
ASSERT(emitter && emitter->IsLoaded());
- ASSERT(emitter->Capacity != 0 && emitter->Graph.Layout.Size != 0);
+ if (emitter->Capacity == 0 || emitter->Graph.Layout.Size == 0)
+ continue;
PROFILE_CPU_ASSET(emitter);
// Calculate new time position
diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp
index 7c180d98d..b2db80b0b 100644
--- a/Source/Engine/Physics/Actors/Cloth.cpp
+++ b/Source/Engine/Physics/Actors/Cloth.cpp
@@ -95,14 +95,17 @@ void Cloth::SetFabric(const FabricSettings& value)
void Cloth::Rebuild()
{
#if WITH_CLOTH
- // Remove old
- if (IsDuringPlay())
- PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
- DestroyCloth();
+ if (_cloth)
+ {
+ // Remove old
+ if (IsDuringPlay())
+ PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
+ DestroyCloth();
+ }
// Create new
CreateCloth();
- if (IsDuringPlay())
+ if (IsDuringPlay() && _cloth)
PhysicsBackend::AddCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
#endif
}
diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h
index 4dc81ce3f..6137ec3b6 100644
--- a/Source/Engine/Physics/Actors/Cloth.h
+++ b/Source/Engine/Physics/Actors/Cloth.h
@@ -232,13 +232,13 @@ private:
public:
///
- /// Gets the mesh to use for the cloth simulation (single mesh from specific LOD).
+ /// Gets the mesh to use for the cloth simulation (single mesh from specific LOD). Always from the parent static or animated model actor.
///
API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Cloth\")")
ModelInstanceActor::MeshReference GetMesh() const;
///
- /// Sets the mesh to use for the cloth simulation (single mesh from specific LOD).
+ /// Sets the mesh to use for the cloth simulation (single mesh from specific LOD). Always from the parent static or animated model actor.
///
API_PROPERTY() void SetMesh(const ModelInstanceActor::MeshReference& value);
diff --git a/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp b/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp
index 02645943c..f2ab5d8a2 100644
--- a/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp
+++ b/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp
@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "PhysicsColliderActor.h"
-#include "Engine/Scripting/Script.h"
#include "RigidBody.h"
PhysicsColliderActor::PhysicsColliderActor(const SpawnParams& params)
diff --git a/Source/Engine/Physics/Actors/RigidBody.h b/Source/Engine/Physics/Actors/RigidBody.h
index e203e70fc..e7b929483 100644
--- a/Source/Engine/Physics/Actors/RigidBody.h
+++ b/Source/Engine/Physics/Actors/RigidBody.h
@@ -41,19 +41,8 @@ protected:
public:
///
- /// Enables kinematic mode for the rigidbody.
+ /// Enables kinematic mode for the rigidbody. Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum. They are considered to have infinite mass and can push regular dynamic actors out of the way. Kinematics will not collide with static or other kinematic objects but are great for moving platforms or characters, where direct motion control is desired.
///
- ///
- /// Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum.
- /// They are considered to have infinite mass and can push regular dynamic actors out of the way.
- /// Kinematics will not collide with static or other kinematic objects.
- ///
- /// Kinematic rigidbodies are great for moving platforms or characters, where direct motion control is desired.
- ///
- ///
- /// Kinematic rigidbodies are incompatible with CCD.
- ///
- ///
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(false), EditorDisplay(\"Rigid Body\")")
FORCE_INLINE bool GetIsKinematic() const
{
@@ -61,26 +50,13 @@ public:
}
///
- /// Enables kinematic mode for the rigidbody.
+ /// Enables kinematic mode for the rigidbody. Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum. They are considered to have infinite mass and can push regular dynamic actors out of the way. Kinematics will not collide with static or other kinematic objects but are great for moving platforms or characters, where direct motion control is desired.
///
- ///
- /// Kinematic rigidbodies are special dynamic actors that are not influenced by forces(such as gravity), and have no momentum.
- /// They are considered to have infinite mass and can push regular dynamic actors out of the way.
- /// Kinematics will not collide with static or other kinematic objects.
- ///
- /// Kinematic rigidbodies are great for moving platforms or characters, where direct motion control is desired.
- ///
- ///
- /// Kinematic rigidbodies are incompatible with CCD.
- ///
- ///
- /// The value.
API_PROPERTY() void SetIsKinematic(const bool value);
///
- /// Gets the 'drag' force added to reduce linear movement.
+ /// Gets the 'drag' force added to reduce linear movement. Linear damping can be used to slow down an object. The higher the drag the more the object slows down.
///
- /// Linear damping can be used to slow down an object. The higher the drag the more the object slows down.
API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(0.01f), Limit(0), EditorDisplay(\"Rigid Body\")")
FORCE_INLINE float GetLinearDamping() const
{
@@ -88,16 +64,13 @@ public:
}
///
- /// Sets the 'drag' force added to reduce linear movement.
+ /// Sets the 'drag' force added to reduce linear movement. Linear damping can be used to slow down an object. The higher the drag the more the object slows down.
///
- /// Linear damping can be used to slow down an object. The higher the drag the more the object slows down.
- /// The value.
API_PROPERTY() void SetLinearDamping(float value);
///
- /// Gets the 'drag' force added to reduce angular movement.
+ /// Gets the 'drag' force added to reduce angular movement. Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down.
///
- /// Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down.
API_PROPERTY(Attributes="EditorOrder(70), DefaultValue(0.05f), Limit(0), EditorDisplay(\"Rigid Body\")")
FORCE_INLINE float GetAngularDamping() const
{
@@ -105,9 +78,8 @@ public:
}
///
- /// Sets the 'drag' force added to reduce angular movement.
+ /// Sets the 'drag' force added to reduce angular movement. Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down.
///
- /// Angular damping can be used to slow down the rotation of an object. The higher the drag the more the rotation slows down.
/// The value.
API_PROPERTY() void SetAngularDamping(float value);
@@ -123,7 +95,6 @@ public:
///
/// If true simulation and collisions detection will be enabled for the rigidbody.
///
- /// The value.
API_PROPERTY() void SetEnableSimulation(bool value);
///
@@ -138,7 +109,6 @@ public:
///
/// If true Continuous Collision Detection (CCD) will be used for this component.
///
- /// The value.
API_PROPERTY() void SetUseCCD(const bool value);
///
@@ -153,7 +123,6 @@ public:
///
/// If object should have the force of gravity applied.
///
- /// The value.
API_PROPERTY() void SetEnableGravity(bool value);
///
@@ -168,7 +137,6 @@ public:
///
/// If object should start awake, or if it should initially be sleeping.
///
- /// The value.
API_PROPERTY() void SetStartAwake(bool value);
///
@@ -183,16 +151,11 @@ public:
///
/// If true, it will update mass when actor scale changes.
///
- /// The value.
API_PROPERTY() void SetUpdateMassWhenScaleChanges(bool value);
///
- /// Gets the maximum angular velocity that a simulated object can achieve.
+ /// Gets the maximum angular velocity that a simulated object can achieve. The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies. Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody.
///
- ///
- /// The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies.
- /// Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody.
- ///
API_PROPERTY(Attributes="EditorOrder(90), DefaultValue(7.0f), Limit(0), EditorDisplay(\"Rigid Body\")")
FORCE_INLINE float GetMaxAngularVelocity() const
{
@@ -200,13 +163,8 @@ public:
}
///
- /// Sets the maximum angular velocity that a simulated object can achieve.
+ /// Sets the maximum angular velocity that a simulated object can achieve. The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies. Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody.
///
- ///
- /// The angular velocity of rigidbodies is clamped to MaxAngularVelocity to avoid numerical instability with fast rotating bodies.
- /// Because this may prevent intentional fast rotations on objects such as wheels, you can override this value per rigidbody.
- ///
- /// The value.
API_PROPERTY() void SetMaxAngularVelocity(float value);
///
@@ -218,7 +176,6 @@ public:
///
/// Override the auto computed mass.
///
- /// The value.
API_PROPERTY() void SetOverrideMass(bool value);
///
@@ -230,8 +187,6 @@ public:
///
/// Sets the mass value measured in kilograms (use override value only if OverrideMass is checked).
///
- /// If set auto enables mass override.
- /// The value.
API_PROPERTY() void SetMass(float value);
///
@@ -243,7 +198,6 @@ public:
///
/// Sets the per-instance scaling of the mass.
///
- /// The value.
API_PROPERTY() void SetMassScale(float value);
///
@@ -258,7 +212,6 @@ public:
///
/// Sets the user specified offset for the center of mass of this object, from the calculated location.
///
- /// The value.
API_PROPERTY() void SetCenterOfMassOffset(const Float3& value);
///
@@ -273,28 +226,27 @@ public:
///
/// Sets the object movement constraint flags that define degrees of freedom are allowed for the simulation of object.
///
- /// The value.
API_PROPERTY() void SetConstraints(const RigidbodyConstraints value);
public:
///
/// Gets the linear velocity of the rigidbody.
///
- /// It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour.
+ /// It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour.
API_PROPERTY(Attributes="HideInEditor")
Vector3 GetLinearVelocity() const;
///
/// Sets the linear velocity of the rigidbody.
///
- /// It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour.
+ /// It's used mostly to get the current velocity. Manual modifications may result in unrealistic behaviour.
/// The value.
API_PROPERTY() void SetLinearVelocity(const Vector3& value) const;
///
/// Gets the angular velocity of the rigidbody measured in radians per second.
///
- /// It's used mostly to get the current angular velocity. Manual modifications may result in unrealistic behaviour.
+ /// It's used mostly to get the current angular velocity. Manual modifications may result in unrealistic behaviour.
API_PROPERTY(Attributes="HideInEditor")
Vector3 GetAngularVelocity() const;
diff --git a/Source/Engine/Physics/Colliders/BoxCollider.cpp b/Source/Engine/Physics/Colliders/BoxCollider.cpp
index 90f0dab38..fde3b4632 100644
--- a/Source/Engine/Physics/Colliders/BoxCollider.cpp
+++ b/Source/Engine/Physics/Colliders/BoxCollider.cpp
@@ -81,6 +81,13 @@ void BoxCollider::OnDebugDrawSelected()
const Color color = Color::GreenYellow;
DEBUG_DRAW_WIRE_BOX(_bounds, color * 0.3f, 0, false);
+ if (_contactOffset > 0)
+ {
+ OrientedBoundingBox contactBounds = _bounds;
+ contactBounds.Extents += Vector3(_contactOffset) / contactBounds.Transformation.Scale;
+ DEBUG_DRAW_WIRE_BOX(contactBounds, Color::Blue.AlphaMultiplied(0.2f), 0, false);
+ }
+
Vector3 corners[8];
_bounds.GetCorners(corners);
const float margin = 1.0f;
diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp
index ba133e8b6..98346b0a9 100644
--- a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp
+++ b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp
@@ -42,27 +42,33 @@ void CapsuleCollider::DrawPhysicsDebug(RenderView& view)
const BoundingSphere sphere(_sphere.Center - view.Origin, _sphere.Radius);
if (!view.CullingFrustum.Intersects(sphere))
return;
- Quaternion rot;
- Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rot);
+ Quaternion rotation;
+ Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rotation);
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float minSize = 0.001f;
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
if (view.Mode == ViewMode::PhysicsColliders && !GetIsTrigger())
- DEBUG_DRAW_TUBE(_transform.LocalToWorld(_center), rot, radius, height, _staticActor ? Color::CornflowerBlue : Color::Orchid, 0, true);
+ DEBUG_DRAW_TUBE(_transform.LocalToWorld(_center), rotation, radius, height, _staticActor ? Color::CornflowerBlue : Color::Orchid, 0, true);
else
- DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rot, radius, height, Color::GreenYellow * 0.8f, 0, true);
+ DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rotation, radius, height, Color::GreenYellow * 0.8f, 0, true);
}
void CapsuleCollider::OnDebugDrawSelected()
{
- Quaternion rot;
- Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rot);
+ Quaternion rotation;
+ Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rotation);
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float minSize = 0.001f;
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
- DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), rot, radius, height, Color::GreenYellow, 0, false);
+ const Vector3 position = _transform.LocalToWorld(_center);
+ DEBUG_DRAW_WIRE_TUBE(position, rotation, radius, height, Color::GreenYellow, 0, false);
+
+ if (_contactOffset > 0)
+ {
+ DEBUG_DRAW_WIRE_TUBE(position, rotation, radius + _contactOffset, height, Color::Blue.AlphaMultiplied(0.2f), 0, false);
+ }
// Base
Collider::OnDebugDrawSelected();
diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp
index 59521e51c..41ee95d04 100644
--- a/Source/Engine/Physics/Colliders/CharacterController.cpp
+++ b/Source/Engine/Physics/Colliders/CharacterController.cpp
@@ -23,6 +23,7 @@ CharacterController::CharacterController(const SpawnParams& params)
, _nonWalkableMode(NonWalkableModes::PreventClimbing)
, _lastFlags(CollisionFlags::None)
{
+ _contactOffset = 10.0f;
}
float CharacterController::GetRadius() const
diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp
index 49de1f799..6c292bf20 100644
--- a/Source/Engine/Physics/Colliders/Collider.cpp
+++ b/Source/Engine/Physics/Colliders/Collider.cpp
@@ -19,7 +19,7 @@ Collider::Collider(const SpawnParams& params)
, _shape(nullptr)
, _staticActor(nullptr)
, _cachedScale(1.0f)
- , _contactOffset(10.0f)
+ , _contactOffset(2.0f)
{
Material.Changed.Bind(this);
}
diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h
index 6b9dfcb9d..d3bae9407 100644
--- a/Source/Engine/Physics/Colliders/Collider.h
+++ b/Source/Engine/Physics/Colliders/Collider.h
@@ -35,11 +35,8 @@ public:
void* GetPhysicsShape() const;
///
- /// Gets the 'IsTrigger' flag.
+ /// Gets the 'IsTrigger' flag. A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter and OnTriggerExit message when a rigidbody enters or exits the trigger volume.
///
- ///
- /// A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter, OnTriggerExit and OnTriggerStay message when a rigidbody enters or exits the trigger volume.
- ///
API_PROPERTY(Attributes="EditorOrder(0), DefaultValue(false), EditorDisplay(\"Collider\")")
FORCE_INLINE bool GetIsTrigger() const
{
@@ -47,11 +44,8 @@ public:
}
///
- /// Sets the `IsTrigger` flag. A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter, OnTriggerExit and OnTriggerStay message when a rigidbody enters or exits the trigger volume.
+ /// Sets the `IsTrigger` flag. A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter and OnTriggerExit message when a rigidbody enters or exits the trigger volume.
///
- ///
- /// A trigger doesn't register a collision with an incoming Rigidbody. Instead, it sends OnTriggerEnter, OnTriggerExit and OnTriggerStay message when a rigidbody enters or exits the trigger volume.
- ///
API_PROPERTY() void SetIsTrigger(bool value);
///
@@ -69,23 +63,17 @@ public:
API_PROPERTY() void SetCenter(const Vector3& value);
///
- /// Gets the contact offset.
+ /// Gets the contact offset. Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated.
///
- ///
- /// Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated.
- ///
- API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(10.0f), Limit(0, 100), EditorDisplay(\"Collider\")")
+ API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(2.0f), Limit(0, 100), EditorDisplay(\"Collider\")")
FORCE_INLINE float GetContactOffset() const
{
return _contactOffset;
}
///
- /// Sets the contact offset.
+ /// Sets the contact offset. Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated.
///
- ///
- /// Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated.
- ///
API_PROPERTY() void SetContactOffset(float value);
///
diff --git a/Source/Engine/Physics/Colliders/SphereCollider.cpp b/Source/Engine/Physics/Colliders/SphereCollider.cpp
index e163484e8..fa2fb1cca 100644
--- a/Source/Engine/Physics/Colliders/SphereCollider.cpp
+++ b/Source/Engine/Physics/Colliders/SphereCollider.cpp
@@ -40,6 +40,13 @@ void SphereCollider::OnDebugDrawSelected()
{
DEBUG_DRAW_WIRE_SPHERE(_sphere, Color::GreenYellow, 0, false);
+ if (_contactOffset > 0)
+ {
+ BoundingSphere contactBounds = _sphere;
+ contactBounds.Radius += _contactOffset;
+ DEBUG_DRAW_WIRE_SPHERE(contactBounds, Color::Blue.AlphaMultiplied(0.2f), 0, false);
+ }
+
// Base
Collider::OnDebugDrawSelected();
}
diff --git a/Source/Engine/Physics/CollisionCooking.cpp b/Source/Engine/Physics/CollisionCooking.cpp
index dc2c42584..4c8cffdcc 100644
--- a/Source/Engine/Physics/CollisionCooking.cpp
+++ b/Source/Engine/Physics/CollisionCooking.cpp
@@ -127,6 +127,8 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali
const auto& mesh = *meshes[i];
if ((arg.MaterialSlotsMask & (1 << mesh.GetMaterialSlotIndex())) == 0)
continue;
+ if (mesh.GetVertexCount() == 0)
+ continue;
int32 count;
if (mesh.DownloadDataCPU(MeshBufferType::Vertex0, vertexBuffers[i], count))
@@ -159,6 +161,8 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali
const auto& mesh = *meshes[i];
if ((arg.MaterialSlotsMask & (1 << mesh.GetMaterialSlotIndex())) == 0)
continue;
+ if (mesh.GetVertexCount() == 0)
+ continue;
auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, vertexBuffers[i]);
if (task == nullptr)
@@ -208,6 +212,8 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali
const int32 firstVertexIndex = vertexCounter;
const int32 vertexCount = vertexCounts[i];
+ if (vertexCount == 0)
+ continue;
Platform::MemoryCopy(finalVertexData.Get() + firstVertexIndex, vData.Get(), vertexCount * sizeof(Float3));
vertexCounter += vertexCount;
diff --git a/Source/Engine/Physics/CollisionData.cpp b/Source/Engine/Physics/CollisionData.cpp
index 36f28c111..54a49671d 100644
--- a/Source/Engine/Physics/CollisionData.cpp
+++ b/Source/Engine/Physics/CollisionData.cpp
@@ -23,12 +23,16 @@ CollisionData::CollisionData(const SpawnParams& params, const AssetInfo* info)
bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, int32 modelLodIndex, uint32 materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
{
- // Validate state
if (!IsVirtual())
{
LOG(Warning, "Only virtual assets can be modified at runtime.");
return true;
}
+ if (IsInMainThread() && modelObj && modelObj->IsVirtual())
+ {
+ LOG(Error, "Cannot cook collision data for virtual models on a main thread (virtual models data is stored on GPU only). Use thread pool or async task.");
+ return true;
+ }
// Prepare
CollisionCooking::Argument arg;
@@ -43,18 +47,12 @@ bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, i
SerializedOptions options;
BytesContainer outputData;
if (CollisionCooking::CookCollision(arg, options, outputData))
- {
return true;
- }
-
- // Clear state
- unload(true);
// Load data
+ unload(true);
if (load(&options, outputData.Get(), outputData.Length()) != LoadResult::Ok)
- {
return true;
- }
// Mark as loaded (eg. Mesh Colliders using this asset will update shape for physics simulation)
onLoaded();
diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
index 7c9355099..5781e4641 100644
--- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
+++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
@@ -117,9 +117,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
{
// Skip sending events to removed actors
if (pairHeader.flags & (PxContactPairHeaderFlag::eREMOVED_ACTOR_0 | PxContactPairHeaderFlag::eREMOVED_ACTOR_1))
- {
return;
- }
Collision c;
PxContactPairExtraDataIterator j(pairHeader.extraDataStream, pairHeader.extraDataStreamSize);
@@ -132,13 +130,18 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
const PxReal* impulses = pair.contactImpulses;
//const PxU32 flippedContacts = (pair.flags & PxContactPairFlag::eINTERNAL_CONTACTS_ARE_FLIPPED);
- const PxU32 hasImpulses = (pair.flags & PxContactPairFlag::eINTERNAL_HAS_IMPULSES);
+ const bool hasImpulses = pair.flags.isSet(PxContactPairFlag::eINTERNAL_HAS_IMPULSES);
+ const bool hasPostVelocities = !pair.flags.isSet(PxContactPairFlag::eACTOR_PAIR_LOST_TOUCH);
PxU32 nbContacts = 0;
PxVec3 totalImpulse(0.0f);
c.ThisActor = static_cast(pair.shapes[0]->userData);
c.OtherActor = static_cast(pair.shapes[1]->userData);
- ASSERT_LOW_LAYER(c.ThisActor && c.OtherActor);
+ if (c.ThisActor == nullptr || c.OtherActor == nullptr)
+ {
+ // One of the actors was deleted (eg. via RigidBody destroyed by gameplay) then skip processing this collision
+ continue;
+ }
// Extract contact points
while (i.hasNextPatch())
@@ -166,7 +169,8 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
}
// Extract velocities
- if (j.nextItemSet())
+ c.ThisVelocity = c.OtherVelocity = Vector3::Zero;
+ if (hasPostVelocities && j.nextItemSet())
{
ASSERT(j.contactPairIndex == pairIndex);
if (j.postSolverVelocity)
@@ -177,10 +181,6 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
c.ThisVelocity = P2C(linearVelocityActor0);
c.OtherVelocity = P2C(linearVelocityActor1);
}
- else
- {
- c.ThisVelocity = c.OtherVelocity = Vector3::Zero;
- }
}
c.ContactsCount = nbContacts;
@@ -195,6 +195,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
RemovedCollisions.Add(c);
}
}
+ //ASSERT(!j.nextItemSet());
}
void SimulationEventCallback::onTrigger(PxTriggerPair* pairs, PxU32 count)
diff --git a/Source/Engine/Platform/Android/AndroidFileSystem.cpp b/Source/Engine/Platform/Android/AndroidFileSystem.cpp
index 78fa8953d..a04fc0229 100644
--- a/Source/Engine/Platform/Android/AndroidFileSystem.cpp
+++ b/Source/Engine/Platform/Android/AndroidFileSystem.cpp
@@ -7,6 +7,7 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/TimeSpan.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Utilities/StringConverter.h"
diff --git a/Source/Engine/Platform/Android/AndroidPlatform.cpp b/Source/Engine/Platform/Android/AndroidPlatform.cpp
index c61e2aadd..c844ab81d 100644
--- a/Source/Engine/Platform/Android/AndroidPlatform.cpp
+++ b/Source/Engine/Platform/Android/AndroidPlatform.cpp
@@ -665,6 +665,7 @@ void AndroidPlatform::PreInit(android_app* app)
app->onInputEvent = OnAppInput;
ANativeActivity_setWindowFlags(app->activity, AWINDOW_FLAG_KEEP_SCREEN_ON | AWINDOW_FLAG_TURN_SCREEN_ON | AWINDOW_FLAG_FULLSCREEN | AWINDOW_FLAG_DISMISS_KEYGUARD, 0);
ANativeActivity_setWindowFormat(app->activity, WINDOW_FORMAT_RGBA_8888);
+ pthread_setname_np(pthread_self(), "Main");
}
bool AndroidPlatform::Is64BitPlatform()
diff --git a/Source/Engine/Platform/Apple/AppleFileSystem.cpp b/Source/Engine/Platform/Apple/AppleFileSystem.cpp
index cc18fc3ed..9e306019a 100644
--- a/Source/Engine/Platform/Apple/AppleFileSystem.cpp
+++ b/Source/Engine/Platform/Apple/AppleFileSystem.cpp
@@ -8,6 +8,7 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/TimeSpan.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Log.h"
#include "Engine/Utilities/StringConverter.h"
diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp
index bf79c95c2..c939c1af2 100644
--- a/Source/Engine/Platform/Apple/ApplePlatform.cpp
+++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp
@@ -19,6 +19,7 @@
#include "Engine/Platform/StringUtils.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/Clipboard.h"
+#include "Engine/Platform/Thread.h"
#include "Engine/Platform/IGuiData.h"
#include "Engine/Platform/Base/PlatformUtils.h"
#include "Engine/Utilities/StringConverter.h"
@@ -48,6 +49,7 @@ CPUInfo Cpu;
String UserLocale;
double SecondsPerCycle;
NSAutoreleasePool* AutoreleasePool = nullptr;
+int32 AutoreleasePoolInterval = 0;
float ApplePlatform::ScreenScale = 1.0f;
@@ -175,12 +177,21 @@ uint64 ApplePlatform::GetCurrentThreadID()
void ApplePlatform::SetThreadPriority(ThreadPriority priority)
{
- // TODO: impl this
+ struct sched_param sched;
+ Platform::MemoryClear(&sched, sizeof(struct sched_param));
+ int32 policy = SCHED_RR;
+ pthread_getschedparam(pthread_self(), &policy, &sched);
+ sched.sched_priority = AppleThread::GetAppleThreadPriority(priority);
+ pthread_setschedparam(pthread_self(), policy, &sched);
}
void ApplePlatform::SetThreadAffinityMask(uint64 affinityMask)
{
- // TODO: impl this
+#if PLATFORM_MAC
+ thread_affinity_policy policy;
+ policy.affinity_tag = affinityMask;
+ thread_policy_set(pthread_mach_thread_np(pthread_self()), THREAD_AFFINITY_POLICY, (integer_t*)&policy, THREAD_AFFINITY_POLICY_COUNT);
+#endif
}
void ApplePlatform::Sleep(int32 milliseconds)
@@ -245,13 +256,40 @@ void ApplePlatform::GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int3
millisecond = time.tv_usec / 1000;
}
+#if !BUILD_RELEASE
+
void ApplePlatform::Log(const StringView& msg)
{
-#if !BUILD_RELEASE && !USE_EDITOR
+#if !USE_EDITOR
NSLog(@"%s", StringAsANSI<>(*msg, msg.Length()).Get());
#endif
}
+bool ApplePlatform::IsDebuggerPresent()
+{
+ // Reference: https://developer.apple.com/library/archive/qa/qa1361/_index.html
+ int mib[4];
+ struct kinfo_proc info;
+
+ // Initialize the flags so that, if sysctl fails for some bizarre reason, we get a predictable result
+ info.kp_proc.p_flag = 0;
+
+ // Initialize mib, which tells sysctl the info we want, in this case we're looking for information about a specific process ID
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = getpid();
+
+ // Call sysctl
+ size_t size = sizeof(info);
+ sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
+
+ // We're being debugged if the P_TRACED flag is set
+ return ((info.kp_proc.p_flag & P_TRACED) != 0);
+}
+
+#endif
+
bool ApplePlatform::Init()
{
if (UnixPlatform::Init())
@@ -332,9 +370,13 @@ bool ApplePlatform::Init()
void ApplePlatform::Tick()
{
- // TODO: do it once every X fames
- [AutoreleasePool drain];
- AutoreleasePool = [[NSAutoreleasePool alloc] init];
+ AutoreleasePoolInterval++;
+ if (AutoreleasePoolInterval >= 60)
+ {
+ AutoreleasePoolInterval = 0;
+ [AutoreleasePool drain];
+ AutoreleasePool = [[NSAutoreleasePool alloc] init];
+ }
}
void ApplePlatform::BeforeExit()
@@ -343,6 +385,11 @@ void ApplePlatform::BeforeExit()
void ApplePlatform::Exit()
{
+ if (AutoreleasePool)
+ {
+ [AutoreleasePool drain];
+ AutoreleasePool = nullptr;
+ }
}
void ApplePlatform::SetHighDpiAwarenessEnabled(bool enable)
@@ -369,9 +416,7 @@ bool ApplePlatform::GetHasFocus()
if (window->IsFocused())
return true;
}
-
- // Default to true if has no windows open
- return WindowsManager::Windows.IsEmpty();
+ return false;
}
void ApplePlatform::CreateGuid(Guid& result)
diff --git a/Source/Engine/Platform/Apple/ApplePlatform.h b/Source/Engine/Platform/Apple/ApplePlatform.h
index f1148d0b6..94b6534d6 100644
--- a/Source/Engine/Platform/Apple/ApplePlatform.h
+++ b/Source/Engine/Platform/Apple/ApplePlatform.h
@@ -79,7 +79,10 @@ public:
static uint64 GetClockFrequency();
static void GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
static void GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
+#if !BUILD_RELEASE
static void Log(const StringView& msg);
+ static bool IsDebuggerPresent();
+#endif
static bool Init();
static void Tick();
static void BeforeExit();
diff --git a/Source/Engine/Platform/Apple/AppleThread.h b/Source/Engine/Platform/Apple/AppleThread.h
index 43d9d2966..a578092b6 100644
--- a/Source/Engine/Platform/Apple/AppleThread.h
+++ b/Source/Engine/Platform/Apple/AppleThread.h
@@ -40,10 +40,7 @@ public:
return (AppleThread*)Setup(New(runnable, name, priority), stackSize);
}
-protected:
-
- // [UnixThread]
- int32 GetThreadPriority(ThreadPriority priority) override
+ static int32 GetAppleThreadPriority(ThreadPriority priority)
{
switch (priority)
{
@@ -60,6 +57,14 @@ protected:
}
return 31;
}
+
+protected:
+
+ // [UnixThread]
+ int32 GetThreadPriority(ThreadPriority priority) override
+ {
+ return GetAppleThreadPriority(priority);
+ }
int32 Start(pthread_attr_t& attr) override
{
return pthread_create(&_thread, &attr, ThreadProc, this);
diff --git a/Source/Engine/Platform/Base/DragDropHelper.h b/Source/Engine/Platform/Base/DragDropHelper.h
new file mode 100644
index 000000000..a5382afd6
--- /dev/null
+++ b/Source/Engine/Platform/Base/DragDropHelper.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Threading/ThreadPoolTask.h"
+#include "Engine/Threading/ThreadPool.h"
+#include "Engine/Scripting/Scripting.h"
+#include "Engine/Scripting/ManagedCLR/MDomain.h"
+#include "Engine/Engine/Engine.h"
+#include "Engine/Platform/Platform.h"
+#if USE_EDITOR
+#if !COMPILE_WITH_DEBUG_DRAW
+#define COMPILE_WITH_DEBUG_DRAW 1
+#define COMPILE_WITH_DEBUG_DRAW_HACK
+#endif
+#include "Engine/Debug/DebugDraw.h"
+#ifdef COMPILE_WITH_DEBUG_DRAW_HACK
+#undef COMPILE_WITH_DEBUG_DRAW_HACK
+#undef COMPILE_WITH_DEBUG_DRAW
+#define COMPILE_WITH_DEBUG_DRAW 0
+#endif
+#endif
+
+///
+/// Async DoDragDrop helper (used for rendering frames during main thread stall).
+///
+class DoDragDropJob : public ThreadPoolTask
+{
+public:
+ int64 ExitFlag = 0;
+
+ // [ThreadPoolTask]
+ bool Run() override
+ {
+ Scripting::GetScriptsDomain()->Dispatch();
+ while (Platform::AtomicRead(&ExitFlag) == 0)
+ {
+#if USE_EDITOR
+ // Flush any single-frame shapes to prevent memory leaking (eg. via terrain collision debug during scene drawing with PhysicsColliders or PhysicsDebug flag)
+ DebugDraw::UpdateContext(nullptr, 0.0f);
+#endif
+ Engine::OnDraw();
+ Platform::Sleep(20);
+ }
+ return false;
+ }
+};
diff --git a/Source/Engine/Platform/Base/FileBase.h b/Source/Engine/Platform/Base/FileBase.h
index c6f76c610..5587e20a9 100644
--- a/Source/Engine/Platform/Base/FileBase.h
+++ b/Source/Engine/Platform/Base/FileBase.h
@@ -7,7 +7,6 @@
#include "Engine/Core/NonCopyable.h"
#include "Engine/Core/Types/BaseTypes.h"
#include "Engine/Core/Types/DateTime.h"
-#include "Engine/Core/Collections/Array.h"
class StringBuilder;
template
@@ -16,17 +15,51 @@ class DataContainer;
///
/// Specifies how the operating system should open a file.
///
-DECLARE_ENUM_FLAGS_5(FileMode, uint32, CreateAlways, 2, CreateNew, 1, OpenAlways, 4, OpenExisting, 3, TruncateExisting, 5);
+enum class FileMode : uint32
+{
+ // Creates a new file, only if it does not already exist.
+ CreateNew = 1,
+ // Creates a new file, always.
+ CreateAlways = 2,
+ // Opens a file, only if it exists. Fails if file doesn't exist.
+ OpenExisting = 3,
+ // Opens a file, always.
+ OpenAlways = 4,
+ // Opens a file and truncates it so that its size is zero bytes, only if it exists. Fails if file doesn't exist.
+ TruncateExisting = 5,
+};
///
/// Defines constants for read, write, or read/write access to a file.
///
-DECLARE_ENUM_FLAGS_3(FileAccess, uint32, Read, 0x80000000, Write, 0x40000000, ReadWrite, (uint32)FileAccess::Read | (uint32)FileAccess::Write);
+enum class FileAccess : uint32
+{
+ // Enables reading data from the file.
+ Read = 0x80000000,
+ // Enables writing data to the file.
+ Write = 0x40000000,
+ // Enables both data read and write operations on the file.
+ ReadWrite = Read | Write,
+};
///
/// Contains constants for controlling the kind of access other objects can have to the same file.
///
-DECLARE_ENUM_FLAGS_6(FileShare, uint32, Delete, 0x00000004, None, 0x00000000, Read, 0x00000001, Write, 0x00000002, ReadWrite, (uint32)FileShare::Read | (uint32)FileShare::Write, All, (uint32)FileShare::ReadWrite | (uint32)FileShare::Delete);
+enum class FileShare : uint32
+{
+ // Prevents any operations on the file file it's opened.
+ None = 0x00000000,
+ // Allows read operations on a file.
+ Read = 0x00000001,
+ // Allows write operations on a file.
+ Write = 0x00000002,
+ // Allows delete operations on a file.
+ Delete = 0x00000004,
+ // Allows read and write operations on a file.
+ ReadWrite = Read | Write,
+ // Allows any operations on a file.
+ All = ReadWrite | Delete,
+};
///
/// The base class for file objects.
@@ -34,7 +67,6 @@ DECLARE_ENUM_FLAGS_6(FileShare, uint32, Delete, 0x00000004, None, 0x00000000, Re
class FLAXENGINE_API FileBase : public NonCopyable
{
public:
-
///
/// Finalizes an instance of the class.
///
@@ -43,7 +75,6 @@ public:
}
public:
-
///
/// Reads data from a file.
///
@@ -68,7 +99,6 @@ public:
virtual void Close() = 0;
public:
-
///
/// Gets size of the file (in bytes).
///
@@ -100,14 +130,13 @@ public:
virtual bool IsOpened() const = 0;
public:
-
static bool ReadAllBytes(const StringView& path, byte* data, int32 length);
- static bool ReadAllBytes(const StringView& path, Array& data);
+ static bool ReadAllBytes(const StringView& path, Array& data);
static bool ReadAllBytes(const StringView& path, DataContainer& data);
static bool ReadAllText(const StringView& path, String& data);
static bool ReadAllText(const StringView& path, StringAnsi& data);
static bool WriteAllBytes(const StringView& path, const byte* data, int32 length);
- static bool WriteAllBytes(const StringView& path, const Array& data);
+ static bool WriteAllBytes(const StringView& path, const Array& data);
static bool WriteAllText(const StringView& path, const String& data, Encoding encoding);
static bool WriteAllText(const StringView& path, const StringBuilder& data, Encoding encoding);
static bool WriteAllText(const StringView& path, const Char* data, int32 length, Encoding encoding);
diff --git a/Source/Engine/Platform/Base/FileSystemBase.cpp b/Source/Engine/Platform/Base/FileSystemBase.cpp
index f2f45a6d4..3d0c053d5 100644
--- a/Source/Engine/Platform/Base/FileSystemBase.cpp
+++ b/Source/Engine/Platform/Base/FileSystemBase.cpp
@@ -5,6 +5,7 @@
#include "Engine/Core/Types/Guid.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringView.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Engine/Globals.h"
diff --git a/Source/Engine/Platform/Base/WindowsManager.cpp b/Source/Engine/Platform/Base/WindowsManager.cpp
index 0d58008d8..34381b3d2 100644
--- a/Source/Engine/Platform/Base/WindowsManager.cpp
+++ b/Source/Engine/Platform/Base/WindowsManager.cpp
@@ -59,9 +59,11 @@ void WindowsManagerService::Update()
// Update windows
const float deltaTime = Time::Update.UnscaledDeltaTime.GetTotalSeconds();
WindowsManager::WindowsLocker.Lock();
- for (Window* win : WindowsManager::Windows)
+ Array> windows;
+ windows.Add(WindowsManager::Windows);
+ for (Window* win : windows)
{
- if (win && win->IsVisible())
+ if (win->IsVisible())
win->OnUpdate(deltaTime);
}
WindowsManager::WindowsLocker.Unlock();
@@ -71,7 +73,8 @@ void WindowsManagerService::Dispose()
{
// Close remaining windows
WindowsManager::WindowsLocker.Lock();
- auto windows = WindowsManager::Windows;
+ Array> windows;
+ windows.Add(WindowsManager::Windows);
for (Window* win : windows)
{
win->Close(ClosingReason::EngineExit);
diff --git a/Source/Engine/Platform/CreateWindowSettings.cs b/Source/Engine/Platform/CreateWindowSettings.cs
index d4d9ce727..8f7cd8c0d 100644
--- a/Source/Engine/Platform/CreateWindowSettings.cs
+++ b/Source/Engine/Platform/CreateWindowSettings.cs
@@ -12,7 +12,7 @@ namespace FlaxEngine
Position = new Float2(100, 100),
Size = new Float2(640, 480),
MinimumSize = Float2.One,
- MaximumSize = new Float2(4100, 4100),
+ MaximumSize = Float2.Zero, // Unlimited size
StartPosition = WindowStartPosition.CenterParent,
HasBorder = true,
ShowInTaskbar = true,
diff --git a/Source/Engine/Platform/CreateWindowSettings.h b/Source/Engine/Platform/CreateWindowSettings.h
index 1ff596df9..9c543a05d 100644
--- a/Source/Engine/Platform/CreateWindowSettings.h
+++ b/Source/Engine/Platform/CreateWindowSettings.h
@@ -59,9 +59,9 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(CreateWindowSettings);
API_FIELD() Float2 MinimumSize = Float2(1, 1);
///
- /// The maximum size.
+ /// The maximum size. Set to 0 to use unlimited size.
///
- API_FIELD() Float2 MaximumSize = Float2(8192, 4096);
+ API_FIELD() Float2 MaximumSize = Float2(0, 0);
///
/// The start position mode.
diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp
index 3f33a8f65..97cde4a1c 100644
--- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp
+++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp
@@ -9,6 +9,7 @@
#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/TimeSpan.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Log.h"
#include "Engine/Utilities/StringConverter.h"
@@ -678,8 +679,14 @@ void LinuxFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& res
result = TEXT("/usr/share");
break;
case SpecialFolder::LocalAppData:
- result = home;
+ {
+ String dataHome;
+ if (!Platform::GetEnvironmentVariable(TEXT("XDG_DATA_HOME"), dataHome))
+ result = dataHome;
+ else
+ result = home / TEXT(".local/share");
break;
+ }
case SpecialFolder::ProgramData:
result = String::Empty;
break;
diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
index 4787b4549..36affd8c8 100644
--- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp
+++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
@@ -2622,9 +2622,7 @@ bool LinuxPlatform::GetHasFocus()
if (window->IsFocused())
return true;
}
-
- // Default to true if has no windows open
- return WindowsManager::Windows.IsEmpty();
+ return false;
}
bool LinuxPlatform::CanOpenUrl(const StringView& url)
diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp
index a232dea55..b3bae0276 100644
--- a/Source/Engine/Platform/Linux/LinuxWindow.cpp
+++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp
@@ -150,9 +150,9 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
{
// Set resizing range
hints.min_width = (int)settings.MinimumSize.X;
- hints.max_width = (int)settings.MaximumSize.X;
+ hints.max_width = settings.MaximumSize.X > 0 ? (int)settings.MaximumSize.X : MAX_uint16;
hints.min_height = (int)settings.MinimumSize.Y;
- hints.max_height = (int)settings.MaximumSize.Y;
+ hints.max_height = settings.MaximumSize.Y > 0 ? (int)settings.MaximumSize.Y : MAX_uint16;
hints.flags |= USSize;
}
// honor the WM placement except for manual (overriding) placements
@@ -594,6 +594,12 @@ void LinuxWindow::OnButtonPress(void* event)
case Button3:
mouseButton = MouseButton::Right;
break;
+ case 8:
+ mouseButton = MouseButton::Extended2;
+ break;
+ case 9:
+ mouseButton = MouseButton::Extended1;
+ break;
default:
return;
}
@@ -641,6 +647,12 @@ void LinuxWindow::OnButtonRelease(void* event)
case Button5:
Input::Mouse->OnMouseWheel(ClientToScreen(mousePos), -1.0f, this);
break;
+ case 8:
+ Input::Mouse->OnMouseUp(ClientToScreen(mousePos), MouseButton::Extended2, this);
+ break;
+ case 9:
+ Input::Mouse->OnMouseUp(ClientToScreen(mousePos), MouseButton::Extended1, this);
+ break;
default:
return;
}
diff --git a/Source/Engine/Platform/Mac/MacFileSystem.cpp b/Source/Engine/Platform/Mac/MacFileSystem.cpp
index c765feef4..e2ed24f13 100644
--- a/Source/Engine/Platform/Mac/MacFileSystem.cpp
+++ b/Source/Engine/Platform/Mac/MacFileSystem.cpp
@@ -9,6 +9,7 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/TimeSpan.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Log.h"
#include "Engine/Utilities/StringConverter.h"
diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp
index 000accbfa..373d19bae 100644
--- a/Source/Engine/Platform/Mac/MacWindow.cpp
+++ b/Source/Engine/Platform/Mac/MacWindow.cpp
@@ -4,7 +4,12 @@
#include "../Window.h"
#include "Engine/Platform/Apple/AppleUtils.h"
+#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/IGuiData.h"
+#if USE_EDITOR
+#include "Engine/Platform/CriticalSection.h"
+#include "Engine/Platform/Base/DragDropHelper.h"
+#endif
#include "Engine/Core/Log.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h"
@@ -14,6 +19,21 @@
#include
#include
+#if USE_EDITOR
+// Data for drawing window while doing drag&drop on Mac (engine is paused during platform tick)
+CriticalSection MacDragLocker;
+NSDraggingSession* MacDragSession = nullptr;
+DoDragDropJob* MacDragJob = nullptr;
+#endif
+
+inline bool IsWindowInvalid(Window* win)
+{
+ WindowsManager::WindowsLocker.Lock();
+ const bool hasWindow = WindowsManager::Windows.Contains(win);
+ WindowsManager::WindowsLocker.Unlock();
+ return !hasWindow || !win;
+}
+
KeyboardKeys GetKey(NSEvent* event)
{
switch ([event keyCode])
@@ -268,17 +288,20 @@ NSDragOperation GetDragDropOperation(DragDropEffect dragDropEffect)
// Handle resizing to be sure that content has valid size when window was resized
[self windowDidResize:notification];
+ if (IsWindowInvalid(Window)) return;
Window->OnGotFocus();
}
- (void)windowDidResignKey:(NSNotification*)notification
{
+ if (IsWindowInvalid(Window)) return;
Window->OnLostFocus();
}
- (void)windowWillClose:(NSNotification*)notification
{
[self setDelegate: nil];
+ if (IsWindowInvalid(Window)) return;
Window->Close(ClosingReason::User);
}
@@ -311,7 +334,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
@end
-@interface MacViewImpl : NSView
+@interface MacViewImpl : NSView
{
MacWindow* Window;
NSTrackingArea* TrackingArea;
@@ -375,6 +398,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)keyDown:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
KeyboardKeys key = GetKey(event);
if (key != KeyboardKeys::None)
Input::Keyboard->OnKeyDown(key, Window);
@@ -405,6 +429,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)keyUp:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
KeyboardKeys key = GetKey(event);
if (key != KeyboardKeys::None)
Input::Keyboard->OnKeyUp(key, Window);
@@ -412,6 +437,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)flagsChanged:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
int32 modMask;
int32 keyCode = [event keyCode];
if (keyCode == 0x36 || keyCode == 0x37)
@@ -437,6 +463,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)scrollWheel:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
double deltaX = [event scrollingDeltaX];
double deltaY = [event scrollingDeltaY];
@@ -451,32 +478,39 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)mouseMoved:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
- if (!Window->IsMouseTracking() && !IsMouseOver)
+ if (Window->IsMouseTracking())
+ return; // Skip mouse events when tracking mouse (handled in MacWindow::OnUpdate)
+ if (!IsMouseOver)
return;
Input::Mouse->OnMouseMove(Window->ClientToScreen(mousePos), Window);
}
- (void)mouseEntered:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
IsMouseOver = true;
Window->SetIsMouseOver(true);
}
- (void)mouseExited:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
IsMouseOver = false;
Window->SetIsMouseOver(false);
}
- (void)mouseDown:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
+ mousePos = Window->ClientToScreen(mousePos);
MouseButton mouseButton = MouseButton::Left;
if ([event clickCount] == 2)
- Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window);
+ Input::Mouse->OnMouseDoubleClick(mousePos, mouseButton, Window);
else
- Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window);
+ Input::Mouse->OnMouseDown(mousePos, mouseButton, Window);
}
- (void)mouseDragged:(NSEvent*)event
@@ -486,13 +520,28 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)mouseUp:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
+ mousePos = Window->ClientToScreen(mousePos);
MouseButton mouseButton = MouseButton::Left;
- Input::Mouse->OnMouseUp(Window->ClientToScreen(mousePos), mouseButton, Window);
+ Input::Mouse->OnMouseUp(mousePos, mouseButton, Window);
+
+ // Redirect event to any window that tracks the mouse (eg. dock window in Editor)
+ WindowsManager::WindowsLocker.Lock();
+ for (auto* win : WindowsManager::Windows)
+ {
+ if (win->IsVisible() && win->IsMouseTracking() && win != Window)
+ {
+ Input::Mouse->OnMouseUp(mousePos, mouseButton, win);
+ break;
+ }
+ }
+ WindowsManager::WindowsLocker.Unlock();
}
- (void)rightMouseDown:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton = MouseButton::Right;
if ([event clickCount] == 2)
@@ -508,6 +557,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)rightMouseUp:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton = MouseButton::Right;
Input::Mouse->OnMouseUp(Window->ClientToScreen(mousePos), mouseButton, Window);
@@ -515,6 +565,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)otherMouseDown:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton;
switch ([event buttonNumber])
@@ -544,6 +595,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)otherMouseUp:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton;
switch ([event buttonNumber])
@@ -565,6 +617,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (NSDragOperation)draggingEntered:(id)sender
{
+ if (IsWindowInvalid(Window)) return NSDragOperationNone;
Float2 mousePos;
MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData);
@@ -580,6 +633,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (NSDragOperation)draggingUpdated:(id)sender
{
+ if (IsWindowInvalid(Window)) return NSDragOperationNone;
Float2 mousePos;
MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData);
@@ -590,6 +644,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (BOOL)performDragOperation:(id)sender
{
+ if (IsWindowInvalid(Window)) return NO;
Float2 mousePos;
MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData);
@@ -600,9 +655,38 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)draggingExited:(id)sender
{
+ if (IsWindowInvalid(Window)) return;
Window->OnDragLeave();
}
+- (NSDragOperation)draggingSession:(NSDraggingSession*)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
+{
+ if (IsWindowInvalid(Window)) return NSDragOperationNone;
+ return NSDragOperationMove;
+}
+
+- (void)draggingSession:(NSDraggingSession*)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
+{
+#if USE_EDITOR
+ // Stop background worker once the drag ended
+ MacDragLocker.Lock();
+ if (MacDragSession && MacDragSession == session)
+ {
+ Platform::AtomicStore(&MacDragJob->ExitFlag, 1);
+ MacDragJob->Wait();
+ MacDragSession = nullptr;
+ MacDragJob = nullptr;
+ }
+ MacDragLocker.Unlock();
+#endif
+}
+
+- (void)pasteboard:(nullable NSPasteboard*)pasteboard item:(NSPasteboardItem*)item provideDataForType:(NSPasteboardType)type
+{
+ if (IsWindowInvalid(Window)) return;
+ [pasteboard setString:(NSString*)AppleUtils::ToString(Window->GetDragText()) forType:NSPasteboardTypeString];
+}
+
@end
MacWindow::MacWindow(const CreateWindowSettings& settings)
@@ -624,6 +708,8 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
{
styleMask |= NSWindowStyleMaskBorderless;
}
+ if (settings.Fullscreen)
+ styleMask |= NSWindowStyleMaskFullScreen;
if (settings.HasBorder)
{
styleMask |= NSWindowStyleMaskTitled;
@@ -647,12 +733,15 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
[window setWindow:this];
[window setReleasedWhenClosed:NO];
[window setMinSize:NSMakeSize(settings.MinimumSize.X, settings.MinimumSize.Y)];
- [window setMaxSize:NSMakeSize(settings.MaximumSize.X, settings.MaximumSize.Y)];
+ if (settings.MaximumSize.SumValues() > 0)
+ [window setMaxSize:NSMakeSize(settings.MaximumSize.X, settings.MaximumSize.Y)];
[window setOpaque:!settings.SupportsTransparency];
[window setContentView:view];
- [window setAcceptsMouseMovedEvents:YES];
+ if (settings.AllowInput)
+ [window setAcceptsMouseMovedEvents:YES];
[window setDelegate:window];
_window = window;
+ _view = view;
if (settings.AllowDragAndDrop)
{
[view registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]];
@@ -664,8 +753,6 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
layer.contentsScale = screenScale;
// TODO: impl Parent for MacWindow
- // TODO: impl StartPosition for MacWindow
- // TODO: impl Fullscreen for MacWindow
// TODO: impl ShowInTaskbar for MacWindow
// TODO: impl IsTopmost for MacWindow
}
@@ -676,6 +763,7 @@ MacWindow::~MacWindow()
[window close];
[window release];
_window = nullptr;
+ _view = nullptr;
}
void MacWindow::CheckForResize(float width, float height)
@@ -699,7 +787,6 @@ void MacWindow::SetIsMouseOver(bool value)
// Refresh cursor typet
SetCursor(CursorType::Default);
SetCursor(cursor);
-
}
else
{
@@ -714,6 +801,22 @@ void* MacWindow::GetNativePtr() const
return _window;
}
+void MacWindow::OnUpdate(float dt)
+{
+ if (IsMouseTracking())
+ {
+ // Keep sending mouse movement events no matter if window has focus
+ Float2 mousePos = Platform::GetMousePosition();
+ if (_mouseTrackPos != mousePos)
+ {
+ _mouseTrackPos = mousePos;
+ Input::Mouse->OnMouseMove(mousePos, this);
+ }
+ }
+
+ WindowBase::OnUpdate(dt);
+}
+
void MacWindow::Show()
{
if (!_visible)
@@ -728,7 +831,10 @@ void MacWindow::Show()
// Show
NSWindow* window = (NSWindow*)_window;
- [window makeKeyAndOrderFront:window];
+ if (_settings.AllowInput)
+ [window makeKeyAndOrderFront:window];
+ else
+ [window orderFront:window];
if (_settings.ActivateWhenFirstShown)
[NSApp activateIgnoringOtherApps:YES];
_focused = true;
@@ -746,7 +852,7 @@ void MacWindow::Hide()
// Hide
NSWindow* window = (NSWindow*)_window;
- [window orderOut:nil];
+ [window orderOut:window];
// Base
WindowBase::Hide();
@@ -782,14 +888,9 @@ void MacWindow::Restore()
[window zoom:nil];
}
-bool MacWindow::IsClosed() const
-{
- return _window != nullptr;
-}
-
bool MacWindow::IsForegroundWindow() const
{
- return Platform::GetHasFocus() && IsFocused();
+ return IsFocused() && Platform::GetHasFocus();
}
void MacWindow::BringToFront(bool force)
@@ -808,14 +909,13 @@ void MacWindow::SetClientBounds(const Rectangle& clientArea)
NSWindow* window = (NSWindow*)_window;
if (!window)
return;
+ const float screenScale = MacPlatform::ScreenScale;
+
NSRect oldRect = [window frame];
- NSRect newRect = NSMakeRect(0, 0, clientArea.Size.X, clientArea.Size.Y);
+ NSRect newRect = NSMakeRect(0, 0, clientArea.Size.X / screenScale, clientArea.Size.Y / screenScale);
newRect = [window frameRectForContentRect:newRect];
- //newRect.origin.x = oldRect.origin.x;
- //newRect.origin.y = NSMaxY(oldRect) - newRect.size.height;
-
- Float2 pos = AppleUtils::PosToCoca(clientArea.Location);
+ Float2 pos = AppleUtils::PosToCoca(clientArea.Location) / screenScale;
Float2 titleSize = GetWindowTitleSize(this);
newRect.origin.x = pos.X + titleSize.X;
newRect.origin.y = pos.Y - newRect.size.height + titleSize.Y;
@@ -909,8 +1009,63 @@ void MacWindow::SetTitle(const StringView& title)
DragDropEffect MacWindow::DoDragDrop(const StringView& data)
{
- // TODO: implement using beginDraggingSession and NSDraggingSource
- return DragDropEffect::None;
+ NSWindow* window = (NSWindow*)_window;
+ MacViewImpl* view = (MacViewImpl*)_view;
+ _dragText = data;
+
+ // Create mouse drag event
+ NSEvent* event = [NSEvent
+ mouseEventWithType:NSEventTypeLeftMouseDragged
+ location:window.mouseLocationOutsideOfEventStream
+ modifierFlags:0
+ timestamp:NSApp.currentEvent.timestamp
+ windowNumber:window.windowNumber
+ context:nil
+ eventNumber:0
+ clickCount:1
+ pressure:1.0];
+
+ // Create drag item
+ NSPasteboardItem* pasteItem = [NSPasteboardItem new];
+ [pasteItem setDataProvider:view forTypes:[NSArray arrayWithObjects:NSPasteboardTypeString, nil]];
+ NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pasteItem];
+ [dragItem setDraggingFrame:NSMakeRect(event.locationInWindow.x, event.locationInWindow.y, 1, 1) contents:nil];
+
+ // Start dragging session
+ NSDraggingSession* draggingSession = [view beginDraggingSessionWithItems:[NSArray arrayWithObject:dragItem] event:event source:view];
+ DragDropEffect result = DragDropEffect::None;
+
+#if USE_EDITOR
+ // Create background worker that will keep updating GUI (perform rendering)
+ MacDragLocker.Lock();
+ ASSERT(!MacDragSession && !MacDragJob);
+ MacDragSession = draggingSession;
+ MacDragJob = New();
+ Task::StartNew(MacDragJob);
+ MacDragLocker.Unlock();
+ while (MacDragJob->GetState() == TaskState::Queued)
+ Platform::Sleep(1);
+ // TODO: maybe wait for the drag end to return result?
+#endif
+
+ return result;
+}
+
+void MacWindow::StartTrackingMouse(bool useMouseScreenOffset)
+{
+ if (_isTrackingMouse || !_window)
+ return;
+ _isTrackingMouse = true;
+ _trackingMouseOffset = Float2::Zero;
+ _isUsingMouseOffset = useMouseScreenOffset;
+ _mouseTrackPos = Float2::Minimum;
+}
+
+void MacWindow::EndTrackingMouse()
+{
+ if (!_isTrackingMouse || !_window)
+ return;
+ _isTrackingMouse = false;
}
void MacWindow::SetCursor(CursorType type)
diff --git a/Source/Engine/Platform/Mac/MacWindow.h b/Source/Engine/Platform/Mac/MacWindow.h
index 8850f80ba..ccd43b354 100644
--- a/Source/Engine/Platform/Mac/MacWindow.h
+++ b/Source/Engine/Platform/Mac/MacWindow.h
@@ -6,6 +6,7 @@
#include "Engine/Platform/Base/WindowBase.h"
#include "Engine/Platform/Platform.h"
+#include "Engine/Core/Math/Vector2.h"
///
/// Implementation of the window class for Mac platform.
@@ -13,28 +14,32 @@
class FLAXENGINE_API MacWindow : public WindowBase
{
private:
-
- void* _window;
+ void* _window = nullptr;
+ void* _view = nullptr;
bool _isMouseOver = false;
+ Float2 _mouseTrackPos = Float2::Minimum;
+ String _dragText;
public:
-
MacWindow(const CreateWindowSettings& settings);
~MacWindow();
void CheckForResize(float width, float height);
void SetIsMouseOver(bool value);
+ const String& GetDragText() const
+ {
+ return _dragText;
+ }
public:
-
// [WindowBase]
void* GetNativePtr() const override;
+ void OnUpdate(float dt) override;
void Show() override;
void Hide() override;
void Minimize() override;
void Maximize() override;
void Restore() override;
- bool IsClosed() const override;
bool IsForegroundWindow() const override;
void BringToFront(bool force = false) override;
void SetClientBounds(const Rectangle& clientArea) override;
@@ -50,6 +55,8 @@ public:
void Focus() override;
void SetTitle(const StringView& title) override;
DragDropEffect DoDragDrop(const StringView& data) override;
+ void StartTrackingMouse(bool useMouseScreenOffset) override;
+ void EndTrackingMouse() override;
void SetCursor(CursorType type) override;
};
diff --git a/Source/Engine/Platform/Unix/UnixNetwork.cpp b/Source/Engine/Platform/Unix/UnixNetwork.cpp
index 5ffdb2c01..ae2d8d9f2 100644
--- a/Source/Engine/Platform/Unix/UnixNetwork.cpp
+++ b/Source/Engine/Platform/Unix/UnixNetwork.cpp
@@ -96,7 +96,11 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint)
return true;
}
char strPort[6];
+#if __APPLE__
+ snprintf(strPort, sizeof(strPort), "%d", port);
+#else
sprintf(strPort, "%d", port);
+#endif
endPoint.IPVersion = addr->sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4;
memcpy(endPoint.Data, addr, size);
return false;
diff --git a/Source/Engine/Platform/Unix/UnixThread.cpp b/Source/Engine/Platform/Unix/UnixThread.cpp
index f662bdba8..29dc5f881 100644
--- a/Source/Engine/Platform/Unix/UnixThread.cpp
+++ b/Source/Engine/Platform/Unix/UnixThread.cpp
@@ -26,6 +26,12 @@ int32 UnixThread::Start(pthread_attr_t& attr)
void* UnixThread::ThreadProc(void* pThis)
{
auto thread = (UnixThread*)pThis;
+#if PLATFORM_APPLE_FAMILY
+ // Apple doesn't support creating named thread so assign name here
+ {
+ pthread_setname_np(StringAnsi(thread->GetName()).Get());
+ }
+#endif
const int32 exitCode = thread->Run();
return (void*)(uintptr)exitCode;
}
diff --git a/Source/Engine/Platform/Win32/Win32CriticalSection.h b/Source/Engine/Platform/Win32/Win32CriticalSection.h
index 2c103ef85..95d85efac 100644
--- a/Source/Engine/Platform/Win32/Win32CriticalSection.h
+++ b/Source/Engine/Platform/Win32/Win32CriticalSection.h
@@ -16,16 +16,13 @@ class FLAXENGINE_API Win32CriticalSection
friend Win32ConditionVariable;
private:
-
mutable Windows::CRITICAL_SECTION _criticalSection;
private:
-
Win32CriticalSection(const Win32CriticalSection&);
Win32CriticalSection& operator=(const Win32CriticalSection&);
public:
-
///
/// Initializes a new instance of the class.
///
@@ -43,17 +40,12 @@ public:
}
public:
-
///
/// Locks the critical section.
///
void Lock() const
{
- // Spin first before entering critical section, causing ring-0 transition and context switch
- if (Windows::TryEnterCriticalSection(&_criticalSection) == 0)
- {
- Windows::EnterCriticalSection(&_criticalSection);
- }
+ Windows::EnterCriticalSection(&_criticalSection);
}
///
diff --git a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
index 38384f05f..d07c333e0 100644
--- a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
+++ b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
@@ -8,6 +8,7 @@
#include "Engine/Platform/Windows/ComPtr.h"
#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Core/Types/StringView.h"
+#include "Engine/Core/Collections/Array.h"
#include "../Win32/IncludeWindowsHeaders.h"
// Hack this stuff (the problem is that GDI has function named Rectangle -> like one of the Flax core types)
diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
index c5bc44ab9..cc2e52725 100644
--- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp
+++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
@@ -451,6 +451,7 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri
default:
break;
}
+ flags |= MB_TASKMODAL;
// Show dialog
int result = MessageBoxW(parent ? static_cast(parent->GetNativePtr()) : nullptr, String(text).GetText(), String(caption).GetText(), flags);
@@ -1200,8 +1201,8 @@ void* WindowsPlatform::LoadLibrary(const Char* filename)
folder = StringView::Empty;
if (folder.HasChars())
{
- String folderNullTerminated(folder);
- SetDllDirectoryW(folderNullTerminated.Get());
+ const String folderNullTerminated(folder);
+ AddDllDirectory(folderNullTerminated.Get());
}
// Avoiding windows dialog boxes if missing
@@ -1209,7 +1210,10 @@ void* WindowsPlatform::LoadLibrary(const Char* filename)
DWORD prevErrorMode = 0;
const BOOL hasErrorMode = SetThreadErrorMode(errorMode, &prevErrorMode);
- // Load the DLL
+ // Ensure that dll is properly searched
+ SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS);
+
+ // Load the library
void* handle = ::LoadLibraryW(filename);
if (!handle)
{
@@ -1220,10 +1224,6 @@ void* WindowsPlatform::LoadLibrary(const Char* filename)
{
SetThreadErrorMode(prevErrorMode, nullptr);
}
- if (folder.HasChars())
- {
- SetDllDirectoryW(nullptr);
- }
#if CRASH_LOG_ENABLE
// Refresh modules info during next stack trace collecting to have valid debug symbols information
diff --git a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp b/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp
deleted file mode 100644
index b6e636697..000000000
--- a/Source/Engine/Platform/Windows/WindowsWindow.DragDrop.cpp
+++ /dev/null
@@ -1,698 +0,0 @@
-// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
-
-#if PLATFORM_WINDOWS
-
-#include "WindowsWindow.h"
-
-#if USE_EDITOR
-
-#include "Engine/Core/Collections/Array.h"
-#include "Engine/Engine/Engine.h"
-#include "Engine/Platform/IGuiData.h"
-#include "Engine/Input/Input.h"
-#include "Engine/Input/Mouse.h"
-#include "Engine/Threading/ThreadPoolTask.h"
-#include "Engine/Threading/ThreadPool.h"
-#include "Engine/Scripting/Scripting.h"
-#include "Engine/Scripting/ManagedCLR/MDomain.h"
-#include "../Win32/IncludeWindowsHeaders.h"
-#include
-#include
-
-HGLOBAL duplicateGlobalMem(HGLOBAL hMem)
-{
- auto len = GlobalSize(hMem);
- auto source = GlobalLock(hMem);
- auto dest = GlobalAlloc(GMEM_FIXED, len);
- Platform::MemoryCopy(dest, source, len);
- GlobalUnlock(hMem);
- return dest;
-}
-
-DWORD dropEffect2OleEnum(DragDropEffect effect)
-{
- DWORD result;
- switch (effect)
- {
- case DragDropEffect::None:
- result = DROPEFFECT_NONE;
- break;
- case DragDropEffect::Copy:
- result = DROPEFFECT_COPY;
- break;
- case DragDropEffect::Move:
- result = DROPEFFECT_MOVE;
- break;
- case DragDropEffect::Link:
- result = DROPEFFECT_LINK;
- break;
- default:
- result = DROPEFFECT_NONE;
- break;
- }
- return result;
-}
-
-DragDropEffect dropEffectFromOleEnum(DWORD effect)
-{
- DragDropEffect result;
- switch (effect)
- {
- case DROPEFFECT_NONE:
- result = DragDropEffect::None;
- break;
- case DROPEFFECT_COPY:
- result = DragDropEffect::Copy;
- break;
- case DROPEFFECT_MOVE:
- result = DragDropEffect::Move;
- break;
- case DROPEFFECT_LINK:
- result = DragDropEffect::Link;
- break;
- default:
- result = DragDropEffect::None;
- break;
- }
- return result;
-}
-
-HANDLE StringToHandle(const StringView& str)
-{
- // Allocate and lock a global memory buffer.
- // Make it fixed data so we don't have to use GlobalLock
- const int32 length = str.Length();
- char* ptr = static_cast(GlobalAlloc(GMEM_FIXED, length + 1));
-
- // Copy the string into the buffer as ANSI text
- StringUtils::ConvertUTF162ANSI(str.Get(), ptr, length);
- ptr[length] = '\0';
-
- return ptr;
-}
-
-void DeepCopyFormatEtc(FORMATETC* dest, FORMATETC* source)
-{
- // Copy the source FORMATETC into dest
- *dest = *source;
-
- if (source->ptd)
- {
- // Allocate memory for the DVTARGETDEVICE if necessary
- dest->ptd = static_cast(CoTaskMemAlloc(sizeof(DVTARGETDEVICE)));
-
- // Copy the contents of the source DVTARGETDEVICE into dest->ptd
- *(dest->ptd) = *(source->ptd);
- }
-}
-
-HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc);
-
-///
-/// GUI data for Windows platform
-///
-class WindowsGuiData : public IGuiData
-{
-private:
-
- Type _type;
- Array _data;
-
-public:
-
- ///
- /// Init
- ///
- WindowsGuiData()
- : _type(Type::Unknown)
- , _data(1)
- {
- }
-
-public:
-
- ///
- /// Init from Ole IDataObject
- ///
- /// Object
- void Init(IDataObject* pDataObj)
- {
- // Temporary data
- FORMATETC fmtetc;
- STGMEDIUM stgmed;
-
- // Clear
- _type = Type::Unknown;
- _data.Clear();
-
- // Check type
- fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
- if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
- {
- // Text
- _type = Type::Text;
-
- // Get data
- char* text = static_cast(GlobalLock(stgmed.hGlobal));
- _data.Add(String(text));
- GlobalUnlock(stgmed.hGlobal);
- ReleaseStgMedium(&stgmed);
- }
- else
- {
- fmtetc = { CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
- if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
- {
- // Unicode Text
- _type = Type::Text;
-
- // Get data
- Char* text = static_cast(GlobalLock(stgmed.hGlobal));
- _data.Add(String(text));
- GlobalUnlock(stgmed.hGlobal);
- ReleaseStgMedium(&stgmed);
- }
- else
- {
- fmtetc = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
- if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
- {
- // Files
- _type = Type::Files;
-
- // Get data
- Char item[MAX_PATH];
- HDROP hdrop = static_cast(GlobalLock(stgmed.hGlobal));
- UINT filesCount = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);
- for (UINT i = 0; i < filesCount; i++)
- {
- if (DragQueryFileW(hdrop, i, item, MAX_PATH) != 0)
- {
- _data.Add(String(item));
- }
- }
- GlobalUnlock(stgmed.hGlobal);
- ReleaseStgMedium(&stgmed);
- }
- }
- }
- }
-
-public:
-
- // [IGuiData]
- Type GetType() const override
- {
- return _type;
- }
-
- String GetAsText() const override
- {
- String result;
- if (_type == Type::Text)
- {
- result = _data[0];
- }
- return result;
- }
-
- void GetAsFiles(Array* files) const override
- {
- if (_type == Type::Files)
- {
- files->Add(_data);
- }
- }
-};
-
-///
-/// Tool class for Windows Ole support
-///
-class WindowsEnumFormatEtc : public IEnumFORMATETC
-{
-private:
-
- ULONG _refCount;
- ULONG _index;
- ULONG _formatsCount;
- FORMATETC* _formatEtc;
-
-public:
-
- WindowsEnumFormatEtc(FORMATETC* pFormatEtc, int32 nNumFormats)
- : _refCount(1)
- , _index(0)
- , _formatsCount(nNumFormats)
- , _formatEtc(nullptr)
- {
- // Allocate memory
- _formatEtc = new FORMATETC[nNumFormats];
-
- // Copy the FORMATETC structures
- for (int32 i = 0; i < nNumFormats; i++)
- {
- DeepCopyFormatEtc(&_formatEtc[i], &pFormatEtc[i]);
- }
- }
-
- ~WindowsEnumFormatEtc()
- {
- if (_formatEtc)
- {
- for (uint32 i = 0; i < _formatsCount; i++)
- {
- if (_formatEtc[i].ptd)
- {
- CoTaskMemFree(_formatEtc[i].ptd);
- }
- }
-
- delete[] _formatEtc;
- }
- }
-
-public:
-
- HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override
- {
- // Check to see what interface has been requested
- if (riid == IID_IEnumFORMATETC || riid == IID_IUnknown)
- {
- AddRef();
- *ppvObject = this;
- return S_OK;
- }
-
- // No interface
- *ppvObject = nullptr;
- return E_NOINTERFACE;
- }
-
- ULONG STDMETHODCALLTYPE AddRef() override
- {
- _InterlockedIncrement(&_refCount);
- return _refCount;
- }
-
- ULONG STDMETHODCALLTYPE Release() override
- {
- ULONG ulRefCount = _InterlockedDecrement(&_refCount);
- if (_refCount == 0)
- {
- delete this;
- }
- return ulRefCount;
- }
-
- // [IEnumFormatEtc]
- HRESULT STDMETHODCALLTYPE Next(ULONG celt, FORMATETC* pFormatEtc, ULONG* pceltFetched) override
- {
- ULONG copied = 0;
-
- // validate arguments
- if (celt == 0 || pFormatEtc == nullptr)
- return E_INVALIDARG;
-
- // copy FORMATETC structures into caller's buffer
- while (_index < _formatsCount && copied < celt)
- {
- DeepCopyFormatEtc(&pFormatEtc[copied], &_formatEtc[_index]);
- copied++;
- _index++;
- }
-
- // store result
- if (pceltFetched != nullptr)
- *pceltFetched = copied;
-
- // did we copy all that was requested?
- return (copied == celt) ? S_OK : S_FALSE;
- }
-
- HRESULT STDMETHODCALLTYPE Skip(ULONG celt) override
- {
- _index += celt;
- return (_index <= _formatsCount) ? S_OK : S_FALSE;
- }
-
- HRESULT STDMETHODCALLTYPE Reset() override
- {
- _index = 0;
- return S_OK;
- }
-
- HRESULT STDMETHODCALLTYPE Clone(IEnumFORMATETC** ppEnumFormatEtc) override
- {
- HRESULT result;
-
- // Make a duplicate enumerator
- result = CreateEnumFormatEtc(_formatsCount, _formatEtc, ppEnumFormatEtc);
-
- if (result == S_OK)
- {
- // Manually set the index state
- static_cast(*ppEnumFormatEtc)->_index = _index;
- }
-
- return result;
- }
-};
-
-HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc)
-{
- if (nNumFormats == 0 || pFormatEtc == nullptr || ppEnumFormatEtc == nullptr)
- return E_INVALIDARG;
-
- *ppEnumFormatEtc = new WindowsEnumFormatEtc(pFormatEtc, nNumFormats);
-
- return (*ppEnumFormatEtc) ? S_OK : E_OUTOFMEMORY;
-}
-
-///
-/// Drag drop source and data container for Ole
-///
-class WindowsDragSource : public IDataObject, public IDropSource
-{
-private:
-
- ULONG _refCount;
- int32 _formatsCount;
- FORMATETC* _formatEtc;
- STGMEDIUM* _stgMedium;
-
-public:
-
- WindowsDragSource(FORMATETC* fmtetc, STGMEDIUM* stgmed, int32 count)
- : _refCount(1)
- , _formatsCount(count)
- , _formatEtc(nullptr)
- , _stgMedium(nullptr)
- {
- // Allocate memory
- _formatEtc = new FORMATETC[count];
- _stgMedium = new STGMEDIUM[count];
-
- // Copy descriptors
- for (int32 i = 0; i < count; i++)
- {
- _formatEtc[i] = fmtetc[i];
- _stgMedium[i] = stgmed[i];
- }
- }
-
- virtual ~WindowsDragSource()
- {
- if (_formatEtc)
- delete[] _formatEtc;
- if (_stgMedium)
- delete[] _stgMedium;
- }
-
-public:
-
- // [IUnknown]
- HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override
- {
- // Check to see what interface has been requested
- if (riid == IID_IDataObject || riid == IID_IUnknown || riid == IID_IDropSource)
- {
- AddRef();
- *ppvObject = this;
- return S_OK;
- }
-
- // No interface
- *ppvObject = nullptr;
- return E_NOINTERFACE;
- }
-
- ULONG STDMETHODCALLTYPE AddRef() override
- {
- _InterlockedIncrement(&_refCount);
- return _refCount;
- }
-
- ULONG STDMETHODCALLTYPE Release() override
- {
- ULONG ulRefCount = _InterlockedDecrement(&_refCount);
- if (_refCount == 0)
- {
- delete this;
- }
- return ulRefCount;
- }
-
- // [IDropSource]
- HRESULT STDMETHODCALLTYPE QueryContinueDrag(_In_ BOOL fEscapePressed, _In_ DWORD grfKeyState) override
- {
- // If the Escape key has been pressed since the last call, cancel the drop
- if (fEscapePressed == TRUE || grfKeyState & MK_RBUTTON)
- return DRAGDROP_S_CANCEL;
-
- // If the LeftMouse button has been released, then do the drop!
- if ((grfKeyState & MK_LBUTTON) == 0)
- return DRAGDROP_S_DROP;
-
- // Continue with the drag-drop
- return S_OK;
- }
-
- HRESULT STDMETHODCALLTYPE GiveFeedback(_In_ DWORD dwEffect) override
- {
- // TODO: allow to use custom mouse cursor during drop and drag operation
- return DRAGDROP_S_USEDEFAULTCURSORS;
- }
-
- // [IDataObject]
- HRESULT STDMETHODCALLTYPE GetData(_In_ FORMATETC* pformatetcIn, _Out_ STGMEDIUM* pmedium) override
- {
- if (pformatetcIn == nullptr || pmedium == nullptr)
- return E_INVALIDARG;
-
- // Try to match the specified FORMATETC with one of our supported formats
- int32 index = lookupFormatEtc(pformatetcIn);
- if (index == INVALID_INDEX)
- return DV_E_FORMATETC;
-
- // Found a match - transfer data into supplied storage medium
- pmedium->tymed = _formatEtc[index].tymed;
- pmedium->pUnkForRelease = nullptr;
-
- // Copy the data into the caller's storage medium
- switch (_formatEtc[index].tymed)
- {
- case TYMED_HGLOBAL:
- pmedium->hGlobal = duplicateGlobalMem(_stgMedium[index].hGlobal);
- break;
-
- default:
- return DV_E_FORMATETC;
- }
-
- return S_OK;
- }
-
- HRESULT STDMETHODCALLTYPE GetDataHere(_In_ FORMATETC* pformatetc, _Inout_ STGMEDIUM* pmedium) override
- {
- return DATA_E_FORMATETC;
- }
-
- HRESULT STDMETHODCALLTYPE QueryGetData(__RPC__in_opt FORMATETC* pformatetc) override
- {
- return lookupFormatEtc(pformatetc) == INVALID_INDEX ? DV_E_FORMATETC : S_OK;
- }
-
- HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(__RPC__in_opt FORMATETC* pformatectIn, __RPC__out FORMATETC* pformatetcOut) override
- {
- // Apparently we have to set this field to NULL even though we don't do anything else
- pformatetcOut->ptd = nullptr;
- return E_NOTIMPL;
- }
-
- HRESULT STDMETHODCALLTYPE SetData(_In_ FORMATETC* pformatetc, _In_ STGMEDIUM* pmedium, BOOL fRelease) override
- {
- return E_NOTIMPL;
- }
-
- HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dwDirection, __RPC__deref_out_opt IEnumFORMATETC** ppenumFormatEtc) override
- {
- // Only the get direction is supported for OLE
- if (dwDirection == DATADIR_GET)
- {
- // TODO: use SHCreateStdEnumFmtEtc API call
- return CreateEnumFormatEtc(_formatsCount, _formatEtc, ppenumFormatEtc);
- }
-
- // The direction specified is not supported for drag+drop
- return E_NOTIMPL;
- }
-
- HRESULT STDMETHODCALLTYPE DAdvise(__RPC__in FORMATETC* pformatetc, DWORD advf, __RPC__in_opt IAdviseSink* pAdvSink, __RPC__out DWORD* pdwConnection) override
- {
- return OLE_E_ADVISENOTSUPPORTED;
- }
-
- HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) override
- {
- return OLE_E_ADVISENOTSUPPORTED;
- }
-
- HRESULT STDMETHODCALLTYPE EnumDAdvise(__RPC__deref_out_opt IEnumSTATDATA** ppenumAdvise) override
- {
- return OLE_E_ADVISENOTSUPPORTED;
- }
-
-private:
-
- int32 lookupFormatEtc(FORMATETC* pFormatEtc) const
- {
- // Check each of our formats in turn to see if one matches
- for (int32 i = 0; i < _formatsCount; i++)
- {
- if ((_formatEtc[i].tymed & pFormatEtc->tymed) &&
- _formatEtc[i].cfFormat == pFormatEtc->cfFormat &&
- _formatEtc[i].dwAspect == pFormatEtc->dwAspect)
- {
- // Return index of stored format
- return i;
- }
- }
-
- // Format not found
- return INVALID_INDEX;
- }
-};
-
-WindowsGuiData GuiDragDropData;
-
-///
-/// Async DoDragDrop helper (used for rendering frames during main thread stall).
-///
-class DoDragDropJob : public ThreadPoolTask
-{
-public:
-
- int64 ExitFlag = 0;
-
- // [ThreadPoolTask]
- bool Run() override
- {
- Scripting::GetScriptsDomain()->Dispatch();
- while (Platform::AtomicRead(&ExitFlag) == 0)
- {
- Engine::OnDraw();
- Platform::Sleep(20);
- }
- return false;
- }
-};
-
-DragDropEffect WindowsWindow::DoDragDrop(const StringView& data)
-{
- // Create background worker that will keep updating GUI (perform rendering)
- const auto task = New();
- Task::StartNew(task);
- while (task->GetState() == TaskState::Queued)
- {
- Platform::Sleep(1);
- }
-
- // Create descriptors
- FORMATETC fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
- STGMEDIUM stgmed = { TYMED_HGLOBAL, { nullptr }, nullptr };
-
- // Create a HGLOBAL inside the storage medium
- stgmed.hGlobal = StringToHandle(data);
-
- // Create drop source
- auto dropSource = new WindowsDragSource(&fmtetc, &stgmed, 1);
-
- // Do the drag drop operation
- DWORD dwEffect;
- HRESULT result = ::DoDragDrop(dropSource, dropSource, DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK | DROPEFFECT_SCROLL, &dwEffect);
-
- // Wait for job end
- Platform::AtomicStore(&task->ExitFlag, 1);
- task->Wait();
-
- // Release allocated data
- dropSource->Release();
- ReleaseStgMedium(&stgmed);
-
- // Fix hanging mouse state (Windows doesn't send WM_LBUTTONUP when we end the drag and drop)
- if (Input::GetMouseButton(MouseButton::Left))
- {
- ::POINT point;
- ::GetCursorPos(&point);
- Input::Mouse->OnMouseUp(Float2((float)point.x, (float)point.y), MouseButton::Left, this);
- }
-
- return SUCCEEDED(result) ? dropEffectFromOleEnum(dwEffect) : DragDropEffect::None;
-}
-
-HRESULT WindowsWindow::DragEnter(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect)
-{
- // Call GUI
- POINT p = { pt.x, pt.y };
- ::ScreenToClient(_handle, &p);
- GuiDragDropData.Init((IDataObject*)pDataObj);
- DragDropEffect effect = DragDropEffect::None;
- OnDragEnter(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect);
-
- // Focus
- Focus();
-
- // Translate effect into Ole Api type
- *pdwEffect = dropEffect2OleEnum(effect);
-
- return S_OK;
-}
-
-HRESULT WindowsWindow::DragOver(Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect)
-{
- // Call GUI
- POINT p = { pt.x, pt.y };
- ::ScreenToClient(_handle, &p);
- DragDropEffect effect = DragDropEffect::None;
- OnDragOver(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect);
-
- // Translate effect into Ole Api type
- *pdwEffect = dropEffect2OleEnum(effect);
-
- return S_OK;
-}
-
-HRESULT WindowsWindow::DragLeave()
-{
- // Call GUI
- OnDragLeave();
-
- return S_OK;
-}
-
-HRESULT WindowsWindow::Drop(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect)
-{
- // Call GUI
- POINT p = { pt.x, pt.y };
- ::ScreenToClient(_handle, &p);
- GuiDragDropData.Init((IDataObject*)pDataObj);
- DragDropEffect effect = DragDropEffect::None;
- OnDragDrop(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect);
-
- // Translate effect into Ole Api type
- *pdwEffect = dropEffect2OleEnum(effect);
-
- return S_OK;
-}
-
-#else
-
-DragDropEffect WindowsWindow::DoDragDrop(const StringView& data)
-{
- // Not supported
- return DragDropEffect::None;
-}
-
-#endif
-
-#endif
diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp
index fb9fbe09d..181077125 100644
--- a/Source/Engine/Platform/Windows/WindowsWindow.cpp
+++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp
@@ -10,8 +10,19 @@
#include "Engine/Graphics/GPUSwapChain.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/GPUDevice.h"
+#if USE_EDITOR
+#include "Engine/Core/Collections/Array.h"
+#include "Engine/Platform/IGuiData.h"
+#include "Engine/Platform/Base/DragDropHelper.h"
+#include "Engine/Input/Input.h"
+#include "Engine/Input/Mouse.h"
+#endif
#include "../Win32/IncludeWindowsHeaders.h"
#include
+#if USE_EDITOR
+#include
+#include
+#endif
#define DefaultDPI 96
@@ -139,7 +150,7 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings)
const HMODULE user32Dll = LoadLibraryW(L"user32.dll");
if (user32Dll)
{
- typedef UINT (STDAPICALLTYPE* GetDpiForWindowProc)(HWND hwnd);
+ typedef UINT (STDAPICALLTYPE*GetDpiForWindowProc)(HWND hwnd);
const GetDpiForWindowProc getDpiForWindowProc = (GetDpiForWindowProc)GetProcAddress(user32Dll, "GetDpiForWindow");
if (getDpiForWindowProc)
{
@@ -262,7 +273,7 @@ void WindowsWindow::Maximize()
void WindowsWindow::SetBorderless(bool isBorderless, bool maximized)
{
ASSERT(HasHWND());
-
+
if (IsFullscreen())
SetIsFullscreen(false);
@@ -278,7 +289,7 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized)
{
LONG lStyle = GetWindowLong(_handle, GWL_STYLE);
lStyle &= ~(WS_THICKFRAME | WS_SYSMENU | WS_OVERLAPPED | WS_BORDER | WS_CAPTION);
- lStyle |= WS_POPUP;
+ lStyle |= WS_POPUP;
lStyle |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
#if WINDOWS_USE_NEW_BORDER_LESS
if (_settings.IsRegularWindow)
@@ -289,8 +300,8 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized)
#endif
SetWindowLong(_handle, GWL_STYLE, lStyle);
- SetWindowPos(_handle, HWND_TOP, 0, 0,0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
-
+ SetWindowPos(_handle, HWND_TOP, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
+
if (maximized)
{
ShowWindow(_handle, SW_SHOWMAXIMIZED);
@@ -311,10 +322,10 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized)
if (_settings.HasSizingFrame)
lStyle |= WS_THICKFRAME;
lStyle |= WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION;
-
+
SetWindowLong(_handle, GWL_STYLE, lStyle);
- SetWindowPos(_handle, nullptr, 0, 0, (int)_settings.Size.X, (int)_settings.Size.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
-
+ SetWindowPos(_handle, nullptr, 0, 0, (int)_settings.Size.X, (int)_settings.Size.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
+
if (maximized)
{
Maximize();
@@ -532,6 +543,12 @@ Float2 WindowsWindow::ClientToScreen(const Float2& clientPos) const
{
ASSERT(HasHWND());
+ if (_minimized)
+ {
+ // Return cached position when window is not on screen
+ return _minimizedScreenPosition + clientPos;
+ }
+
POINT p;
p.x = static_cast(clientPos.X);
p.y = static_cast(clientPos.Y);
@@ -721,7 +738,7 @@ void WindowsWindow::CheckForWindowResize()
MONITORINFO monitorInfo;
monitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfoW(monitor, &monitorInfo);
-
+
auto cwidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
auto cheight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
if (width > cwidth && height > cheight)
@@ -1050,22 +1067,23 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam)
case WM_GETMINMAXINFO:
{
const auto minMax = reinterpret_cast(lParam);
-
- int32 borderWidth = 0, borderHeight = 0;
- if (_settings.HasBorder)
- {
- const DWORD windowStyle = GetWindowLongW(_handle, GWL_STYLE);
- const DWORD windowExStyle = GetWindowLongW(_handle, GWL_EXSTYLE);
- RECT borderRect = { 0, 0, 0, 0 };
- AdjustWindowRectEx(&borderRect, windowStyle, false, windowExStyle);
- borderWidth = borderRect.right - borderRect.left;
- borderHeight = borderRect.bottom - borderRect.top;
- }
-
minMax->ptMinTrackSize.x = (int32)_settings.MinimumSize.X;
minMax->ptMinTrackSize.y = (int32)_settings.MinimumSize.Y;
- minMax->ptMaxTrackSize.x = (int32)_settings.MaximumSize.X + borderWidth;
- minMax->ptMaxTrackSize.y = (int32)_settings.MaximumSize.Y + borderHeight;
+ if (_settings.MaximumSize.SumValues() > 0)
+ {
+ int32 borderWidth = 0, borderHeight = 0;
+ if (_settings.HasBorder)
+ {
+ const DWORD windowStyle = GetWindowLongW(_handle, GWL_STYLE);
+ const DWORD windowExStyle = GetWindowLongW(_handle, GWL_EXSTYLE);
+ RECT borderRect = { 0, 0, 0, 0 };
+ AdjustWindowRectEx(&borderRect, windowStyle, false, windowExStyle);
+ borderWidth = borderRect.right - borderRect.left;
+ borderHeight = borderRect.bottom - borderRect.top;
+ }
+ minMax->ptMaxTrackSize.x = (int32)_settings.MaximumSize.X + borderWidth;
+ minMax->ptMaxTrackSize.y = (int32)_settings.MaximumSize.Y + borderHeight;
+ }
// Include Windows task bar size into maximized tool window
WINDOWPLACEMENT e;
@@ -1109,6 +1127,28 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
if (SIZE_MINIMIZED == wParam)
{
+ // Get the minimized window position in workspace coordinates
+ WINDOWPLACEMENT placement;
+ placement.length = sizeof(WINDOWPLACEMENT);
+ GetWindowPlacement(_handle, &placement);
+
+ // Calculate client offsets from window borders and title bar
+ RECT winRect = { 0, 0, static_cast(_clientSize.X), static_cast(_clientSize.Y) };
+ LONG style = GetWindowLong(_handle, GWL_STYLE);
+ LONG exStyle = GetWindowLong(_handle, GWL_EXSTYLE);
+ AdjustWindowRectEx(&winRect, style, FALSE, exStyle);
+
+ // Calculate monitor offsets from taskbar position
+ const HMONITOR monitor = MonitorFromWindow(_handle, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO monitorInfo;
+ monitorInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfoW(monitor, &monitorInfo);
+
+ // Convert the workspace coordinates to screen space and store it
+ _minimizedScreenPosition = Float2(
+ static_cast(placement.rcNormalPosition.left + monitorInfo.rcWork.left - monitorInfo.rcMonitor.left - winRect.left),
+ static_cast(placement.rcNormalPosition.top + monitorInfo.rcWork.top - monitorInfo.rcMonitor.top - winRect.top));
+
_minimized = true;
_maximized = false;
}
@@ -1268,4 +1308,610 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam)
return DefWindowProc(_handle, msg, wParam, lParam);
}
+#if USE_EDITOR
+
+HGLOBAL duplicateGlobalMem(HGLOBAL hMem)
+{
+ auto len = GlobalSize(hMem);
+ auto source = GlobalLock(hMem);
+ auto dest = GlobalAlloc(GMEM_FIXED, len);
+ Platform::MemoryCopy(dest, source, len);
+ GlobalUnlock(hMem);
+ return dest;
+}
+
+DWORD dropEffect2OleEnum(DragDropEffect effect)
+{
+ DWORD result;
+ switch (effect)
+ {
+ case DragDropEffect::None:
+ result = DROPEFFECT_NONE;
+ break;
+ case DragDropEffect::Copy:
+ result = DROPEFFECT_COPY;
+ break;
+ case DragDropEffect::Move:
+ result = DROPEFFECT_MOVE;
+ break;
+ case DragDropEffect::Link:
+ result = DROPEFFECT_LINK;
+ break;
+ default:
+ result = DROPEFFECT_NONE;
+ break;
+ }
+ return result;
+}
+
+DragDropEffect dropEffectFromOleEnum(DWORD effect)
+{
+ DragDropEffect result;
+ switch (effect)
+ {
+ case DROPEFFECT_NONE:
+ result = DragDropEffect::None;
+ break;
+ case DROPEFFECT_COPY:
+ result = DragDropEffect::Copy;
+ break;
+ case DROPEFFECT_MOVE:
+ result = DragDropEffect::Move;
+ break;
+ case DROPEFFECT_LINK:
+ result = DragDropEffect::Link;
+ break;
+ default:
+ result = DragDropEffect::None;
+ break;
+ }
+ return result;
+}
+
+HANDLE StringToHandle(const StringView& str)
+{
+ // Allocate and lock a global memory buffer.
+ // Make it fixed data so we don't have to use GlobalLock
+ const int32 length = str.Length();
+ char* ptr = static_cast(GlobalAlloc(GMEM_FIXED, length + 1));
+
+ // Copy the string into the buffer as ANSI text
+ StringUtils::ConvertUTF162ANSI(str.Get(), ptr, length);
+ ptr[length] = '\0';
+
+ return ptr;
+}
+
+void DeepCopyFormatEtc(FORMATETC* dest, FORMATETC* source)
+{
+ // Copy the source FORMATETC into dest
+ *dest = *source;
+
+ if (source->ptd)
+ {
+ // Allocate memory for the DVTARGETDEVICE if necessary
+ dest->ptd = static_cast(CoTaskMemAlloc(sizeof(DVTARGETDEVICE)));
+
+ // Copy the contents of the source DVTARGETDEVICE into dest->ptd
+ *(dest->ptd) = *(source->ptd);
+ }
+}
+
+HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc);
+
+///
+/// GUI data for Windows platform
+///
+class WindowsGuiData : public IGuiData
+{
+private:
+ Type _type;
+ Array _data;
+
+public:
+ ///
+ /// Init
+ ///
+ WindowsGuiData()
+ : _type(Type::Unknown)
+ , _data(1)
+ {
+ }
+
+public:
+ ///
+ /// Init from Ole IDataObject
+ ///
+ /// Object
+ void Init(IDataObject* pDataObj)
+ {
+ // Temporary data
+ FORMATETC fmtetc;
+ STGMEDIUM stgmed;
+
+ // Clear
+ _type = Type::Unknown;
+ _data.Clear();
+
+ // Check type
+ fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
+ {
+ // Text
+ _type = Type::Text;
+
+ // Get data
+ char* text = static_cast(GlobalLock(stgmed.hGlobal));
+ _data.Add(String(text));
+ GlobalUnlock(stgmed.hGlobal);
+ ReleaseStgMedium(&stgmed);
+ }
+ else
+ {
+ fmtetc = { CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
+ {
+ // Unicode Text
+ _type = Type::Text;
+
+ // Get data
+ Char* text = static_cast(GlobalLock(stgmed.hGlobal));
+ _data.Add(String(text));
+ GlobalUnlock(stgmed.hGlobal);
+ ReleaseStgMedium(&stgmed);
+ }
+ else
+ {
+ fmtetc = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK)
+ {
+ // Files
+ _type = Type::Files;
+
+ // Get data
+ Char item[MAX_PATH];
+ HDROP hdrop = static_cast(GlobalLock(stgmed.hGlobal));
+ UINT filesCount = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);
+ for (UINT i = 0; i < filesCount; i++)
+ {
+ if (DragQueryFileW(hdrop, i, item, MAX_PATH) != 0)
+ {
+ _data.Add(String(item));
+ }
+ }
+ GlobalUnlock(stgmed.hGlobal);
+ ReleaseStgMedium(&stgmed);
+ }
+ }
+ }
+ }
+
+public:
+ // [IGuiData]
+ Type GetType() const override
+ {
+ return _type;
+ }
+ String GetAsText() const override
+ {
+ String result;
+ if (_type == Type::Text)
+ {
+ result = _data[0];
+ }
+ return result;
+ }
+ void GetAsFiles(Array* files) const override
+ {
+ if (_type == Type::Files)
+ {
+ files->Add(_data);
+ }
+ }
+};
+
+///
+/// Tool class for Windows Ole support
+///
+class WindowsEnumFormatEtc : public IEnumFORMATETC
+{
+private:
+ ULONG _refCount;
+ ULONG _index;
+ ULONG _formatsCount;
+ FORMATETC* _formatEtc;
+
+public:
+ WindowsEnumFormatEtc(FORMATETC* pFormatEtc, int32 nNumFormats)
+ : _refCount(1)
+ , _index(0)
+ , _formatsCount(nNumFormats)
+ , _formatEtc(nullptr)
+ {
+ // Allocate memory
+ _formatEtc = new FORMATETC[nNumFormats];
+
+ // Copy the FORMATETC structures
+ for (int32 i = 0; i < nNumFormats; i++)
+ {
+ DeepCopyFormatEtc(&_formatEtc[i], &pFormatEtc[i]);
+ }
+ }
+
+ ~WindowsEnumFormatEtc()
+ {
+ if (_formatEtc)
+ {
+ for (uint32 i = 0; i < _formatsCount; i++)
+ {
+ if (_formatEtc[i].ptd)
+ {
+ CoTaskMemFree(_formatEtc[i].ptd);
+ }
+ }
+
+ delete[] _formatEtc;
+ }
+ }
+
+public:
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override
+ {
+ // Check to see what interface has been requested
+ if (riid == IID_IEnumFORMATETC || riid == IID_IUnknown)
+ {
+ AddRef();
+ *ppvObject = this;
+ return S_OK;
+ }
+
+ // No interface
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+ ULONG STDMETHODCALLTYPE AddRef() override
+ {
+ _InterlockedIncrement(&_refCount);
+ return _refCount;
+ }
+ ULONG STDMETHODCALLTYPE Release() override
+ {
+ ULONG ulRefCount = _InterlockedDecrement(&_refCount);
+ if (_refCount == 0)
+ {
+ delete this;
+ }
+ return ulRefCount;
+ }
+
+ // [IEnumFormatEtc]
+ HRESULT STDMETHODCALLTYPE Next(ULONG celt, FORMATETC* pFormatEtc, ULONG* pceltFetched) override
+ {
+ ULONG copied = 0;
+
+ // validate arguments
+ if (celt == 0 || pFormatEtc == nullptr)
+ return E_INVALIDARG;
+
+ // copy FORMATETC structures into caller's buffer
+ while (_index < _formatsCount && copied < celt)
+ {
+ DeepCopyFormatEtc(&pFormatEtc[copied], &_formatEtc[_index]);
+ copied++;
+ _index++;
+ }
+
+ // store result
+ if (pceltFetched != nullptr)
+ *pceltFetched = copied;
+
+ // did we copy all that was requested?
+ return (copied == celt) ? S_OK : S_FALSE;
+ }
+ HRESULT STDMETHODCALLTYPE Skip(ULONG celt) override
+ {
+ _index += celt;
+ return (_index <= _formatsCount) ? S_OK : S_FALSE;
+ }
+ HRESULT STDMETHODCALLTYPE Reset() override
+ {
+ _index = 0;
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE Clone(IEnumFORMATETC** ppEnumFormatEtc) override
+ {
+ HRESULT result;
+
+ // Make a duplicate enumerator
+ result = CreateEnumFormatEtc(_formatsCount, _formatEtc, ppEnumFormatEtc);
+
+ if (result == S_OK)
+ {
+ // Manually set the index state
+ static_cast(*ppEnumFormatEtc)->_index = _index;
+ }
+
+ return result;
+ }
+};
+
+HRESULT CreateEnumFormatEtc(UINT nNumFormats, FORMATETC* pFormatEtc, IEnumFORMATETC** ppEnumFormatEtc)
+{
+ if (nNumFormats == 0 || pFormatEtc == nullptr || ppEnumFormatEtc == nullptr)
+ return E_INVALIDARG;
+ *ppEnumFormatEtc = new WindowsEnumFormatEtc(pFormatEtc, nNumFormats);
+ return *ppEnumFormatEtc ? S_OK : E_OUTOFMEMORY;
+}
+
+///
+/// Drag drop source and data container for Ole
+///
+class WindowsDragSource : public IDataObject, public IDropSource
+{
+private:
+ ULONG _refCount;
+ int32 _formatsCount;
+ FORMATETC* _formatEtc;
+ STGMEDIUM* _stgMedium;
+
+public:
+ WindowsDragSource(FORMATETC* fmtetc, STGMEDIUM* stgmed, int32 count)
+ : _refCount(1)
+ , _formatsCount(count)
+ , _formatEtc(nullptr)
+ , _stgMedium(nullptr)
+ {
+ // Allocate memory
+ _formatEtc = new FORMATETC[count];
+ _stgMedium = new STGMEDIUM[count];
+
+ // Copy descriptors
+ for (int32 i = 0; i < count; i++)
+ {
+ _formatEtc[i] = fmtetc[i];
+ _stgMedium[i] = stgmed[i];
+ }
+ }
+
+ virtual ~WindowsDragSource()
+ {
+ delete[] _formatEtc;
+ delete[] _stgMedium;
+ }
+
+public:
+ // [IUnknown]
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR * ppvObject) override
+ {
+ // Check to see what interface has been requested
+ if (riid == IID_IDataObject || riid == IID_IUnknown || riid == IID_IDropSource)
+ {
+ AddRef();
+ *ppvObject = this;
+ return S_OK;
+ }
+
+ // No interface
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ ULONG STDMETHODCALLTYPE AddRef() override
+ {
+ _InterlockedIncrement(&_refCount);
+ return _refCount;
+ }
+
+ ULONG STDMETHODCALLTYPE Release() override
+ {
+ ULONG ulRefCount = _InterlockedDecrement(&_refCount);
+ if (_refCount == 0)
+ {
+ delete this;
+ }
+ return ulRefCount;
+ }
+
+ // [IDropSource]
+ HRESULT STDMETHODCALLTYPE QueryContinueDrag(_In_ BOOL fEscapePressed, _In_ DWORD grfKeyState) override
+ {
+ // If the Escape key has been pressed since the last call, cancel the drop
+ if (fEscapePressed == TRUE || grfKeyState & MK_RBUTTON)
+ return DRAGDROP_S_CANCEL;
+
+ // If the LeftMouse button has been released, then do the drop!
+ if ((grfKeyState & MK_LBUTTON) == 0)
+ return DRAGDROP_S_DROP;
+
+ // Continue with the drag-drop
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE GiveFeedback(_In_ DWORD dwEffect) override
+ {
+ // TODO: allow to use custom mouse cursor during drop and drag operation
+ return DRAGDROP_S_USEDEFAULTCURSORS;
+ }
+
+ // [IDataObject]
+ HRESULT STDMETHODCALLTYPE GetData(_In_ FORMATETC* pformatetcIn, _Out_ STGMEDIUM* pmedium) override
+ {
+ if (pformatetcIn == nullptr || pmedium == nullptr)
+ return E_INVALIDARG;
+
+ // Try to match the specified FORMATETC with one of our supported formats
+ int32 index = lookupFormatEtc(pformatetcIn);
+ if (index == INVALID_INDEX)
+ return DV_E_FORMATETC;
+
+ // Found a match - transfer data into supplied storage medium
+ pmedium->tymed = _formatEtc[index].tymed;
+ pmedium->pUnkForRelease = nullptr;
+
+ // Copy the data into the caller's storage medium
+ switch (_formatEtc[index].tymed)
+ {
+ case TYMED_HGLOBAL:
+ pmedium->hGlobal = duplicateGlobalMem(_stgMedium[index].hGlobal);
+ break;
+
+ default:
+ return DV_E_FORMATETC;
+ }
+
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE GetDataHere(_In_ FORMATETC* pformatetc, _Inout_ STGMEDIUM* pmedium) override
+ {
+ return DATA_E_FORMATETC;
+ }
+ HRESULT STDMETHODCALLTYPE QueryGetData(__RPC__in_opt FORMATETC* pformatetc) override
+ {
+ return lookupFormatEtc(pformatetc) == INVALID_INDEX ? DV_E_FORMATETC : S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(__RPC__in_opt FORMATETC* pformatectIn, __RPC__out FORMATETC* pformatetcOut) override
+ {
+ // Apparently we have to set this field to NULL even though we don't do anything else
+ pformatetcOut->ptd = nullptr;
+ return E_NOTIMPL;
+ }
+ HRESULT STDMETHODCALLTYPE SetData(_In_ FORMATETC* pformatetc, _In_ STGMEDIUM* pmedium, BOOL fRelease) override
+ {
+ return E_NOTIMPL;
+ }
+ HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dwDirection, __RPC__deref_out_opt IEnumFORMATETC** ppenumFormatEtc) override
+ {
+ // Only the get direction is supported for OLE
+ if (dwDirection == DATADIR_GET)
+ {
+ // TODO: use SHCreateStdEnumFmtEtc API call
+ return CreateEnumFormatEtc(_formatsCount, _formatEtc, ppenumFormatEtc);
+ }
+
+ // The direction specified is not supported for drag+drop
+ return E_NOTIMPL;
+ }
+ HRESULT STDMETHODCALLTYPE DAdvise(__RPC__in FORMATETC* pformatetc, DWORD advf, __RPC__in_opt IAdviseSink* pAdvSink, __RPC__out DWORD* pdwConnection) override
+ {
+ return OLE_E_ADVISENOTSUPPORTED;
+ }
+ HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) override
+ {
+ return OLE_E_ADVISENOTSUPPORTED;
+ }
+ HRESULT STDMETHODCALLTYPE EnumDAdvise(__RPC__deref_out_opt IEnumSTATDATA** ppenumAdvise) override
+ {
+ return OLE_E_ADVISENOTSUPPORTED;
+ }
+
+private:
+ int32 lookupFormatEtc(FORMATETC* pFormatEtc) const
+ {
+ // Check each of our formats in turn to see if one matches
+ for (int32 i = 0; i < _formatsCount; i++)
+ {
+ if ((_formatEtc[i].tymed & pFormatEtc->tymed) &&
+ _formatEtc[i].cfFormat == pFormatEtc->cfFormat &&
+ _formatEtc[i].dwAspect == pFormatEtc->dwAspect)
+ {
+ // Return index of stored format
+ return i;
+ }
+ }
+
+ // Format not found
+ return INVALID_INDEX;
+ }
+};
+
+WindowsGuiData GuiDragDropData;
+
+DragDropEffect WindowsWindow::DoDragDrop(const StringView& data)
+{
+ // Create background worker that will keep updating GUI (perform rendering)
+ const auto task = New();
+ Task::StartNew(task);
+ while (task->GetState() == TaskState::Queued)
+ Platform::Sleep(1);
+
+ // Create descriptors
+ FORMATETC fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ STGMEDIUM stgmed = { TYMED_HGLOBAL, { nullptr }, nullptr };
+
+ // Create a HGLOBAL inside the storage medium
+ stgmed.hGlobal = StringToHandle(data);
+
+ // Create drop source
+ auto dropSource = new WindowsDragSource(&fmtetc, &stgmed, 1);
+
+ // Do the drag drop operation
+ DWORD dwEffect;
+ HRESULT result = ::DoDragDrop(dropSource, dropSource, DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK | DROPEFFECT_SCROLL, &dwEffect);
+
+ // Wait for job end
+ Platform::AtomicStore(&task->ExitFlag, 1);
+ task->Wait();
+
+ // Release allocated data
+ dropSource->Release();
+ ReleaseStgMedium(&stgmed);
+
+ // Fix hanging mouse state (Windows doesn't send WM_LBUTTONUP when we end the drag and drop)
+ if (Input::GetMouseButton(MouseButton::Left))
+ {
+ ::POINT point;
+ ::GetCursorPos(&point);
+ Input::Mouse->OnMouseUp(Float2((float)point.x, (float)point.y), MouseButton::Left, this);
+ }
+
+ return SUCCEEDED(result) ? dropEffectFromOleEnum(dwEffect) : DragDropEffect::None;
+}
+
+HRESULT WindowsWindow::DragEnter(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect)
+{
+ POINT p = { pt.x, pt.y };
+ ::ScreenToClient(_handle, &p);
+ GuiDragDropData.Init((IDataObject*)pDataObj);
+ DragDropEffect effect = DragDropEffect::None;
+ OnDragEnter(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect);
+ Focus();
+ *pdwEffect = dropEffect2OleEnum(effect);
+ return S_OK;
+}
+
+HRESULT WindowsWindow::DragOver(Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect)
+{
+ POINT p = { pt.x, pt.y };
+ ::ScreenToClient(_handle, &p);
+ DragDropEffect effect = DragDropEffect::None;
+ OnDragOver(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect);
+ *pdwEffect = dropEffect2OleEnum(effect);
+ return S_OK;
+}
+
+HRESULT WindowsWindow::DragLeave()
+{
+ OnDragLeave();
+ return S_OK;
+}
+
+HRESULT WindowsWindow::Drop(Windows::IDataObject* pDataObj, Windows::DWORD grfKeyState, Windows::POINTL pt, Windows::DWORD* pdwEffect)
+{
+ POINT p = { pt.x, pt.y };
+ ::ScreenToClient(_handle, &p);
+ GuiDragDropData.Init((IDataObject*)pDataObj);
+ DragDropEffect effect = DragDropEffect::None;
+ OnDragDrop(&GuiDragDropData, Float2(static_cast(p.x), static_cast(p.y)), effect);
+ *pdwEffect = dropEffect2OleEnum(effect);
+ return S_OK;
+}
+
+#else
+
+DragDropEffect WindowsWindow::DoDragDrop(const StringView& data)
+{
+ return DragDropEffect::None;
+}
+
+#endif
+
#endif
diff --git a/Source/Engine/Platform/Windows/WindowsWindow.h b/Source/Engine/Platform/Windows/WindowsWindow.h
index e15f86a45..186793b1e 100644
--- a/Source/Engine/Platform/Windows/WindowsWindow.h
+++ b/Source/Engine/Platform/Windows/WindowsWindow.h
@@ -32,6 +32,7 @@ private:
Windows::HANDLE _monitor = nullptr;
Windows::LONG _clipCursorRect[4];
int32 _regionWidth = 0, _regionHeight = 0;
+ Float2 _minimizedScreenPosition = Float2::Zero;
public:
diff --git a/Source/Engine/Platform/iOS/iOSPlatform.cpp b/Source/Engine/Platform/iOS/iOSPlatform.cpp
index 7ca27c481..26c3b8f28 100644
--- a/Source/Engine/Platform/iOS/iOSPlatform.cpp
+++ b/Source/Engine/Platform/iOS/iOSPlatform.cpp
@@ -410,7 +410,7 @@ bool iOSWindow::IsClosed() const
bool iOSWindow::IsForegroundWindow() const
{
- return Platform::GetHasFocus() && IsFocused();
+ return IsFocused() && Platform::GetHasFocus();
}
void iOSWindow::BringToFront(bool force)
diff --git a/Source/Engine/Profiler/ProfilerGPU.cpp b/Source/Engine/Profiler/ProfilerGPU.cpp
index 3054abe67..6944441be 100644
--- a/Source/Engine/Profiler/ProfilerGPU.cpp
+++ b/Source/Engine/Profiler/ProfilerGPU.cpp
@@ -74,6 +74,7 @@ void ProfilerGPU::EventBuffer::Clear()
_data.Clear();
_isResolved = false;
FrameIndex = 0;
+ PresentTime = 0.0f;
}
GPUTimerQuery* ProfilerGPU::GetTimerQuery()
@@ -133,7 +134,9 @@ void ProfilerGPU::BeginFrame()
// Clear stats
RenderStatsData::Counter = RenderStatsData();
_depth = 0;
- Buffers[CurrentBuffer].FrameIndex = Engine::FrameCount;
+ auto& buffer = Buffers[CurrentBuffer];
+ buffer.FrameIndex = Engine::FrameCount;
+ buffer.PresentTime = 0.0f;
// Try to resolve previous frames
for (int32 i = 0; i < PROFILER_GPU_EVENTS_FRAMES; i++)
@@ -149,6 +152,12 @@ void ProfilerGPU::OnPresent()
buffer.EndAll();
}
+void ProfilerGPU::OnPresentTime(float time)
+{
+ auto& buffer = Buffers[CurrentBuffer];
+ buffer.PresentTime += time;
+}
+
void ProfilerGPU::EndFrame()
{
if (_depth)
@@ -164,7 +173,7 @@ void ProfilerGPU::EndFrame()
buffer.Clear();
}
-bool ProfilerGPU::GetLastFrameData(float& drawTimeMs, RenderStatsData& statsData)
+bool ProfilerGPU::GetLastFrameData(float& drawTimeMs, float& presentTimeMs, RenderStatsData& statsData)
{
uint64 maxFrame = 0;
int32 maxFrameIndex = -1;
@@ -177,17 +186,19 @@ bool ProfilerGPU::GetLastFrameData(float& drawTimeMs, RenderStatsData& statsData
maxFrameIndex = i;
}
}
-
if (maxFrameIndex != -1)
{
auto& frame = frames[maxFrameIndex];
const auto root = frame.Get(0);
drawTimeMs = root->Time;
+ presentTimeMs = frame.PresentTime;
statsData = root->Stats;
return true;
}
+ // No data
drawTimeMs = 0.0f;
+ presentTimeMs = 0.0f;
Platform::MemoryClear(&statsData, sizeof(statsData));
return false;
}
diff --git a/Source/Engine/Profiler/ProfilerGPU.h b/Source/Engine/Profiler/ProfilerGPU.h
index 29392b7a5..94ed345b9 100644
--- a/Source/Engine/Profiler/ProfilerGPU.h
+++ b/Source/Engine/Profiler/ProfilerGPU.h
@@ -20,6 +20,8 @@ class GPUTimerQuery;
API_CLASS(Static) class FLAXENGINE_API ProfilerGPU
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(ProfilerGPU);
+ friend class Engine;
+ friend class GPUDevice;
public:
///
/// Represents single CPU profiling event data.
@@ -69,6 +71,11 @@ public:
///
uint64 FrameIndex;
+ ///
+ /// Sum of all present events duration on CPU during this frame (in milliseconds).
+ ///
+ float PresentTime;
+
///
/// Determines whether this buffer has ready data (resolved and not empty).
///
@@ -125,7 +132,7 @@ public:
///
/// True if GPU profiling is enabled, otherwise false to disable events collecting and GPU timer queries usage. Can be changed during rendering.
///
- static bool Enabled;
+ API_FIELD() static bool Enabled;
///
/// The current frame buffer to collect events.
@@ -151,32 +158,20 @@ public:
/// The event token index returned by the BeginEvent method.
static void EndEvent(int32 index);
- ///
- /// Begins the new frame rendering. Called by the engine to sync profiling data.
- ///
- static void BeginFrame();
-
- ///
- /// Called when just before flushing current frame GPU commands (via Present or Flush). Call active timer queries should be ended now.
- ///
- static void OnPresent();
-
- ///
- /// Ends the frame rendering. Called by the engine to sync profiling data.
- ///
- static void EndFrame();
-
///
/// Tries to get the rendering stats from the last frame drawing (that has been resolved and has valid data).
///
/// The draw execution time on a GPU (in milliseconds).
+ /// The final frame present time on a CPU (in milliseconds). Time game waited for vsync or GPU to finish previous frame rendering.
/// The rendering stats data.
/// True if got the data, otherwise false.
- static bool GetLastFrameData(float& drawTimeMs, RenderStatsData& statsData);
+ API_FUNCTION() static bool GetLastFrameData(float& drawTimeMs, float& presentTimeMs, RenderStatsData& statsData);
- ///
- /// Releases resources. Calls to the profiling API after Dispose are not valid
- ///
+private:
+ static void BeginFrame();
+ static void OnPresent();
+ static void OnPresentTime(float time);
+ static void EndFrame();
static void Dispose();
};
diff --git a/Source/Engine/Profiler/ProfilingTools.cpp b/Source/Engine/Profiler/ProfilingTools.cpp
index 61c0e2c33..113a04ec8 100644
--- a/Source/Engine/Profiler/ProfilingTools.cpp
+++ b/Source/Engine/Profiler/ProfilingTools.cpp
@@ -48,7 +48,9 @@ void ProfilingToolsService::Update()
stats.PhysicsTimeMs = static_cast(Time::Physics.LastLength * 1000.0);
stats.DrawCPUTimeMs = static_cast(Time::Draw.LastLength * 1000.0);
- ProfilerGPU::GetLastFrameData(stats.DrawGPUTimeMs, stats.DrawStats);
+ float presentTime;
+ ProfilerGPU::GetLastFrameData(stats.DrawGPUTimeMs, presentTime, stats.DrawStats);
+ stats.DrawCPUTimeMs = Math::Max(stats.DrawCPUTimeMs - presentTime, 0.0f); // Remove swapchain present wait time to exclude from drawing on CPU
}
// Extract CPU profiler events
diff --git a/Source/Engine/Render2D/SpriteAtlas.cpp b/Source/Engine/Render2D/SpriteAtlas.cpp
index 0e0aa071f..ac711b8b8 100644
--- a/Source/Engine/Render2D/SpriteAtlas.cpp
+++ b/Source/Engine/Render2D/SpriteAtlas.cpp
@@ -47,10 +47,16 @@ int32 SpriteAtlas::GetSpritesCount() const
Sprite SpriteAtlas::GetSprite(int32 index) const
{
- CHECK_RETURN(index >= 0 && index < Sprites.Count(), Sprite())
+ CHECK_RETURN(index >= 0 && index < Sprites.Count(), Sprite());
return Sprites.Get()[index];
}
+void SpriteAtlas::GetSpriteArea(int32 index, Rectangle& result) const
+{
+ CHECK(index >= 0 && index < Sprites.Count());
+ result = Sprites.Get()[index].Area;
+}
+
void SpriteAtlas::SetSprite(int32 index, const Sprite& value)
{
CHECK(index >= 0 && index < Sprites.Count());
diff --git a/Source/Engine/Render2D/SpriteAtlas.cs b/Source/Engine/Render2D/SpriteAtlas.cs
index b3796ffd3..93cdf68b2 100644
--- a/Source/Engine/Render2D/SpriteAtlas.cs
+++ b/Source/Engine/Render2D/SpriteAtlas.cs
@@ -70,7 +70,13 @@ namespace FlaxEngine
[NoSerialize]
public Float2 Size
{
- get => Area.Size * Atlas.Size;
+ get
+ {
+ if (Atlas == null)
+ throw new InvalidOperationException("Cannot use invalid sprite.");
+ Atlas.GetSpriteArea(Index, out var area);
+ return area.Size * Atlas.Size;
+ }
set
{
var area = Area;
@@ -89,7 +95,8 @@ namespace FlaxEngine
{
if (Atlas == null)
throw new InvalidOperationException("Cannot use invalid sprite.");
- return Atlas.GetSprite(Index).Area;
+ Atlas.GetSpriteArea(Index, out var area);
+ return area;
}
set
{
diff --git a/Source/Engine/Render2D/SpriteAtlas.h b/Source/Engine/Render2D/SpriteAtlas.h
index 6fcda5162..533440c68 100644
--- a/Source/Engine/Render2D/SpriteAtlas.h
+++ b/Source/Engine/Render2D/SpriteAtlas.h
@@ -120,6 +120,14 @@ public:
/// The sprite data.
API_FUNCTION() Sprite GetSprite(int32 index) const;
+ ///
+ /// Gets the sprite area.
+ ///
+ /// The index.
+ /// The output sprite area.
+ /// The sprite data.
+ API_FUNCTION() void GetSpriteArea(int32 index, API_PARAM(Out) Rectangle& result) const;
+
///
/// Sets the sprite data.
///
diff --git a/Source/Engine/Renderer/Config.h b/Source/Engine/Renderer/Config.h
index 659369a71..876da3100 100644
--- a/Source/Engine/Renderer/Config.h
+++ b/Source/Engine/Renderer/Config.h
@@ -114,6 +114,3 @@ PACK_STRUCT(struct ProbeData {
// Maximum amount of directional light cascades (using CSM technique)
#define MAX_CSM_CASCADES 4
-
-// Default format for the shadow map textures
-#define SHADOW_MAPS_FORMAT PixelFormat::D16_UNorm
diff --git a/Source/Engine/Renderer/DepthOfFieldPass.cpp b/Source/Engine/Renderer/DepthOfFieldPass.cpp
index 4c43ccdbf..707a441e6 100644
--- a/Source/Engine/Renderer/DepthOfFieldPass.cpp
+++ b/Source/Engine/Renderer/DepthOfFieldPass.cpp
@@ -202,16 +202,14 @@ GPUTexture* DepthOfFieldPass::getDofBokehShape(DepthOfFieldSettings& dofSettings
void DepthOfFieldPass::Render(RenderContext& renderContext, GPUTexture*& frame, GPUTexture*& tmp)
{
- if (!_platformSupportsDoF || checkIfSkipPass())
+ DepthOfFieldSettings& dofSettings = renderContext.List->Settings.DepthOfField;
+ const bool useDoF = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::DepthOfField) && dofSettings.Enabled;
+ if (!useDoF || !_platformSupportsDoF || checkIfSkipPass())
return;
auto device = GPUDevice::Instance;
auto context = device->GetMainContext();
const auto depthBuffer = renderContext.Buffers->DepthBuffer;
const auto shader = _shader->GetShader();
- DepthOfFieldSettings& dofSettings = renderContext.List->Settings.DepthOfField;
- const bool useDoF = _platformSupportsDoF && EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::DepthOfField) && dofSettings.Enabled;
- if (!useDoF)
- return;
PROFILE_GPU_CPU("Depth Of Field");
context->ResetSR();
diff --git a/Source/Engine/Renderer/EyeAdaptationPass.cpp b/Source/Engine/Renderer/EyeAdaptationPass.cpp
index 12b26ea41..3a840ff01 100644
--- a/Source/Engine/Renderer/EyeAdaptationPass.cpp
+++ b/Source/Engine/Renderer/EyeAdaptationPass.cpp
@@ -35,7 +35,6 @@ PACK_STRUCT(struct EyeAdaptationData {
void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBuffer)
{
- // Cache data
auto device = GPUDevice::Instance;
auto context = device->GetMainContext();
auto& view = renderContext.View;
@@ -45,12 +44,8 @@ void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBu
//const float frameDelta = Time::ElapsedGameTime.GetTotalSeconds();
const float frameDelta = time - renderContext.Buffers->LastEyeAdaptationTime;
renderContext.Buffers->LastEyeAdaptationTime = 0.0f;
-
- // Optionally skip the rendering
- if (checkIfSkipPass() || (view.Flags & ViewFlags::EyeAdaptation) == ViewFlags::None || settings.Mode == EyeAdaptationMode::None)
- {
+ if ((view.Flags & ViewFlags::EyeAdaptation) == ViewFlags::None || settings.Mode == EyeAdaptationMode::None || checkIfSkipPass())
return;
- }
PROFILE_GPU_CPU("Eye Adaptation");
diff --git a/Source/Engine/Renderer/MotionBlurPass.cpp b/Source/Engine/Renderer/MotionBlurPass.cpp
index d425df80f..2030027ed 100644
--- a/Source/Engine/Renderer/MotionBlurPass.cpp
+++ b/Source/Engine/Renderer/MotionBlurPass.cpp
@@ -244,7 +244,7 @@ void MotionBlurPass::RenderDebug(RenderContext& renderContext, GPUTextureView* f
{
auto context = GPUDevice::Instance->GetMainContext();
const auto motionVectors = renderContext.Buffers->MotionVectors;
- if (!motionVectors->IsAllocated() || checkIfSkipPass())
+ if (!motionVectors || !motionVectors->IsAllocated() || checkIfSkipPass())
{
context->Draw(frame);
return;
diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp
index 345147b61..006927639 100644
--- a/Source/Engine/Renderer/PostProcessingPass.cpp
+++ b/Source/Engine/Renderer/PostProcessingPass.cpp
@@ -195,7 +195,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input,
bool useLensFlares = EnumHasAnyFlags(view.Flags, ViewFlags::LensFlares) && settings.LensFlares.Intensity > 0.0f && useBloom;
// Ensure to have valid data and if at least one effect should be applied
- if (checkIfSkipPass() || !(useBloom || useToneMapping || useCameraArtifacts))
+ if (!(useBloom || useToneMapping || useCameraArtifacts) || checkIfSkipPass())
{
// Resources are missing. Do not perform rendering. Just copy raw frame
context->SetViewportAndScissors((float)output->Width(), (float)output->Height());
diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp
index e24022b07..a965dee09 100644
--- a/Source/Engine/Renderer/ProbesRenderer.cpp
+++ b/Source/Engine/Renderer/ProbesRenderer.cpp
@@ -105,7 +105,7 @@ class ProbesRendererService : public EngineService
{
public:
ProbesRendererService()
- : EngineService(TEXT("Probes Renderer"), 70)
+ : EngineService(TEXT("Probes Renderer"), 500)
{
}
diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp
index edac958bc..833daed8e 100644
--- a/Source/Engine/Renderer/Renderer.cpp
+++ b/Source/Engine/Renderer/Renderer.cpp
@@ -343,6 +343,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
// Prepare
renderContext.View.Prepare(renderContext);
renderContext.Buffers->Prepare();
+ ShadowsPass::Instance()->Prepare();
// Build batch of render contexts (main view and shadow projections)
{
diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp
index 7e0cd1053..ba326b728 100644
--- a/Source/Engine/Renderer/ShadowsPass.cpp
+++ b/Source/Engine/Renderer/ShadowsPass.cpp
@@ -4,11 +4,11 @@
#include "GBufferPass.h"
#include "VolumetricFogPass.h"
#include "Engine/Graphics/Graphics.h"
+#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Content/Content.h"
-#include "Engine/Graphics/GPUContext.h"
#include "Engine/Scripting/Enums.h"
#if USE_EDITOR
#include "Engine/Renderer/Lightmaps.h"
@@ -81,19 +81,22 @@ bool ShadowsPass::Init()
_shader.Get()->OnReloading.Bind(this);
#endif
- // If GPU doesn't support linear sampling for the shadow map then fallback to the single sample on lowest quality
- const auto formatTexture = PixelFormatExtensions::FindShaderResourceFormat(SHADOW_MAPS_FORMAT, false);
- const auto formatFeaturesDepth = GPUDevice::Instance->GetFormatFeatures(SHADOW_MAPS_FORMAT);
- const auto formatFeaturesTexture = GPUDevice::Instance->GetFormatFeatures(formatTexture);
- _supportsShadows = EnumHasAllFlags(formatFeaturesDepth.Support, FormatSupport::DepthStencil | FormatSupport::Texture2D)
- && EnumHasAllFlags(formatFeaturesTexture.Support, FormatSupport::ShaderSample | FormatSupport::ShaderSampleComparison);
- // TODO: fallback to 32-bit shadow map format if 16-bit is not supported
- if (!_supportsShadows)
+ // Select format for shadow maps
+ _shadowMapFormat = PixelFormat::Unknown;
+ for (const PixelFormat format : { PixelFormat::D16_UNorm, PixelFormat::D24_UNorm_S8_UInt, PixelFormat::D32_Float })
{
- LOG(Warning, "GPU doesn't support shadows rendering");
- LOG(Warning, "Format: {0}, features support: {1}", ScriptingEnum::ToString(SHADOW_MAPS_FORMAT), (uint32)formatFeaturesDepth.Support);
- LOG(Warning, "Format: {0}, features support: {1}", ScriptingEnum::ToString(formatTexture), (uint32)formatFeaturesTexture.Support);
+ const auto formatTexture = PixelFormatExtensions::FindShaderResourceFormat(format, false);
+ const auto formatFeaturesDepth = GPUDevice::Instance->GetFormatFeatures(format);
+ const auto formatFeaturesTexture = GPUDevice::Instance->GetFormatFeatures(formatTexture);
+ if (EnumHasAllFlags(formatFeaturesDepth.Support, FormatSupport::DepthStencil | FormatSupport::Texture2D) &&
+ EnumHasAllFlags(formatFeaturesTexture.Support, FormatSupport::ShaderSample | FormatSupport::ShaderSampleComparison))
+ {
+ _shadowMapFormat = format;
+ break;
+ }
}
+ if (_shadowMapFormat == PixelFormat::Unknown)
+ LOG(Warning, "GPU doesn't support shadows rendering");
return false;
}
@@ -148,7 +151,7 @@ void ShadowsPass::updateShadowMapSize()
// Select new size
_currentShadowMapsQuality = Graphics::ShadowMapsQuality;
- if (_supportsShadows)
+ if (_shadowMapFormat != PixelFormat::Unknown)
{
switch (_currentShadowMapsQuality)
{
@@ -174,18 +177,18 @@ void ShadowsPass::updateShadowMapSize()
// Check if size will change
if (newSizeCSM > 0 && newSizeCSM != _shadowMapsSizeCSM)
{
- if (_shadowMapCSM->Init(GPUTextureDescription::New2D(newSizeCSM, newSizeCSM, SHADOW_MAPS_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil, 1, MAX_CSM_CASCADES)))
+ if (_shadowMapCSM->Init(GPUTextureDescription::New2D(newSizeCSM, newSizeCSM, _shadowMapFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil, 1, MAX_CSM_CASCADES)))
{
- LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("CSM"), newSizeCSM, (int32)SHADOW_MAPS_FORMAT);
+ LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("CSM"), newSizeCSM, ScriptingEnum::ToString(_shadowMapFormat));
return;
}
_shadowMapsSizeCSM = newSizeCSM;
}
if (newSizeCube > 0 && newSizeCube != _shadowMapsSizeCube)
{
- if (_shadowMapCube->Init(GPUTextureDescription::NewCube(newSizeCube, SHADOW_MAPS_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil)))
+ if (_shadowMapCube->Init(GPUTextureDescription::NewCube(newSizeCube, _shadowMapFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil)))
{
- LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("Cube"), newSizeCube, (int32)SHADOW_MAPS_FORMAT);
+ LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("Cube"), newSizeCube, ScriptingEnum::ToString(_shadowMapFormat));
return;
}
_shadowMapsSizeCube = newSizeCube;
@@ -546,10 +549,17 @@ void ShadowsPass::Dispose()
SAFE_DELETE_GPU_RESOURCE(_shadowMapCube);
}
+void ShadowsPass::Prepare()
+{
+ // Clear cached data
+ _shadowData.Clear();
+ LastDirLightIndex = -1;
+ LastDirLightShadowMap = nullptr;
+}
+
void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch& renderContextBatch)
{
PROFILE_CPU();
- _shadowData.Clear();
auto& view = renderContext.View;
// Update shadow map
@@ -586,7 +596,7 @@ bool ShadowsPass::CanRenderShadow(const RenderContext& renderContext, const Rend
const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance);
- return fade > ZeroTolerance && _supportsShadows;
+ return fade > ZeroTolerance && _shadowMapFormat != PixelFormat::Unknown;
}
bool ShadowsPass::CanRenderShadow(const RenderContext& renderContext, const RendererSpotLightData& light)
@@ -598,12 +608,12 @@ bool ShadowsPass::CanRenderShadow(const RenderContext& renderContext, const Rend
const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance);
- return fade > ZeroTolerance && _supportsShadows;
+ return fade > ZeroTolerance && _shadowMapFormat != PixelFormat::Unknown;
}
bool ShadowsPass::CanRenderShadow(const RenderContext& renderContext, const RendererDirectionalLightData& light)
{
- return _supportsShadows;
+ return _shadowMapFormat != PixelFormat::Unknown;
}
void ShadowsPass::RenderShadow(RenderContextBatch& renderContextBatch, RendererPointLightData& light, GPUTextureView* shadowMask)
diff --git a/Source/Engine/Renderer/ShadowsPass.h b/Source/Engine/Renderer/ShadowsPass.h
index 176fbd9a0..d3589d008 100644
--- a/Source/Engine/Renderer/ShadowsPass.h
+++ b/Source/Engine/Renderer/ShadowsPass.h
@@ -55,7 +55,7 @@ private:
GPUPipelineStatePermutationsPs(Quality::MAX) * 2 * 2> _psShadowDir;
GPUPipelineStatePermutationsPs(Quality::MAX) * 2> _psShadowPoint;
GPUPipelineStatePermutationsPs(Quality::MAX) * 2> _psShadowSpot;
- bool _supportsShadows;
+ PixelFormat _shadowMapFormat;
// Shadow maps stuff
int32 _shadowMapsSizeCSM;
@@ -94,6 +94,8 @@ public:
LightShadowData LastDirLight;
public:
+ void Prepare();
+
///
/// Setups the shadows rendering for batched scene drawing. Checks which lights will cast a shadow.
///
diff --git a/Source/Engine/Scripting/Attributes/PluginLoadOrder.cs b/Source/Engine/Scripting/Attributes/PluginLoadOrder.cs
new file mode 100644
index 000000000..18a6a1dac
--- /dev/null
+++ b/Source/Engine/Scripting/Attributes/PluginLoadOrder.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace FlaxEngine;
+
+///
+/// This attribute allows for specifying initialization and deinitialization order for plugins.
+///
+[Serializable]
+[AttributeUsage(AttributeTargets.Class)]
+public class PluginLoadOrderAttribute : Attribute
+{
+ ///
+ /// The plugin type to initialize this plugin after.
+ ///
+ public Type InitializeAfter;
+
+ ///
+ /// The plugin type to deinitialize this plugin before.
+ ///
+ public Type DeinitializeBefore;
+}
diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp
index efd147917..679ead4e3 100644
--- a/Source/Engine/Scripting/BinaryModule.cpp
+++ b/Source/Engine/Scripting/BinaryModule.cpp
@@ -1270,7 +1270,11 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
// Invoke the method
MObject* exception = nullptr;
+#if USE_NETCORE // NetCore uses the same path for both virtual and non-virtual calls
+ MObject* resultObject = mMethod->Invoke(mInstance, params, &exception);
+#else
MObject* resultObject = withInterfaces ? mMethod->InvokeVirtual((MObject*)mInstance, params, &exception) : mMethod->Invoke(mInstance, params, &exception);
+#endif
if (exception)
{
MException ex(exception);
diff --git a/Source/Engine/Scripting/Object.cs b/Source/Engine/Scripting/Object.cs
index c64532c2b..9fc7d8039 100644
--- a/Source/Engine/Scripting/Object.cs
+++ b/Source/Engine/Scripting/Object.cs
@@ -320,7 +320,7 @@ namespace FlaxEngine
internal static partial Object Internal_Create2(string typeName);
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceCreated", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
- internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr theKlass);
+ internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr typeClass);
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceDeleted", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial void Internal_ManagedInstanceDeleted(IntPtr nativeInstance);
diff --git a/Source/Engine/Scripting/Plugins/PluginManager.cpp b/Source/Engine/Scripting/Plugins/PluginManager.cpp
index 328afa69c..61d7bc698 100644
--- a/Source/Engine/Scripting/Plugins/PluginManager.cpp
+++ b/Source/Engine/Scripting/Plugins/PluginManager.cpp
@@ -13,6 +13,7 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Core/Log.h"
+#include "Engine/Scripting/ManagedCLR/MField.h"
Plugin::Plugin(const SpawnParams& params)
: ScriptingObject(params)
@@ -74,14 +75,57 @@ Action PluginManager::PluginsChanged;
namespace PluginManagerImpl
{
+ bool Initialized = false;
Array GamePlugins;
Array EditorPlugins;
- void LoadPlugin(MClass* klass, bool isEditor);
void OnAssemblyLoaded(MAssembly* assembly);
void OnAssemblyUnloading(MAssembly* assembly);
void OnBinaryModuleLoaded(BinaryModule* module);
void OnScriptsReloading();
+ void InitializePlugins();
+ void DeinitializePlugins();
+
+ template
+ Array SortPlugins(Array plugins, MClass* pluginLoadOrderAttribute, MField* typeField)
+ {
+ // Sort plugins
+ Array newPlugins;
+ for (int i = 0; i < plugins.Count(); i++)
+ {
+ PluginType* plugin = plugins[i];
+ int32 insertIndex = -1;
+ for (int j = 0; j < newPlugins.Count(); j++)
+ {
+ // Get first instance where a game plugin needs another one before it
+ auto attribute = newPlugins[j]->GetClass()->GetAttribute(pluginLoadOrderAttribute);
+ if (attribute == nullptr || MCore::Object::GetClass(attribute) != pluginLoadOrderAttribute)
+ continue;
+
+ // Check if attribute references a valid class
+ MTypeObject* refType = nullptr;
+ typeField->GetValue(attribute, &refType);
+ if (refType == nullptr)
+ continue;
+
+ MType* type = INTERNAL_TYPE_OBJECT_GET(refType);
+ if (type == nullptr)
+ continue;
+ MClass* typeClass = MCore::Type::GetClass(type);
+
+ if (plugin->GetClass() == typeClass)
+ {
+ insertIndex = j;
+ break;
+ }
+ }
+ if (insertIndex == -1)
+ newPlugins.Add(plugin);
+ else
+ newPlugins.Insert(insertIndex, plugin);
+ }
+ return newPlugins;
+ }
}
using namespace PluginManagerImpl;
@@ -90,7 +134,7 @@ class PluginManagerService : public EngineService
{
public:
PluginManagerService()
- : EngineService(TEXT("Plugin Manager"), 130)
+ : EngineService(TEXT("Plugin Manager"), 100)
{
}
@@ -107,7 +151,7 @@ void PluginManagerService::InvokeInitialize(Plugin* plugin)
{
if (plugin->_initialized)
return;
- StringAnsiView typeName = plugin->GetType().GetName();
+ const StringAnsiView typeName = plugin->GetType().GetName();
PROFILE_CPU();
ZoneName(typeName.Get(), typeName.Length());
@@ -125,7 +169,7 @@ void PluginManagerService::InvokeDeinitialize(Plugin* plugin)
{
if (!plugin->_initialized)
return;
- StringAnsiView typeName = plugin->GetType().GetName();
+ const StringAnsiView typeName = plugin->GetType().GetName();
PROFILE_CPU();
ZoneName(typeName.Get(), typeName.Length());
@@ -139,30 +183,6 @@ void PluginManagerService::InvokeDeinitialize(Plugin* plugin)
PluginManager::PluginUnloaded(plugin);
}
-void PluginManagerImpl::LoadPlugin(MClass* klass, bool isEditor)
-{
- // Create and check if use it
- auto plugin = (Plugin*)Scripting::NewObject(klass);
- if (!plugin)
- return;
-
- if (!isEditor)
- {
- GamePlugins.Add((GamePlugin*)plugin);
-#if !USE_EDITOR
- PluginManagerService::InvokeInitialize(plugin);
-#endif
- }
-#if USE_EDITOR
- else
- {
- EditorPlugins.Add(plugin);
- PluginManagerService::InvokeInitialize(plugin);
- }
-#endif
- PluginManager::PluginsChanged();
-}
-
void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly)
{
PROFILE_CPU_NAMED("Load Assembly Plugins");
@@ -183,6 +203,7 @@ void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly)
#endif
// Process all classes to find plugins
+ bool loadedAnyPlugin = false;
auto& classes = assembly->GetClasses();
for (auto i = classes.Begin(); i.IsNotEnd(); ++i)
{
@@ -192,40 +213,69 @@ void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly)
if (mclass->IsGeneric() || mclass->IsStatic() || mclass->IsAbstract())
continue;
- if (mclass->IsSubClassOf(gamePluginClass))
- {
- LoadPlugin(mclass, false);
- }
-
+ if (mclass->IsSubClassOf(gamePluginClass)
#if USE_EDITOR
- if (mclass->IsSubClassOf(editorPluginClass))
- {
- LoadPlugin(mclass, true);
- }
+ || mclass->IsSubClassOf(editorPluginClass)
#endif
+ )
+ {
+ auto plugin = (Plugin*)Scripting::NewObject(mclass);
+ if (plugin)
+ {
+#if USE_EDITOR
+ if (mclass->IsSubClassOf(editorPluginClass))
+ {
+ EditorPlugins.Add(plugin);
+ }
+ else
+#endif
+ {
+ GamePlugins.Add((GamePlugin*)plugin);
+ }
+ loadedAnyPlugin = true;
+ }
+ }
+ }
+
+ // Send event and initialize newly added plugins (ignore during startup)
+ if (loadedAnyPlugin && Initialized)
+ {
+ InitializePlugins();
+ PluginManager::PluginsChanged();
}
}
void PluginManagerImpl::OnAssemblyUnloading(MAssembly* assembly)
{
bool changed = false;
- for (int32 i = EditorPlugins.Count() - 1; i >= 0 && EditorPlugins.Count() > 0; i--)
+
+ auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
+ auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute");
+ auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore");
+ ASSERT(beforeTypeField);
+
+#if USE_EDITOR
+ auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, beforeTypeField);
+ for (int32 i = editorPlugins.Count() - 1; i >= 0 && editorPlugins.Count() > 0; i--)
{
- auto plugin = EditorPlugins[i];
+ auto plugin = editorPlugins[i];
if (plugin->GetType().ManagedClass->GetAssembly() == assembly)
{
PluginManagerService::InvokeDeinitialize(plugin);
- EditorPlugins.RemoveAtKeepOrder(i);
+ EditorPlugins.Remove(plugin);
changed = true;
}
}
- for (int32 i = GamePlugins.Count() - 1; i >= 0 && GamePlugins.Count() > 0; i--)
+#endif
+
+ auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField);
+ for (int32 i = gamePlugins.Count() - 1; i >= 0 && gamePlugins.Count() > 0; i--)
{
- auto plugin = GamePlugins[i];
+ auto plugin = gamePlugins[i];
if (plugin->GetType().ManagedClass->GetAssembly() == assembly)
{
PluginManagerService::InvokeDeinitialize(plugin);
- GamePlugins.RemoveAtKeepOrder(i);
+ GamePlugins.Remove(plugin);
changed = true;
}
}
@@ -260,38 +310,83 @@ void PluginManagerImpl::OnBinaryModuleLoaded(BinaryModule* module)
void PluginManagerImpl::OnScriptsReloading()
{
// When scripting is reloading (eg. for hot-reload in Editor) we have to deinitialize plugins (Scripting service destroys C# objects later on)
- bool changed = false;
- for (int32 i = EditorPlugins.Count() - 1; i >= 0 && EditorPlugins.Count() > 0; i--)
+ DeinitializePlugins();
+}
+
+void PluginManagerImpl::InitializePlugins()
+{
+ if (EditorPlugins.Count() + GamePlugins.Count() == 0)
+ return;
+ PROFILE_CPU_NAMED("InitializePlugins");
+
+ auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
+ auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute");
+ auto afterTypeField = pluginLoadOrderAttribute->GetField("InitializeAfter");
+ ASSERT(afterTypeField);
+
+#if USE_EDITOR
+ auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, afterTypeField);
+ for (auto plugin : editorPlugins)
{
- auto plugin = EditorPlugins[i];
- {
- PluginManagerService::InvokeDeinitialize(plugin);
- EditorPlugins.RemoveAtKeepOrder(i);
- changed = true;
- }
+ PluginManagerService::InvokeInitialize(plugin);
}
- for (int32 i = GamePlugins.Count() - 1; i >= 0 && GamePlugins.Count() > 0; i--)
+#else
+ // Game plugins are managed via InitializeGamePlugins/DeinitializeGamePlugins by Editor for play mode
+ auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, afterTypeField);
+ for (auto plugin : gamePlugins)
{
- auto plugin = GamePlugins[i];
- {
- PluginManagerService::InvokeDeinitialize(plugin);
- GamePlugins.RemoveAtKeepOrder(i);
- changed = true;
- }
+ PluginManagerService::InvokeInitialize(plugin);
}
- if (changed)
- PluginManager::PluginsChanged();
+#endif
+}
+
+void PluginManagerImpl::DeinitializePlugins()
+{
+ if (EditorPlugins.Count() + GamePlugins.Count() == 0)
+ return;
+ PROFILE_CPU_NAMED("DeinitializePlugins");
+
+ auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
+ auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute");
+ auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore");
+ ASSERT(beforeTypeField);
+
+#if USE_EDITOR
+ auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, beforeTypeField);
+ for (int32 i = editorPlugins.Count() - 1; i >= 0 && editorPlugins.Count() > 0; i--)
+ {
+ auto plugin = editorPlugins[i];
+ PluginManagerService::InvokeDeinitialize(plugin);
+ EditorPlugins.Remove(plugin);
+ }
+#endif
+
+ auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField);
+ for (int32 i = gamePlugins.Count() - 1; i >= 0 && gamePlugins.Count() > 0; i--)
+ {
+ auto plugin = gamePlugins[i];
+ PluginManagerService::InvokeDeinitialize(plugin);
+ GamePlugins.Remove(plugin);
+ }
+
+ PluginManager::PluginsChanged();
}
bool PluginManagerService::Init()
{
+ Initialized = false;
+
// Process already loaded modules
for (auto module : BinaryModule::GetModules())
{
OnBinaryModuleLoaded(module);
}
+ // Invoke plugins initialization for all of them
+ InitializePlugins();
+
// Register for new binary modules load actions
+ Initialized = true;
Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded);
Scripting::ScriptsReloading.Bind(&OnScriptsReloading);
@@ -300,28 +395,13 @@ bool PluginManagerService::Init()
void PluginManagerService::Dispose()
{
+ // Unregister from new modules loading
+ Initialized = false;
Scripting::BinaryModuleLoaded.Unbind(&OnBinaryModuleLoaded);
Scripting::ScriptsReloading.Unbind(&OnScriptsReloading);
// Cleanup all plugins
- PROFILE_CPU_NAMED("Dispose Plugins");
- const int32 pluginsCount = EditorPlugins.Count() + GamePlugins.Count();
- if (pluginsCount == 0)
- return;
- LOG(Info, "Unloading {0} plugins", pluginsCount);
- for (int32 i = EditorPlugins.Count() - 1; i >= 0 && EditorPlugins.Count() > 0; i--)
- {
- auto plugin = EditorPlugins[i];
- InvokeDeinitialize(plugin);
- EditorPlugins.RemoveAtKeepOrder(i);
- }
- for (int32 i = GamePlugins.Count() - 1; i >= 0 && GamePlugins.Count() > 0; i--)
- {
- auto plugin = GamePlugins[i];
- InvokeDeinitialize(plugin);
- GamePlugins.RemoveAtKeepOrder(i);
- }
- PluginManager::PluginsChanged();
+ DeinitializePlugins();
}
const Array& PluginManager::GetGamePlugins()
@@ -336,11 +416,13 @@ const Array& PluginManager::GetEditorPlugins()
Plugin* PluginManager::GetPlugin(const StringView& name)
{
+#if USE_EDITOR
for (Plugin* p : EditorPlugins)
{
if (p->GetDescription().Name == name)
return p;
}
+#endif
for (GamePlugin* gp : GamePlugins)
{
if (gp->GetDescription().Name == name)
@@ -352,11 +434,13 @@ Plugin* PluginManager::GetPlugin(const StringView& name)
Plugin* PluginManager::GetPlugin(const MClass* type)
{
CHECK_RETURN(type, nullptr);
+#if USE_EDITOR
for (Plugin* p : EditorPlugins)
{
if (p->GetClass()->IsSubClassOf(type))
return p;
}
+#endif
for (GamePlugin* gp : GamePlugins)
{
if (gp->GetClass()->IsSubClassOf(type))
@@ -368,11 +452,13 @@ Plugin* PluginManager::GetPlugin(const MClass* type)
Plugin* PluginManager::GetPlugin(const ScriptingTypeHandle& type)
{
CHECK_RETURN(type, nullptr);
+#if USE_EDITOR
for (Plugin* p : EditorPlugins)
{
if (p->Is(type))
return p;
}
+#endif
for (GamePlugin* gp : GamePlugins)
{
if (gp->Is(type))
@@ -386,18 +472,32 @@ Plugin* PluginManager::GetPlugin(const ScriptingTypeHandle& type)
void PluginManager::InitializeGamePlugins()
{
PROFILE_CPU();
- for (int32 i = 0; i < GamePlugins.Count(); i++)
+
+ auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
+ auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute");
+ auto afterTypeField = pluginLoadOrderAttribute->GetField("InitializeAfter");
+ ASSERT(afterTypeField);
+
+ auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, afterTypeField);
+ for (int32 i = 0; i < gamePlugins.Count(); i++)
{
- PluginManagerService::InvokeInitialize(GamePlugins[i]);
+ PluginManagerService::InvokeInitialize(gamePlugins[i]);
}
}
void PluginManager::DeinitializeGamePlugins()
{
PROFILE_CPU();
- for (int32 i = GamePlugins.Count() - 1; i >= 0; i--)
+
+ auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
+ auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute");
+ auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore");
+ ASSERT(beforeTypeField);
+
+ auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField);
+ for (int32 i = gamePlugins.Count() - 1; i >= 0; i--)
{
- PluginManagerService::InvokeDeinitialize(GamePlugins[i]);
+ PluginManagerService::InvokeDeinitialize(gamePlugins[i]);
}
}
diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp
index a111ae337..200efa650 100644
--- a/Source/Engine/Scripting/Runtime/DotNet.cpp
+++ b/Source/Engine/Scripting/Runtime/DotNet.cpp
@@ -46,6 +46,7 @@
#include
#include
#include
+#include
#include
typedef char char_t;
#define DOTNET_HOST_MONO_DEBUG 0
@@ -182,30 +183,23 @@ Dictionary CachedAssemblyHandles;
///
void* GetStaticMethodPointer(const String& methodName);
-///
-/// Calls the managed static method in NativeInterop class with given parameters.
-///
-template
-FORCE_INLINE RetType CallStaticMethodByName(const String& methodName, Args... args)
-{
- typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...);
- return ((fun)GetStaticMethodPointer(methodName))(args...);
-}
-
///
/// Calls the managed static method with given parameters.
///
template
FORCE_INLINE RetType CallStaticMethod(void* methodPtr, Args... args)
{
+#if DOTNET_HOST_MONO
+ ASSERT_LOW_LAYER(mono_domain_get()); // Ensure that Mono runtime has been attached to this thread
+#endif
typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...);
return ((fun)methodPtr)(args...);
}
-void RegisterNativeLibrary(const char* moduleName, const char* modulePath)
+void RegisterNativeLibrary(const char* moduleName, const Char* modulePath)
{
static void* RegisterNativeLibraryPtr = GetStaticMethodPointer(TEXT("RegisterNativeLibrary"));
- CallStaticMethod(RegisterNativeLibraryPtr, moduleName, modulePath);
+ CallStaticMethod(RegisterNativeLibraryPtr, moduleName, modulePath);
}
bool InitHostfxr();
@@ -273,7 +267,7 @@ bool MCore::LoadEngine()
return true;
// Prepare managed side
- CallStaticMethodByName(TEXT("Init"));
+ CallStaticMethod(GetStaticMethodPointer(TEXT("Init")));
#ifdef MCORE_MAIN_MODULE_NAME
// MCORE_MAIN_MODULE_NAME define is injected by Scripting.Build.cs on platforms that use separate shared library for engine symbols
::String flaxLibraryPath(Platform::GetMainDirectory() / TEXT(MACRO_TO_STR(MCORE_MAIN_MODULE_NAME)));
@@ -287,12 +281,13 @@ bool MCore::LoadEngine()
flaxLibraryPath = ::String(StringUtils::GetDirectoryName(Platform::GetExecutableFilePath())) / StringUtils::GetFileName(flaxLibraryPath);
}
#endif
- RegisterNativeLibrary("FlaxEngine", StringAnsi(flaxLibraryPath).Get());
+ RegisterNativeLibrary("FlaxEngine", flaxLibraryPath.Get());
MRootDomain = New("Root");
MDomains.Add(MRootDomain);
- char* buildInfo = CallStaticMethodByName(TEXT("GetRuntimeInformation"));
+ void* GetRuntimeInformationPtr = GetStaticMethodPointer(TEXT("GetRuntimeInformation"));
+ char* buildInfo = CallStaticMethod(GetRuntimeInformationPtr);
LOG(Info, ".NET runtime version: {0}", ::String(buildInfo));
MCore::GC::FreeMemory(buildInfo);
@@ -304,7 +299,7 @@ void MCore::UnloadEngine()
if (!MRootDomain)
return;
PROFILE_CPU();
- CallStaticMethodByName(TEXT("Exit"));
+ CallStaticMethod(GetStaticMethodPointer(TEXT("Exit")));
MDomains.ClearDelete();
MRootDomain = nullptr;
ShutdownHostfxr();
@@ -730,7 +725,6 @@ DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* man
StringAnsi assemblyName;
StringAnsi assemblyFullName;
GetAssemblyName(assemblyHandle, assemblyName, assemblyFullName);
-
assembly = New(nullptr, assemblyName, assemblyFullName, assemblyHandle);
CachedAssemblyHandles.Add(assemblyHandle, assembly);
}
@@ -797,13 +791,13 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa
if (nativePath.HasChars())
{
StringAnsi nativeName = _name.EndsWith(".CSharp") ? StringAnsi(_name.Get(), _name.Length() - 7) : StringAnsi(_name);
- RegisterNativeLibrary(nativeName.Get(), StringAnsi(nativePath).Get());
+ RegisterNativeLibrary(nativeName.Get(), nativePath.Get());
}
#if USE_EDITOR
// Register the editor module location for Assembly resolver
else
{
- RegisterNativeLibrary(_name.Get(), StringAnsi(assemblyPath).Get());
+ RegisterNativeLibrary(_name.Get(), assemblyPath.Get());
}
#endif
@@ -1223,9 +1217,9 @@ MException::~MException()
MField::MField(MClass* parentClass, void* handle, const char* name, void* type, int fieldOffset, MFieldAttributes attributes)
: _handle(handle)
, _type(type)
+ , _fieldOffset(fieldOffset)
, _parentClass(parentClass)
, _name(name)
- , _fieldOffset(fieldOffset)
, _hasCachedAttributes(false)
{
switch (attributes & MFieldAttributes::FieldAccessMask)
@@ -1366,19 +1360,22 @@ MMethod::MMethod(MClass* parentClass, StringAnsi&& name, void* handle, int32 par
void MMethod::CacheSignature() const
{
- _hasCachedSignature = true;
+ ScopeLock lock(BinaryModule::Locker);
+ if (_hasCachedSignature)
+ return;
static void* GetMethodReturnTypePtr = GetStaticMethodPointer(TEXT("GetMethodReturnType"));
static void* GetMethodParameterTypesPtr = GetStaticMethodPointer(TEXT("GetMethodParameterTypes"));
-
_returnType = CallStaticMethod(GetMethodReturnTypePtr, _handle);
+ if (_paramsCount != 0)
+ {
+ void** parameterTypeHandles;
+ CallStaticMethod(GetMethodParameterTypesPtr, _handle, ¶meterTypeHandles);
+ _parameterTypes.Set(parameterTypeHandles, _paramsCount);
+ MCore::GC::FreeMemory(parameterTypeHandles);
+ }
- if (_paramsCount == 0)
- return;
- void** parameterTypeHandles;
- CallStaticMethod(GetMethodParameterTypesPtr, _handle, ¶meterTypeHandles);
- _parameterTypes.Set(parameterTypeHandles, _paramsCount);
- MCore::GC::FreeMemory(parameterTypeHandles);
+ _hasCachedSignature = true;
}
MObject* MMethod::Invoke(void* instance, void** params, MObject** exception) const
@@ -1434,7 +1431,7 @@ MType* MMethod::GetParameterType(int32 paramIdx) const
if (!_hasCachedSignature)
CacheSignature();
ASSERT_LOW_LAYER(paramIdx >= 0 && paramIdx < _paramsCount);
- return (MType*)_parameterTypes[paramIdx];
+ return (MType*)_parameterTypes.Get()[paramIdx];
}
bool MMethod::GetParameterIsOut(int32 paramIdx) const
@@ -1789,6 +1786,7 @@ void* GetStaticMethodPointer(const String& methodName)
void* fun;
if (CachedFunctions.TryGet(methodName, fun))
return fun;
+ PROFILE_CPU();
const int rc = get_function_pointer(NativeInteropTypeName, FLAX_CORECLR_STRING(methodName).Get(), UNMANAGEDCALLERSONLY_METHOD, nullptr, nullptr, &fun);
if (rc != 0)
LOG(Fatal, "Failed to get unmanaged function pointer for method {0}: 0x{1:x}", methodName.Get(), (unsigned int)rc);
@@ -2010,6 +2008,9 @@ bool InitHostfxr()
//Platform::SetEnvironmentVariable(TEXT("MONO_GC_DEBUG"), TEXT("6:gc-log.txt,check-remset-consistency,nursery-canaries"));
#endif
+ // Adjust GC threads suspending mode to not block attached native threads (eg. Job System)
+ Platform::SetEnvironmentVariable(TEXT("MONO_THREADS_SUSPEND"), TEXT("preemptive"));
+
#if defined(USE_MONO_AOT_MODE)
// Enable AOT mode (per-platform)
mono_jit_set_aot_mode(USE_MONO_AOT_MODE);
@@ -2054,7 +2055,7 @@ bool InitHostfxr()
// Setup debugger
{
int32 debuggerLogLevel = 0;
- if (CommandLine::Options.MonoLog.IsTrue())
+ if (CommandLine::Options.MonoLog.IsTrue() || DOTNET_HOST_MONO_DEBUG)
{
LOG(Info, "Using detailed Mono logging");
mono_trace_set_level_string("debug");
@@ -2137,6 +2138,7 @@ bool InitHostfxr()
LOG(Fatal, "Failed to initialize Mono.");
return true;
}
+ mono_gc_init_finalizer_thread();
// Log info
char* buildInfo = mono_get_runtime_build_info();
@@ -2161,6 +2163,7 @@ void* GetStaticMethodPointer(const String& methodName)
void* fun;
if (CachedFunctions.TryGet(methodName, fun))
return fun;
+ PROFILE_CPU();
static MonoClass* nativeInteropClass = nullptr;
if (!nativeInteropClass)
diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp
index e1494cd04..bdf9f60f5 100644
--- a/Source/Engine/Scripting/Scripting.cpp
+++ b/Source/Engine/Scripting/Scripting.cpp
@@ -28,8 +28,8 @@
#include "Engine/Content/Content.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Engine/Globals.h"
+#include "Engine/Engine/Time.h"
#include "Engine/Graphics/RenderTask.h"
-#include "Engine/Platform/MemoryStats.h"
#include "Engine/Serialization/JsonTools.h"
extern void registerFlaxEngineInternalCalls();
@@ -193,6 +193,14 @@ void ScriptingService::Update()
{
PROFILE_CPU_NAMED("Scripting::Update");
INVOKE_EVENT(Update);
+
+#ifdef USE_NETCORE
+ // Force GC to run in background periodically to avoid large blocking collections causing hitches
+ if (Time::Update.TicksCount % 60 == 0)
+ {
+ MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false);
+ }
+#endif
}
void ScriptingService::LateUpdate()
@@ -474,30 +482,32 @@ bool Scripting::Load()
// Load FlaxEngine
const String flaxEnginePath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.dll");
auto* flaxEngineModule = (NativeBinaryModule*)GetBinaryModuleFlaxEngine();
- if (flaxEngineModule->Assembly->Load(flaxEnginePath))
+ if (!flaxEngineModule->Assembly->IsLoaded())
{
- LOG(Error, "Failed to load FlaxEngine C# assembly.");
- return true;
- }
+ if (flaxEngineModule->Assembly->Load(flaxEnginePath))
+ {
+ LOG(Error, "Failed to load FlaxEngine C# assembly.");
+ return true;
+ }
+ onEngineLoaded(flaxEngineModule->Assembly);
- onEngineLoaded(flaxEngineModule->Assembly);
-
- // Insert type aliases for vector types that don't exist in C++ but are just typedef (properly redirect them to actual types)
- // TODO: add support for automatic typedef aliases setup for scripting module to properly lookup type from the alias typename
+ // Insert type aliases for vector types that don't exist in C++ but are just typedef (properly redirect them to actual types)
+ // TODO: add support for automatic typedef aliases setup for scripting module to properly lookup type from the alias typename
#if USE_LARGE_WORLDS
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double2"];
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double3"];
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double4"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double2"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double3"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Double4"];
#else
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float2"];
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float3"];
- flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float4"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float2"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float3"];
+ flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Float4"];
#endif
#if USE_CSHARP
- flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector2")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"];
- flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector3")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"];
- flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector4")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"];
+ flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector2")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector2"];
+ flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector3")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector3"];
+ flaxEngineModule->ClassToTypeIndex[flaxEngineModule->Assembly->GetClass("FlaxEngine.Vector4")] = flaxEngineModule->TypeNameToTypeIndex["FlaxEngine.Vector4"];
#endif
+ }
#if USE_EDITOR
// Skip loading game modules in Editor on startup - Editor loads them later during splash screen (eg. after first compilation)
diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs
index 8347fe7f8..0630985f3 100644
--- a/Source/Engine/Scripting/Scripting.cs
+++ b/Source/Engine/Scripting/Scripting.cs
@@ -135,13 +135,10 @@ namespace FlaxEngine
{
if (e.ExceptionObject is Exception exception)
{
+ Debug.LogError($"Unhandled Exception: {exception.Message}");
+ Debug.LogException(exception);
if (e.IsTerminating && !System.Diagnostics.Debugger.IsAttached)
Platform.Fatal($"Unhandled Exception: {exception}");
- else
- {
- Debug.LogError($"Unhandled Exception: {exception.Message}");
- Debug.LogException(exception);
- }
}
}
@@ -278,19 +275,30 @@ namespace FlaxEngine
BackgroundNormal = Color.FromBgra(0xFF3F3F46),
BorderNormal = Color.FromBgra(0xFF54545C),
TextBoxBackground = Color.FromBgra(0xFF333337),
- ProgressNormal = Color.FromBgra(0xFF0ad328),
TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46),
CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC),
- SharedTooltip = new Tooltip(),
- Statusbar = new Style.StatusbarStyle()
+ ProgressNormal = Color.FromBgra(0xFF0ad328),
+ Statusbar = new Style.StatusbarStyle
{
PlayMode = Color.FromBgra(0xFF2F9135),
Failed = Color.FromBgra(0xFF9C2424),
- Loading = Color.FromBgra(0xFF2D2D30)
- }
+ Loading = Color.FromBgra(0xFF2D2D30),
+ },
+
+ SharedTooltip = new Tooltip(),
};
style.DragWindow = style.BackgroundSelected * 0.7f;
+ // Use optionally bundled default font (matches Editor)
+ var defaultFont = Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular");
+ if (defaultFont)
+ {
+ style.FontTitle = defaultFont.CreateFont(18);
+ style.FontLarge = defaultFont.CreateFont(14);
+ style.FontMedium = defaultFont.CreateFont(9);
+ style.FontSmall = defaultFont.CreateFont(9);
+ }
+
Style.Current = style;
}
diff --git a/Source/Engine/Scripting/ScriptingObjectReference.h b/Source/Engine/Scripting/ScriptingObjectReference.h
index 0012c89ba..0945d6fc2 100644
--- a/Source/Engine/Scripting/ScriptingObjectReference.h
+++ b/Source/Engine/Scripting/ScriptingObjectReference.h
@@ -47,8 +47,12 @@ public:
///
~ScriptingObjectReferenceBase()
{
- if (_object)
- _object->Deleted.Unbind(this);
+ ScriptingObject* obj = _object;
+ if (obj)
+ {
+ _object = nullptr;
+ obj->Deleted.Unbind(this);
+ }
}
public:
diff --git a/Source/Engine/Scripting/SoftObjectReference.h b/Source/Engine/Scripting/SoftObjectReference.h
index 3366dbbc7..174cf4188 100644
--- a/Source/Engine/Scripting/SoftObjectReference.h
+++ b/Source/Engine/Scripting/SoftObjectReference.h
@@ -38,8 +38,12 @@ public:
///
~SoftObjectReferenceBase()
{
- if (_object)
- _object->Deleted.Unbind(this);
+ ScriptingObject* obj = _object;
+ if (obj)
+ {
+ _object = nullptr;
+ obj->Deleted.Unbind(this);
+ }
}
public:
diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h
index 94acd1d79..f9dc0870a 100644
--- a/Source/Engine/Serialization/Serialization.h
+++ b/Source/Engine/Serialization/Serialization.h
@@ -405,14 +405,14 @@ namespace Serialization
// ISerializable
- inline bool ShouldSerialize(ISerializable& v, const void* otherObj)
+ inline bool ShouldSerialize(const ISerializable& v, const void* otherObj)
{
return true;
}
- inline void Serialize(ISerializable::SerializeStream& stream, ISerializable& v, const void* otherObj)
+ inline void Serialize(ISerializable::SerializeStream& stream, const ISerializable& v, const void* otherObj)
{
stream.StartObject();
- v.Serialize(stream, otherObj);
+ const_cast(&v)->Serialize(stream, otherObj);
stream.EndObject();
}
inline void Deserialize(ISerializable::DeserializeStream& stream, ISerializable& v, ISerializeModifier* modifier)
@@ -421,15 +421,15 @@ namespace Serialization
}
template
- inline typename TEnableIf::Value, bool>::Type ShouldSerialize(ISerializable& v, const void* otherObj)
+ inline typename TEnableIf::Value, bool>::Type ShouldSerialize(const ISerializable& v, const void* otherObj)
{
return true;
}
template
- inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, ISerializable& v, const void* otherObj)
+ inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, const ISerializable& v, const void* otherObj)
{
stream.StartObject();
- v.Serialize(stream, otherObj);
+ const_cast(&v)->Serialize(stream, otherObj);
stream.EndObject();
}
template
@@ -441,12 +441,12 @@ namespace Serialization
// Scripting Object
template
- inline typename TEnableIf::Value, bool>::Type ShouldSerialize(T*& v, const void* otherObj)
+ inline typename TEnableIf::Value, bool>::Type ShouldSerialize(const T*& v, const void* otherObj)
{
return !otherObj || v != *(T**)otherObj;
}
template
- inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, T*& v, const void* otherObj)
+ inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T*& v, const void* otherObj)
{
stream.Guid(v ? v->GetID() : Guid::Empty);
}
@@ -568,12 +568,13 @@ namespace Serialization
{
if (!otherObj)
return true;
- const auto other = (Array*)otherObj;
+ const auto other = (const Array*)otherObj;
if (v.Count() != other->Count())
return true;
+ const T* vPtr = v.Get();
for (int32 i = 0; i < v.Count(); i++)
{
- if (ShouldSerialize((T&)v[i], (const void*)&other->At(i)))
+ if (ShouldSerialize(vPtr[i], (const void*)&other->At(i)))
return true;
}
return false;
@@ -582,8 +583,9 @@ namespace Serialization
inline void Serialize(ISerializable::SerializeStream& stream, const Array& v, const void* otherObj)
{
stream.StartArray();
+ const T* vPtr = v.Get();
for (int32 i = 0; i < v.Count(); i++)
- Serialize(stream, (T&)v[i], nullptr);
+ Serialize(stream, vPtr[i], nullptr);
stream.EndArray();
}
template
@@ -593,8 +595,9 @@ namespace Serialization
{
const auto& streamArray = stream.GetArray();
v.Resize(streamArray.Size());
+ T* vPtr = v.Get();
for (int32 i = 0; i < v.Count(); i++)
- Deserialize(streamArray[i], v[i], modifier);
+ Deserialize(streamArray[i], vPtr[i], modifier);
}
else if (TIsPODType::Value && stream.IsString())
{
diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp
index 291910b11..da67ef3b4 100644
--- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp
+++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp
@@ -3,6 +3,7 @@
#if COMPILE_WITH_SHADER_COMPILER
#include "ShaderCompiler.h"
+#include "ShadersCompilation.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Engine/Globals.h"
@@ -14,10 +15,6 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Utilities/StringConverter.h"
-#if USE_EDITOR
-#include "Editor/Editor.h"
-#include "Editor/ProjectInfo.h"
-#endif
namespace IncludedFiles
{
@@ -30,31 +27,6 @@ namespace IncludedFiles
CriticalSection Locker;
Dictionary Files;
-
-#if USE_EDITOR
- bool FindProject(const ProjectInfo* project, HashSet& projects, const StringView& projectName, String& path)
- {
- if (!project || projects.Contains(project))
- return false;
- projects.Add(project);
-
- // Check the project name
- if (project->Name == projectName)
- {
- path = project->ProjectFolderPath;
- return true;
- }
-
- // Initialize referenced projects
- for (const auto& reference : project->References)
- {
- if (reference.Project && FindProject(reference.Project, projects, projectName, path))
- return true;
- }
-
- return false;
- }
-#endif
}
bool ShaderCompiler::Compile(ShaderCompilationContext* context)
@@ -124,7 +96,8 @@ bool ShaderCompiler::Compile(ShaderCompilationContext* context)
output->WriteInt32(context->Includes.Count());
for (auto& include : context->Includes)
{
- output->WriteString(include.Item, 11);
+ String compactPath = ShadersCompilation::CompactShaderPath(include.Item);
+ output->WriteString(compactPath, 11);
const auto date = FileSystem::GetFileLastEditTime(include.Item);
output->Write(date);
}
@@ -138,75 +111,17 @@ bool ShaderCompiler::GetIncludedFileSource(ShaderCompilationContext* context, co
source = nullptr;
sourceLength = 0;
- // Skip to the last root start './' but preserve the leading one
- const int32 includedFileLength = StringUtils::Length(includedFile);
- for (int32 i = includedFileLength - 2; i >= 2; i--)
+ // Get actual file path
+ const String includedFileName(includedFile);
+ String path = ShadersCompilation::ResolveShaderPath(includedFileName);
+ if (!FileSystem::FileExists(path))
{
- if (StringUtils::Compare(includedFile + i, "./", 2) == 0)
- {
- includedFile = includedFile + i;
- break;
- }
+ LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", includedFileName, String(sourceFile), String::Empty);
+ return true;
}
ScopeLock lock(IncludedFiles::Locker);
- // Find the included file path
- String path;
-#if USE_EDITOR
- if (StringUtils::Compare(includedFile, "./", 2) == 0)
- {
- int32 projectNameEnd = -1;
- for (int32 i = 2; i < includedFileLength; i++)
- {
- if (includedFile[i] == '/')
- {
- projectNameEnd = i;
- break;
- }
- }
- if (projectNameEnd == -1)
- {
- LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", String(includedFile), String(sourceFile), TEXT("Missing project name after root path."));
- return true;
- }
- const StringAsUTF16<120> projectName(includedFile + 2, projectNameEnd - 2);
- if (StringUtils::Compare(projectName.Get(), TEXT("FlaxPlatforms")) == 0)
- {
- // Hard-coded redirect to platform-specific includes
- path /= Globals::StartupFolder / TEXT("Source/Platforms");
- }
- else
- {
- HashSet projects;
- if (!IncludedFiles::FindProject(Editor::Project, projects, StringView(projectName.Get(), projectNameEnd - 2), path))
- {
- LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", String(includedFile), String(sourceFile), TEXT("Failed to find the project of the given name."));
- return true;
- }
- path /= TEXT("Source/Shaders/");
- }
- const StringAsUTF16<250> localPath(includedFile + projectNameEnd + 1, includedFileLength - projectNameEnd - 1);
- path /= localPath.Get();
- }
-#else
- if (StringUtils::Compare(includedFile, "./Flax/", 7) == 0)
- {
- // Engine project relative shader path
- const auto includedFileStr = String(includedFile + 6);
- path = Globals::StartupFolder / TEXT("Source/Shaders") / includedFileStr;
- }
-#endif
- else if (FileSystem::FileExists(path = String(includedFile)))
- {
- // Absolute shader path
- }
- else
- {
- LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", String(includedFile), String(sourceFile), String::Empty);
- return true;
- }
-
// Try to reuse file
IncludedFiles::File* result = nullptr;
if (!IncludedFiles::Files.TryGet(path, result) || FileSystem::GetFileLastEditTime(path) > result->LastEditTime)
@@ -460,16 +375,15 @@ bool ShaderCompiler::WriteCustomDataVS(ShaderCompilationContext* context, Shader
auto& element = layout[a];
if (!layoutVisible[a])
continue;
-
- // TODO: serialize whole struct?
-
- output->WriteByte(static_cast(element.Type));
- output->WriteByte(element.Index);
- output->WriteByte(static_cast(element.Format));
- output->WriteByte(element.InputSlot);
- output->WriteUint32(element.AlignedByteOffset);
- output->WriteByte(element.InputSlotClass);
- output->WriteUint32(element.InstanceDataStepRate);
+ GPUShaderProgramVS::InputElement data;
+ data.Type = static_cast(element.Type);
+ data.Index = element.Index;
+ data.Format = static_cast(element.Format);
+ data.InputSlot = element.InputSlot;
+ data.AlignedByteOffset = element.AlignedByteOffset;
+ data.InputSlotClass = element.InputSlotClass;
+ data.InstanceDataStepRate = element.InstanceDataStepRate;
+ output->Write(data);
}
return false;
diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp
index ce4116134..793a3c1c3 100644
--- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp
+++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp
@@ -28,10 +28,11 @@
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Platform/FileSystemWatcher.h"
#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/File.h"
+#include "Engine/Engine/Globals.h"
#include "Editor/Editor.h"
#include "Editor/ProjectInfo.h"
#endif
-
#if COMPILE_WITH_D3D_SHADER_COMPILER
#include "DirectX/ShaderCompilerD3D.h"
#endif
@@ -53,6 +54,49 @@ namespace ShadersCompilationImpl
CriticalSection Locker;
Array Compilers;
Array ReadyCompilers;
+
+#if USE_EDITOR
+ const ProjectInfo* FindProjectByName(const ProjectInfo* project, HashSet& projects, const StringView& projectName)
+ {
+ if (!project || projects.Contains(project))
+ return nullptr;
+ projects.Add(project);
+
+ // Check the project name
+ if (project->Name == projectName)
+ return project;
+
+ // Search referenced projects
+ for (const auto& reference : project->References)
+ {
+ const ProjectInfo* result = FindProjectByName(reference.Project, projects, projectName);
+ if (result)
+ return result;
+ }
+ return nullptr;
+ }
+
+ const ProjectInfo* FindProjectByPath(const ProjectInfo* project, HashSet& projects, const StringView& projectPath)
+ {
+ if (!project || projects.Contains(project))
+ return nullptr;
+ projects.Add(project);
+
+ // Search referenced projects (depth first to handle plugin projects first)
+ for (const auto& reference : project->References)
+ {
+ const ProjectInfo* result = FindProjectByPath(reference.Project, projects, projectPath);
+ if (result)
+ return result;
+ }
+
+ // Check the project path
+ if (projectPath.StartsWith(project->ProjectFolderPath))
+ return project;
+
+ return nullptr;
+ }
+#endif
}
using namespace ShadersCompilationImpl;
@@ -143,9 +187,22 @@ bool ShadersCompilation::Compile(ShaderCompilationOptions& options)
#endif
}
- // Print info if succeed
- if (result == false)
+ if (result)
{
+#if USE_EDITOR
+ // Output shader source to easily investigate errors (eg. for generated shaders like materials or particles)
+ const String outputSourceFolder = Globals::ProjectCacheFolder / TEXT("/Shaders/Source");
+ const String outputSourcePath = outputSourceFolder / options.TargetName + TEXT(".hlsl");
+ if (!FileSystem::DirectoryExists(outputSourceFolder))
+ FileSystem::CreateDirectory(outputSourceFolder);
+ File::WriteAllBytes(outputSourcePath, (const byte*)options.Source, options.SourceLength);
+ LOG(Error, "Shader compilation '{0}' failed (profile: {1})", options.TargetName, ::ToString(options.Profile));
+ LOG(Error, "Source: {0}", outputSourcePath);
+#endif
+ }
+ else
+ {
+ // Success
const DateTime endTime = DateTime::NowUTC();
LOG(Info, "Shader compilation '{0}' succeed in {1} ms (profile: {2})", options.TargetName, Math::CeilToInt(static_cast((endTime - startTime).GetTotalMilliseconds())), ::ToString(options.Profile));
}
@@ -346,11 +403,89 @@ void ShadersCompilation::ExtractShaderIncludes(byte* shaderCache, int32 shaderCa
{
String& include = includes.AddOne();
stream.ReadString(&include, 11);
+ include = ShadersCompilation::ResolveShaderPath(include);
DateTime lastEditTime;
stream.Read(lastEditTime);
}
}
+String ShadersCompilation::ResolveShaderPath(StringView path)
+{
+ // Skip to the last root start './' but preserve the leading one
+ for (int32 i = path.Length() - 2; i >= 2; i--)
+ {
+ if (StringUtils::Compare(path.Get() + i, TEXT("./"), 2) == 0)
+ {
+ path = path.Substring(i);
+ break;
+ }
+ }
+
+ // Find the included file path
+ String result;
+#if USE_EDITOR
+ if (path.StartsWith(StringView(TEXT("./"), 2)))
+ {
+ int32 projectNameEnd = -1;
+ for (int32 i = 2; i < path.Length(); i++)
+ {
+ if (path[i] == '/')
+ {
+ projectNameEnd = i;
+ break;
+ }
+ }
+ if (projectNameEnd == -1)
+ return String::Empty; // Invalid project path
+ StringView projectName = path.Substring(2, projectNameEnd - 2);
+ if (projectName.StartsWith(StringView(TEXT("FlaxPlatforms"))))
+ {
+ // Hard-coded redirect to platform-specific includes
+ result = Globals::StartupFolder / TEXT("Source/Platforms");
+ }
+ else
+ {
+ HashSet projects;
+ const ProjectInfo* project = FindProjectByName(Editor::Project, projects, StringView(projectName.Get(), projectNameEnd - 2));
+ if (project)
+ result = project->ProjectFolderPath / TEXT("/Source/Shaders/");
+ else
+ return String::Empty;
+ }
+ result /= path.Substring(projectNameEnd + 1);
+ }
+#else
+ if (path.StartsWith(StringView(TEXT("./Flax/"), 7)))
+ {
+ // Engine project relative shader path
+ result = Globals::StartupFolder / TEXT("Source/Shaders") / path.Substring(6);
+ }
+#endif
+ else
+ {
+ // Absolute shader path
+ result = path;
+ }
+
+ return result;
+}
+
+String ShadersCompilation::CompactShaderPath(StringView path)
+{
+#if USE_EDITOR
+ // Try to use file path relative to the project shader sources folder
+ HashSet projects;
+ const ProjectInfo* project = FindProjectByPath(Editor::Project, projects, path);
+ if (project)
+ {
+ String projectSourcesPath = project->ProjectFolderPath / TEXT("/Source/Shaders/");
+ if (path.StartsWith(projectSourcesPath))
+ return String::Format(TEXT("./{}/{}"), project->Name, path.Substring(projectSourcesPath.Length()));
+ }
+#endif
+ return String(path);
+}
+
#if USE_EDITOR
namespace
diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.h b/Source/Engine/ShadersCompilation/ShadersCompilation.h
index fca6a6ebe..fd907acf7 100644
--- a/Source/Engine/ShadersCompilation/ShadersCompilation.h
+++ b/Source/Engine/ShadersCompilation/ShadersCompilation.h
@@ -14,7 +14,6 @@ class Asset;
class FLAXENGINE_API ShadersCompilation
{
public:
-
///
/// Compiles the shader.
///
@@ -43,6 +42,11 @@ public:
/// The output included.
static void ExtractShaderIncludes(byte* shaderCache, int32 shaderCacheLength, Array& includes);
+ // Resolves shader path name into absolute file path. Resolves './/ShaderFile.hlsl' cases into a full path.
+ static String ResolveShaderPath(StringView path);
+ // Compacts the full shader file path into portable format with project name prefix such as './/ShaderFile.hlsl'.
+ static String CompactShaderPath(StringView path);
+
private:
static ShaderCompiler* CreateCompiler(ShaderProfile profile);
diff --git a/Source/Engine/Tests/TestCollections.cpp b/Source/Engine/Tests/TestCollections.cpp
index 21aeb1c36..ff5f34bf6 100644
--- a/Source/Engine/Tests/TestCollections.cpp
+++ b/Source/Engine/Tests/TestCollections.cpp
@@ -3,6 +3,8 @@
#include "Engine/Core/RandomStream.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/BitArray.h"
+#include "Engine/Core/Collections/HashSet.h"
+#include "Engine/Core/Collections/Dictionary.h"
#include
TEST_CASE("Array")
@@ -107,4 +109,185 @@ TEST_CASE("BitArray")
a1 = testData;
CHECK(a1 == testData);
}
+
+ SECTION("Test Set All")
+ {
+ BitArray<> a1;
+ a1.Resize(9);
+ CHECK(a1.Count() == 9);
+ a1.SetAll(true);
+ for (int32 i = 0; i < a1.Count(); i++)
+ CHECK(a1[i] == true);
+ a1.SetAll(false);
+ for (int32 i = 0; i < a1.Count(); i++)
+ CHECK(a1[i] == false);
+ }
+}
+
+TEST_CASE("HashSet")
+{
+ SECTION("Test Allocators")
+ {
+ HashSet a1;
+ HashSet> a2;
+ HashSet> a3;
+ for (int32 i = 0; i < 7; i++)
+ {
+ a1.Add(i);
+ a2.Add(i);
+ a3.Add(i);
+ }
+ CHECK(a1.Count() == 7);
+ CHECK(a2.Count() == 7);
+ CHECK(a3.Count() == 7);
+ for (int32 i = 0; i < 7; i++)
+ {
+ CHECK(a1.Contains(i));
+ CHECK(a2.Contains(i));
+ CHECK(a3.Contains(i));
+ }
+ }
+
+ SECTION("Test Resizing")
+ {
+ HashSet a1;
+ for (int32 i = 0; i < 4000; i++)
+ a1.Add(i);
+ CHECK(a1.Count() == 4000);
+ int32 capacity = a1.Capacity();
+ for (int32 i = 0; i < 4000; i++)
+ {
+ CHECK(a1.Contains(i));
+ }
+ a1.Clear();
+ CHECK(a1.Count() == 0);
+ CHECK(a1.Capacity() == capacity);
+ for (int32 i = 0; i < 4000; i++)
+ a1.Add(i);
+ CHECK(a1.Count() == 4000);
+ CHECK(a1.Capacity() == capacity);
+ for (int32 i = 0; i < 4000; i++)
+ a1.Remove(i);
+ CHECK(a1.Count() == 0);
+ CHECK(a1.Capacity() == capacity);
+ for (int32 i = 0; i < 4000; i++)
+ a1.Add(i);
+ CHECK(a1.Count() == 4000);
+ CHECK(a1.Capacity() == capacity);
+ }
+
+ SECTION("Test Default Capacity")
+ {
+ HashSet a1;
+ a1.Add(1);
+ CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY);
+ }
+
+ SECTION("Test Add/Remove")
+ {
+ HashSet a1;
+ for (int32 i = 0; i < 4000; i++)
+ {
+ a1.Add(i);
+ a1.Remove(i);
+ }
+ CHECK(a1.Count() == 0);
+ CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY);
+ a1.Clear();
+ for (int32 i = 1; i <= 10; i++)
+ a1.Add(-i);
+ for (int32 i = 0; i < 4000; i++)
+ {
+ a1.Add(i);
+ a1.Remove(i);
+ }
+ CHECK(a1.Count() == 10);
+ CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY);
+ }
+}
+
+TEST_CASE("Dictionary")
+{
+ SECTION("Test Allocators")
+ {
+ Dictionary a1;
+ Dictionary> a2;
+ Dictionary> a3;
+ for (int32 i = 0; i < 7; i++)
+ {
+ a1.Add(i, i);
+ a2.Add(i, i);
+ a3.Add(i, i);
+ }
+ CHECK(a1.Count() == 7);
+ CHECK(a2.Count() == 7);
+ CHECK(a3.Count() == 7);
+ for (int32 i = 0; i < 7; i++)
+ {
+ CHECK(a1.ContainsKey(i));
+ CHECK(a2.ContainsKey(i));
+ CHECK(a3.ContainsKey(i));
+ CHECK(a1.ContainsValue(i));
+ CHECK(a2.ContainsValue(i));
+ CHECK(a3.ContainsValue(i));
+ }
+ }
+
+ SECTION("Test Resizing")
+ {
+ Dictionary a1;
+ for (int32 i = 0; i < 4000; i++)
+ a1.Add(i, i);
+ CHECK(a1.Count() == 4000);
+ int32 capacity = a1.Capacity();
+ for (int32 i = 0; i < 4000; i++)
+ {
+ CHECK(a1.ContainsKey(i));
+ CHECK(a1.ContainsValue(i));
+ }
+ a1.Clear();
+ CHECK(a1.Count() == 0);
+ CHECK(a1.Capacity() == capacity);
+ for (int32 i = 0; i < 4000; i++)
+ a1.Add(i, i);
+ CHECK(a1.Count() == 4000);
+ CHECK(a1.Capacity() == capacity);
+ for (int32 i = 0; i < 4000; i++)
+ a1.Remove(i);
+ CHECK(a1.Count() == 0);
+ CHECK(a1.Capacity() == capacity);
+ for (int32 i = 0; i < 4000; i++)
+ a1.Add(i, i);
+ CHECK(a1.Count() == 4000);
+ CHECK(a1.Capacity() == capacity);
+ }
+
+ SECTION("Test Default Capacity")
+ {
+ Dictionary a1;
+ a1.Add(1, 1);
+ CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY);
+ }
+
+ SECTION("Test Add/Remove")
+ {
+ Dictionary a1;
+ for (int32 i = 0; i < 4000; i++)
+ {
+ a1.Add(i, i);
+ a1.Remove(i);
+ }
+ CHECK(a1.Count() == 0);
+ CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY);
+ a1.Clear();
+ for (int32 i = 1; i <= 10; i++)
+ a1.Add(-i, -i);
+ for (int32 i = 0; i < 4000; i++)
+ {
+ a1.Add(i, i);
+ a1.Remove(i);
+ }
+ CHECK(a1.Count() == 10);
+ CHECK(a1.Capacity() <= DICTIONARY_DEFAULT_CAPACITY);
+ }
}
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp
index 8db8a0d0d..fe45b8b96 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp
@@ -147,7 +147,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value)
case 8:
{
const Value defaultValue = MaterialValue::InitForZero(VariantType::Void);
- const Value alpha = tryGetValue(node->GetBox(2), 0, Value::Zero);
+ Value alpha = tryGetValue(node->GetBox(2), 0, Value::Zero).AsFloat();
if (alpha.IsZero())
{
// Bottom-only
@@ -178,6 +178,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value)
auto topHeightScaled = writeLocal(VariantType::Float, String::Format(TEXT("{0} * {1}"), topHeight.Value, alpha.Value), node);
auto heightStart = writeLocal(VariantType::Float, String::Format(TEXT("max({0}, {1}) - 0.05"), bottomHeightScaled.Value, topHeightScaled.Value), node);
auto bottomLevel = writeLocal(VariantType::Float, String::Format(TEXT("max({0} - {1}, 0.0001)"), topHeightScaled.Value, heightStart.Value), node);
+ alpha = writeLocal(VariantType::Float, alpha.Value, node);
_writer.Write(TEXT("\t{0} = {1} / (max({2} - {3}, 0) + {4});\n"), alpha.Value, bottomLevel.Value, bottomHeightScaled.Value, heightStart.Value, bottomLevel.Value);
}
#define EAT_BOX(type) writeBlending(MaterialGraphBoxes::type, value, bottom, top, alpha)
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
index 8aa300731..f669d36d2 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
@@ -393,8 +393,8 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
// Sphere Mask
case 28:
{
- const auto a = tryGetValue(node->GetBox(0), 0, Value::Zero);
- const auto b = tryGetValue(node->GetBox(1), 1, Value::Zero).Cast(a.Type);
+ const auto a = tryGetValue(node->GetBox(0), getUVs);
+ const auto b = tryGetValue(node->GetBox(1), Value::Half).Cast(a.Type);
const auto radius = tryGetValue(node->GetBox(2), node->Values[0]).AsFloat();
const auto hardness = tryGetValue(node->GetBox(3), node->Values[1]).AsFloat();
const auto invert = tryGetValue(node->GetBox(4), node->Values[2]).AsBool();
@@ -519,6 +519,40 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
}
break;
}
+ // Rectangle Mask
+ case 40:
+ {
+ const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
+ const auto rectangle = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat2();
+ auto d = writeLocal(ValueType::Float2, String::Format(TEXT("abs({0} * 2 - 1) - {1}"), uv.Value, rectangle.Value), node);
+ auto d2 = writeLocal(ValueType::Float2, String::Format(TEXT("1 - {0} / fwidth({0})"), d.Value), node);
+ value = writeLocal(ValueType::Float, String::Format(TEXT("saturate(min({0}.x, {0}.y))"), d2.Value), node);
+ break;
+ }
+ // FWidth
+ case 41:
+ {
+ const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero);
+ value = writeLocal(inValue.Type, String::Format(TEXT("fwidth({0})"), inValue.Value), node);
+ break;
+ }
+ // AA Step
+ case 42:
+ {
+ // Reference: https://www.ronja-tutorials.com/post/046-fwidth/#a-better-step
+
+ const auto compValue = tryGetValue(node->GetBox(0), getUVs).AsFloat();
+ const auto gradient = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat();
+
+ auto change = writeLocal(ValueType::Float, String::Format(TEXT("fwidth({0})"), gradient.Value), node);
+
+ // Base the range of the inverse lerp on the change over two pixels
+ auto lowerEdge = writeLocal(ValueType::Float, String::Format(TEXT("{0} - {1}"), compValue.Value, change.Value), node);
+ auto upperEdge = writeLocal(ValueType::Float, String::Format(TEXT("{0} + {1}"), compValue.Value, change.Value), node);
+
+ // Do the inverse interpolation and saturate it
+ value = writeLocal(ValueType::Float, String::Format(TEXT("saturate((({0} - {1}) / ({2} - {1})))"), gradient.Value, lowerEdge.Value, upperEdge.Value), node);
+ }
default:
break;
}
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
index 01eb8d369..3d9be61d2 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
@@ -648,6 +648,60 @@ bool ImportMesh(int32 i, ImportedModelData& result, AssimpImporterData& data, St
result.LODs.Resize(lodIndex + 1);
result.LODs[lodIndex].Meshes.Add(meshData);
}
+
+ auto root = data.Scene->mRootNode;
+ Array points;
+ if (root->mNumChildren == 0)
+ {
+ aiQuaternion aiQuat;
+ aiVector3D aiPos;
+ aiVector3D aiScale;
+ root->mTransformation.Decompose(aiScale, aiQuat, aiPos);
+ auto quat = ToQuaternion(aiQuat);
+ auto pos = ToFloat3(aiPos);
+ auto scale = ToFloat3(aiScale);
+ Transform trans = Transform(pos, quat, scale);
+ points.Add(trans);
+ }
+ else
+ {
+ for (unsigned int j = 0; j < root->mNumChildren; j++)
+ {
+ aiQuaternion aiQuat;
+ aiVector3D aiPos;
+ aiVector3D aiScale;
+ root->mChildren[j]->mTransformation.Decompose(aiScale, aiQuat, aiPos);
+ auto quat = ToQuaternion(aiQuat);
+ auto pos = ToFloat3(aiPos);
+ auto scale = ToFloat3(aiScale);
+ Transform trans = Transform(pos, quat, scale);
+ points.Add(trans);
+ }
+ }
+
+ Float3 translation = Float3::Zero;
+ Float3 scale = Float3::Zero;
+ Quaternion orientation = Quaternion::Identity;
+ for (auto point : points)
+ {
+ translation += point.Translation;
+ scale += point.Scale;
+ orientation *= point.Orientation;
+ }
+
+ if (points.Count() > 0)
+ {
+ meshData->OriginTranslation = translation / (float)points.Count();
+ meshData->OriginOrientation = Quaternion::Invert(orientation);
+ meshData->Scaling = scale / (float)points.Count();
+ }
+ else
+ {
+ meshData->OriginTranslation = translation;
+ meshData->OriginOrientation = Quaternion::Invert(orientation);
+ meshData->Scaling = Float3(1);
+ }
+
return false;
}
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp
index f1e89d6bc..c7ee11b42 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp
@@ -836,6 +836,20 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
mesh.TransformBuffer(geometryTransform);
}*/
+ // Get local transform for origin shifting translation
+ auto translation = ToMatrix(aMesh->getGlobalTransform()).GetTranslation();
+ auto scale = data.GlobalSettings.UnitScaleFactor;
+ if (data.GlobalSettings.CoordAxis == ofbx::CoordSystem_RightHanded)
+ mesh.OriginTranslation = scale * Vector3(translation.X, translation.Y, -translation.Z);
+ else
+ mesh.OriginTranslation = scale * Vector3(translation.X, translation.Y, translation.Z);
+
+ auto rot = aMesh->getLocalRotation();
+ auto quat = Quaternion::Euler(-(float)rot.x, -(float)rot.y, -(float)rot.z);
+ mesh.OriginOrientation = quat;
+
+ auto scaling = aMesh->getLocalScaling();
+ mesh.Scaling = Vector3(scale * (float)scaling.x, scale * (float)scaling.y, scale * (float)scaling.z);
return false;
}
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp
index b703b47d1..a07cd8f23 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp
@@ -22,6 +22,7 @@
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Types/Pair.h"
+#include "Engine/Core/Types/Variant.h"
#include "Engine/Graphics/Models/SkeletonUpdater.h"
#include "Engine/Graphics/Models/SkeletonMapping.h"
#include "Engine/Core/Utilities.h"
@@ -366,11 +367,13 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(ImportLODs);
SERIALIZE(ImportVertexColors);
SERIALIZE(ImportBlendShapes);
+ SERIALIZE(CalculateBoneOffsetMatrices);
SERIALIZE(LightmapUVsSource);
SERIALIZE(CollisionMeshesPrefix);
SERIALIZE(Scale);
SERIALIZE(Rotation);
SERIALIZE(Translation);
+ SERIALIZE(UseLocalOrigin);
SERIALIZE(CenterGeometry);
SERIALIZE(Duration);
SERIALIZE(FramesRange);
@@ -396,6 +399,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(SDFResolution);
SERIALIZE(SplitObjects);
SERIALIZE(ObjectIndex);
+ SERIALIZE(SubAssetFolder);
}
void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -411,11 +415,13 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(ImportLODs);
DESERIALIZE(ImportVertexColors);
DESERIALIZE(ImportBlendShapes);
+ DESERIALIZE(CalculateBoneOffsetMatrices);
DESERIALIZE(LightmapUVsSource);
DESERIALIZE(CollisionMeshesPrefix);
DESERIALIZE(Scale);
DESERIALIZE(Rotation);
DESERIALIZE(Translation);
+ DESERIALIZE(UseLocalOrigin);
DESERIALIZE(CenterGeometry);
DESERIALIZE(Duration);
DESERIALIZE(FramesRange);
@@ -441,6 +447,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(SDFResolution);
DESERIALIZE(SplitObjects);
DESERIALIZE(ObjectIndex);
+ DESERIALIZE(SubAssetFolder);
// [Deprecated on 23.11.2021, expires on 21.11.2023]
int32 AnimationIndex = -1;
@@ -744,6 +751,32 @@ void MeshOptDeallocate(void* ptr)
Allocator::Free(ptr);
}
+void TrySetupMaterialParameter(MaterialInstance* instance, Span paramNames, const Variant& value, MaterialParameterType type)
+{
+ for (const Char* name : paramNames)
+ {
+ for (MaterialParameter& param : instance->Params)
+ {
+ const MaterialParameterType paramType = param.GetParameterType();
+ if (type != paramType)
+ {
+ if (type == MaterialParameterType::Color)
+ {
+ if (paramType != MaterialParameterType::Vector3 ||
+ paramType != MaterialParameterType::Vector4)
+ continue;
+ }
+ else
+ continue;
+ }
+ if (StringUtils::CompareIgnoreCase(name, param.GetName().Get()) != 0)
+ continue;
+ param.SetValue(value);
+ return;
+ }
+ }
+}
+
bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& options, String& errorMsg, const String& autoImportOutput)
{
LOG(Info, "Importing model from \'{0}\'", path);
@@ -1014,9 +1047,18 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
{
// Create material instance
AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath, material.AssetID);
- if (MaterialInstance* materialInstance = Content::Load(assetPath))
+ if (auto* materialInstance = Content::Load(assetPath))
{
materialInstance->SetBaseMaterial(options.InstanceToImportAs);
+
+ // Customize base material based on imported material (blind guess based on the common names used in materials)
+ const Char* diffuseColorNames[] = { TEXT("color"), TEXT("col"), TEXT("diffuse"), TEXT("basecolor"), TEXT("base color") };
+ TrySetupMaterialParameter(materialInstance, ToSpan(diffuseColorNames, ARRAY_COUNT(diffuseColorNames)), material.Diffuse.Color, MaterialParameterType::Color);
+ const Char* emissiveColorNames[] = { TEXT("emissive"), TEXT("emission"), TEXT("light") };
+ TrySetupMaterialParameter(materialInstance, ToSpan(emissiveColorNames, ARRAY_COUNT(emissiveColorNames)), material.Emissive.Color, MaterialParameterType::Color);
+ const Char* opacityValueNames[] = { TEXT("opacity"), TEXT("alpha") };
+ TrySetupMaterialParameter(materialInstance, ToSpan(opacityValueNames, ARRAY_COUNT(opacityValueNames)), material.Opacity.Value, MaterialParameterType::Float);
+
materialInstance->Save();
}
else
@@ -1051,11 +1093,16 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
// Prepare import transformation
Transform importTransform(options.Translation, options.Rotation, Float3(options.Scale));
+ if (options.UseLocalOrigin && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
+ {
+ importTransform.Translation -= importTransform.Orientation * data.LODs[0].Meshes[0]->OriginTranslation * importTransform.Scale;
+ }
if (options.CenterGeometry && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
{
// Calculate the bounding box (use LOD0 as a reference)
BoundingBox box = data.LODs[0].GetBox();
- importTransform.Translation -= box.GetCenter();
+ auto center = data.LODs[0].Meshes[0]->OriginOrientation * importTransform.Orientation * box.GetCenter() * importTransform.Scale * data.LODs[0].Meshes[0]->Scaling;
+ importTransform.Translation -= center;
}
const bool applyImportTransform = !importTransform.IsIdentity();
@@ -1380,6 +1427,15 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
SkeletonUpdater hierarchyUpdater(data.Nodes);
hierarchyUpdater.UpdateMatrices();
+ if (options.CalculateBoneOffsetMatrices)
+ {
+ // Calculate offset matrix (inverse bind pose transform) for every bone manually
+ for (SkeletonBone& bone : data.Skeleton.Bones)
+ {
+ CalculateBoneOffsetMatrix(data.Skeleton.Nodes, bone.OffsetMatrix, bone.NodeIndex);
+ }
+ }
+
// Move meshes in the new nodes
for (int32 lodIndex = 0; lodIndex < data.LODs.Count(); lodIndex++)
{
@@ -1401,15 +1457,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
}
}
- // TODO: allow to link skeleton asset to model to retarget model bones skeleton for an animation
- // use SkeletonMapping to map bones?
-
- // Calculate offset matrix (inverse bind pose transform) for every bone manually
- /*for (SkeletonBone& bone : data.Skeleton.Bones)
- {
- CalculateBoneOffsetMatrix(data.Skeleton.Nodes, bone.OffsetMatrix, bone.NodeIndex);
- }*/
-
#if USE_SKELETON_NODES_SORTING
// Sort skeleton nodes and bones hierarchy (parents first)
// Then it can be used with a simple linear loop update
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h
index 36bb9b12c..c7876e826 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.h
+++ b/Source/Engine/Tools/ModelTool/ModelTool.h
@@ -258,6 +258,9 @@ public:
// Enable/disable importing blend shapes (morph targets).
API_FIELD(Attributes="EditorOrder(85), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSkinnedModel))")
bool ImportBlendShapes = false;
+ // Enable skeleton bones offset matrices recalculating.
+ API_FIELD(Attributes="EditorOrder(86), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSkinnedModel))")
+ bool CalculateBoneOffsetMatrices = false;
// The lightmap UVs source.
API_FIELD(Attributes="EditorOrder(90), EditorDisplay(\"Geometry\", \"Lightmap UVs Source\"), VisibleIf(nameof(ShowModel))")
ModelLightmapUVsSource LightmapUVsSource = ModelLightmapUVsSource::Disable;
@@ -279,8 +282,11 @@ public:
// Custom import geometry offset.
API_FIELD(Attributes="EditorOrder(520), EditorDisplay(\"Transform\")")
Float3 Translation = Float3::Zero;
- // If checked, the imported geometry will be shifted to the center of mass.
+ // If checked, the imported geometry will be shifted to its local transform origin.
API_FIELD(Attributes="EditorOrder(530), EditorDisplay(\"Transform\")")
+ bool UseLocalOrigin = false;
+ // If checked, the imported geometry will be shifted to the center of mass.
+ API_FIELD(Attributes="EditorOrder(540), EditorDisplay(\"Transform\")")
bool CenterGeometry = false;
public: // Animation
@@ -370,6 +376,12 @@ public:
API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Splitting\")")
int32 ObjectIndex = -1;
+ public: // Other
+
+ // If specified, will be used as sub-directory name for automatically imported sub assets such as textures and materials. Set to whitespace (single space) to import to the same directory.
+ API_FIELD(Attributes="EditorOrder(3030), EditorDisplay(\"Other\")")
+ String SubAssetFolder = TEXT("");
+
// Runtime data for objects splitting during import (used internally)
void* SplitContext = nullptr;
Function OnSplitImport;
diff --git a/Source/Engine/UI/GUI/CanvasContainer.cs b/Source/Engine/UI/GUI/CanvasContainer.cs
index f032a99d3..084a22503 100644
--- a/Source/Engine/UI/GUI/CanvasContainer.cs
+++ b/Source/Engine/UI/GUI/CanvasContainer.cs
@@ -41,11 +41,12 @@ namespace FlaxEngine.GUI
protected override void DrawChildren()
{
// Draw all screen space canvases
+ var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default;
for (int i = 0; i < _children.Count; i++)
{
var child = (CanvasRootControl)_children[i];
- if (child.Visible && child.Is2D)
+ if (child.Visible && child.Is2D && layerMask.HasLayer(child.Canvas.Layer))
{
child.Draw();
}
@@ -69,10 +70,11 @@ namespace FlaxEngine.GUI
UICanvas.CalculateRay(ref location, out Ray ray);
// Test 3D
+ var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default;
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
{
var child = (CanvasRootControl)_children[i];
- if (child.Visible && child.Enabled && child.Is3D)
+ if (child.Visible && child.Enabled && child.Is3D && layerMask.HasLayer(child.Canvas.Layer))
{
if (child.Intersects3D(ref ray, out var childLocation))
{
@@ -91,10 +93,11 @@ namespace FlaxEngine.GUI
// Check all children collisions with mouse and fire events for them
bool isFirst3DHandled = false;
+ var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default;
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
{
var child = (CanvasRootControl)_children[i];
- if (child.Visible && child.Enabled)
+ if (child.Visible && child.Enabled && layerMask.HasLayer(child.Canvas.Layer))
{
// Fire events
if (child.Is2D)
@@ -156,10 +159,11 @@ namespace FlaxEngine.GUI
UICanvas.CalculateRay(ref location, out Ray ray);
// Test 3D
+ var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default;
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
{
var child = (CanvasRootControl)_children[i];
- if (child.Visible && child.Enabled && child.Is3D)
+ if (child.Visible && child.Enabled && child.Is3D && layerMask.HasLayer(child.Canvas.Layer))
{
if (child.Intersects3D(ref ray, out var childLocation))
{
@@ -183,10 +187,11 @@ namespace FlaxEngine.GUI
UICanvas.CalculateRay(ref location, out Ray ray);
// Test 3D
+ var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default;
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
{
var child = (CanvasRootControl)_children[i];
- if (child.Visible && child.Enabled && child.Is3D)
+ if (child.Visible && child.Enabled && child.Is3D && layerMask.HasLayer(child.Canvas.Layer))
{
if (child.Intersects3D(ref ray, out var childLocation))
{
@@ -210,10 +215,11 @@ namespace FlaxEngine.GUI
UICanvas.CalculateRay(ref location, out Ray ray);
// Test 3D
+ var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default;
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
{
var child = (CanvasRootControl)_children[i];
- if (child.Visible && child.Enabled && child.Is3D)
+ if (child.Visible && child.Enabled && child.Is3D && layerMask.HasLayer(child.Canvas.Layer))
{
if (child.Intersects3D(ref ray, out var childLocation))
{
@@ -237,10 +243,11 @@ namespace FlaxEngine.GUI
UICanvas.CalculateRay(ref location, out Ray ray);
// Test 3D
+ var layerMask = MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default;
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
{
var child = (CanvasRootControl)_children[i];
- if (child.Visible && child.Enabled && child.Is3D)
+ if (child.Visible && child.Enabled && child.Is3D && layerMask.HasLayer(child.Canvas.Layer))
{
if (child.Intersects3D(ref ray, out var childLocation))
{
diff --git a/Source/Engine/UI/GUI/CanvasRootControl.cs b/Source/Engine/UI/GUI/CanvasRootControl.cs
index b4fa067e3..b2ea9aaa0 100644
--- a/Source/Engine/UI/GUI/CanvasRootControl.cs
+++ b/Source/Engine/UI/GUI/CanvasRootControl.cs
@@ -74,6 +74,8 @@ namespace FlaxEngine.GUI
return false;
}
+ private bool SkipEvents => !_canvas.ReceivesEvents || !_canvas.IsVisible();
+
///
public override CursorType Cursor
{
@@ -197,7 +199,7 @@ namespace FlaxEngine.GUI
public override void Update(float deltaTime)
{
// UI navigation
- if (_canvas.ReceivesEvents)
+ if (SkipEvents)
{
UpdateNavigation(deltaTime, _canvas.NavigateUp.Name, NavDirection.Up, ref _navigationHeldTimeUp, ref _navigationRateTimeUp);
UpdateNavigation(deltaTime, _canvas.NavigateDown.Name, NavDirection.Down, ref _navigationHeldTimeDown, ref _navigationRateTimeDown);
@@ -267,7 +269,7 @@ namespace FlaxEngine.GUI
///
public override bool OnCharInput(char c)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return false;
return base.OnCharInput(c);
@@ -276,7 +278,7 @@ namespace FlaxEngine.GUI
///
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return DragDropEffect.None;
return base.OnDragDrop(ref location, data);
@@ -285,7 +287,7 @@ namespace FlaxEngine.GUI
///
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return DragDropEffect.None;
return base.OnDragEnter(ref location, data);
@@ -294,7 +296,7 @@ namespace FlaxEngine.GUI
///
public override void OnDragLeave()
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return;
base.OnDragLeave();
@@ -303,7 +305,7 @@ namespace FlaxEngine.GUI
///
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return DragDropEffect.None;
return base.OnDragMove(ref location, data);
@@ -312,7 +314,7 @@ namespace FlaxEngine.GUI
///
public override bool OnKeyDown(KeyboardKeys key)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return false;
return base.OnKeyDown(key);
@@ -321,7 +323,7 @@ namespace FlaxEngine.GUI
///
public override void OnKeyUp(KeyboardKeys key)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return;
base.OnKeyUp(key);
@@ -330,7 +332,7 @@ namespace FlaxEngine.GUI
///
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return false;
return base.OnMouseDoubleClick(location, button);
@@ -339,7 +341,7 @@ namespace FlaxEngine.GUI
///
public override bool OnMouseDown(Float2 location, MouseButton button)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return false;
return base.OnMouseDown(location, button);
@@ -348,7 +350,7 @@ namespace FlaxEngine.GUI
///
public override void OnMouseEnter(Float2 location)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return;
_mousePosition = location;
@@ -359,8 +361,7 @@ namespace FlaxEngine.GUI
public override void OnMouseLeave()
{
_mousePosition = Float2.Zero;
-
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return;
base.OnMouseLeave();
@@ -369,7 +370,7 @@ namespace FlaxEngine.GUI
///
public override void OnMouseMove(Float2 location)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return;
_mousePosition = location;
@@ -379,7 +380,7 @@ namespace FlaxEngine.GUI
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return false;
return base.OnMouseUp(location, button);
@@ -388,7 +389,7 @@ namespace FlaxEngine.GUI
///
public override bool OnMouseWheel(Float2 location, float delta)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return false;
return base.OnMouseWheel(location, delta);
@@ -397,7 +398,7 @@ namespace FlaxEngine.GUI
///
public override void OnTouchEnter(Float2 location, int pointerId)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return;
base.OnTouchEnter(location, pointerId);
@@ -406,7 +407,7 @@ namespace FlaxEngine.GUI
///
public override bool OnTouchDown(Float2 location, int pointerId)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return false;
return base.OnTouchDown(location, pointerId);
@@ -415,7 +416,7 @@ namespace FlaxEngine.GUI
///
public override void OnTouchMove(Float2 location, int pointerId)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return;
base.OnTouchMove(location, pointerId);
@@ -424,7 +425,7 @@ namespace FlaxEngine.GUI
///
public override bool OnTouchUp(Float2 location, int pointerId)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return false;
return base.OnTouchUp(location, pointerId);
@@ -433,7 +434,7 @@ namespace FlaxEngine.GUI
///
public override void OnTouchLeave(int pointerId)
{
- if (!_canvas.ReceivesEvents)
+ if (SkipEvents)
return;
base.OnTouchLeave(pointerId);
diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
index c08b59f48..55b58634d 100644
--- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs
+++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
@@ -1399,6 +1399,12 @@ namespace FlaxEngine.GUI
}
case KeyboardKeys.Escape:
{
+ if (IsReadOnly)
+ {
+ SetSelection(_selectionEnd);
+ return true;
+ }
+
RestoreTextFromStart();
if (!IsNavFocused)
diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs
index edbec7f7d..bc8c360d1 100644
--- a/Source/Engine/UI/UICanvas.cs
+++ b/Source/Engine/UI/UICanvas.cs
@@ -66,6 +66,8 @@ namespace FlaxEngine
///
public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output)
{
+ if (!Canvas.IsVisible(renderContext.View.RenderLayersMask))
+ return;
var bounds = Canvas.Bounds;
bounds.Transformation.Translation -= renderContext.View.Origin;
if (renderContext.View.Frustum.Contains(bounds.GetBoundingBox()) == ContainmentType.Disjoint)
@@ -873,6 +875,20 @@ namespace FlaxEngine
}
}
+ internal bool IsVisible()
+ {
+ return IsVisible(MainRenderTask.Instance?.ViewLayersMask ?? LayersMask.Default);
+ }
+
+ internal bool IsVisible(LayersMask layersMask)
+ {
+#if FLAX_EDITOR
+ if (_editorTask != null || _editorRoot != null)
+ return true;
+#endif
+ return layersMask.HasLayer(Layer);
+ }
+
#if FLAX_EDITOR
private SceneRenderTask _editorTask;
private ContainerControl _editorRoot;
diff --git a/Source/Engine/Visject/ShaderGraphValue.cpp b/Source/Engine/Visject/ShaderGraphValue.cpp
index 56d7c9d06..6564cde2b 100644
--- a/Source/Engine/Visject/ShaderGraphValue.cpp
+++ b/Source/Engine/Visject/ShaderGraphValue.cpp
@@ -40,11 +40,15 @@ ShaderGraphValue::ShaderGraphValue(const Variant& v)
break;
case VariantType::Float:
Type = VariantType::Types::Float;
- Value = String::Format(TEXT("{:.8f}"), v.AsFloat);
+ Value = String::Format(TEXT("{}"), v.AsFloat);
+ if (Value.Find('.') == -1)
+ Value = String::Format(TEXT("{:.1f}"), v.AsFloat);
break;
case VariantType::Double:
Type = VariantType::Types::Float;
- Value = String::Format(TEXT("{:.8f}"), (float)v.AsDouble);
+ Value = String::Format(TEXT("{}"), (float)v.AsDouble);
+ if (Value.Find('.') == -1)
+ Value = String::Format(TEXT("{:.1f}"), (float)v.AsDouble);
break;
case VariantType::Float2:
{
@@ -134,6 +138,29 @@ bool ShaderGraphValue::IsOne() const
}
}
+bool ShaderGraphValue::IsLiteral() const
+{
+ switch (Type)
+ {
+ case VariantType::Types::Bool:
+ case VariantType::Types::Int:
+ case VariantType::Types::Uint:
+ case VariantType::Types::Float:
+ if (Value.HasChars())
+ {
+ for (int32 i = 0; i < Value.Length(); i++)
+ {
+ const Char c = Value[i];
+ if (!StringUtils::IsDigit(c) && c != '.')
+ return false;
+ }
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
ShaderGraphValue ShaderGraphValue::InitForZero(VariantType::Types type)
{
const Char* v;
diff --git a/Source/Engine/Visject/ShaderGraphValue.h b/Source/Engine/Visject/ShaderGraphValue.h
index 7c7e25154..439b59077 100644
--- a/Source/Engine/Visject/ShaderGraphValue.h
+++ b/Source/Engine/Visject/ShaderGraphValue.h
@@ -143,8 +143,7 @@ public:
///
/// Returns true if value is valid.
///
- /// True if is valid, otherwise false.
- bool IsValid() const
+ FORCE_INLINE bool IsValid() const
{
return Type != VariantType::Types::Null;
}
@@ -152,8 +151,7 @@ public:
///
/// Returns true if value is invalid.
///
- /// True if is invalid, otherwise false.
- bool IsInvalid() const
+ FORCE_INLINE bool IsInvalid() const
{
return Type == VariantType::Types::Null;
}
@@ -161,15 +159,18 @@ public:
///
/// Checks if value contains static part with zero.
///
- /// True if contains zero number.
bool IsZero() const;
///
/// Checks if value contains static part with one.
///
- /// True if contains one number.
bool IsOne() const;
+ ///
+ /// Checks if value is a compile-time constant literal (eg. int, bool or float).
+ ///
+ bool IsLiteral() const;
+
///
/// Clears this instance.
///
diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp
index 1cee5d46e..4e94473f6 100644
--- a/Source/Engine/Visject/VisjectGraph.cpp
+++ b/Source/Engine/Visject/VisjectGraph.cpp
@@ -685,7 +685,7 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
case 36:
{
// Get value with structure data
- const Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection());
+ Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection());
if (!node->GetBox(0)->HasConnection())
return;
@@ -741,7 +741,9 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
return;
}
const ScriptingType& type = typeHandle.GetType();
- if (structureValue.Type.Type != VariantType::Structure || StringUtils::Compare(typeNameAnsi.Get(), structureValue.Type.TypeName) != 0)
+ structureValue.InvertInline(); // Extract any Float3/Int32 into Structure type from inlined format
+ const ScriptingTypeHandle structureValueTypeHandle = Scripting::FindScriptingType(structureValue.Type.GetTypeName());
+ if (structureValue.Type.Type != VariantType::Structure || typeHandle != structureValueTypeHandle)
{
OnError(node, box, String::Format(TEXT("Cannot unpack value of type {0} to structure of type {1}"), structureValue.Type, typeName));
return;
diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h
index 5836afb2f..085c05de0 100644
--- a/Source/Engine/Visject/VisjectGraph.h
+++ b/Source/Engine/Visject/VisjectGraph.h
@@ -262,11 +262,11 @@ protected:
FORCE_INLINE Value tryGetValue(Box* box)
{
- return box && box->HasConnection() ? eatBox(box->GetParent(), box->FirstConnection()) : Value::Zero;
+ return box && box->Connections.HasItems() ? eatBox(box->GetParent(), (VisjectGraphBox*)box->Connections.Get()[0]) : Value::Zero;
}
FORCE_INLINE Value tryGetValue(Box* box, const Value& defaultValue)
{
- return box && box->HasConnection() ? eatBox(box->GetParent(), box->FirstConnection()) : defaultValue;
+ return box && box->Connections.HasItems() ? eatBox(box->GetParent(), (VisjectGraphBox*)box->Connections.Get()[0]) : defaultValue;
}
};
diff --git a/Source/Platforms/Mac/Default.icns b/Source/Platforms/Mac/Default.icns
index 455acd991..911276d8e 100644
Binary files a/Source/Platforms/Mac/Default.icns and b/Source/Platforms/Mac/Default.icns differ
diff --git a/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj b/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj
index 1dd5d4e01..3b8889487 100644
--- a/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj
+++ b/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj
@@ -222,7 +222,7 @@ ${PBXResourcesGroup}
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- HEADER_SEARCH_PATHS = ${HeaderSearchPaths};
+ HEADER_SEARCH_PATHS = "${HeaderSearchPaths}";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
@@ -275,7 +275,7 @@ ${PBXResourcesGroup}
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- HEADER_SEARCH_PATHS = ${HeaderSearchPaths};
+ HEADER_SEARCH_PATHS = "${HeaderSearchPaths}";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader
index 3d4f7dd46..bc4f272fc 100644
--- a/Source/Shaders/GlobalSignDistanceField.shader
+++ b/Source/Shaders/GlobalSignDistanceField.shader
@@ -211,7 +211,7 @@ float SampleSDF(uint3 voxelCoordMip, int3 offset)
float result = GlobalSDFTex[voxelCoordMip].r;
// Extend by distance to the sampled texel location
- float distanceInWorldUnits = length(offset) * (MaxDistance / (float)GenerateMipTexResolution);
+ float distanceInWorldUnits = length((float3)offset) * (MaxDistance / (float)GenerateMipTexResolution);
float distanceToVoxel = distanceInWorldUnits / MaxDistance;
result = CombineDistanceToSDF(result, distanceToVoxel);
diff --git a/Source/Shaders/Math.hlsl b/Source/Shaders/Math.hlsl
index ad3168016..67ddc1887 100644
--- a/Source/Shaders/Math.hlsl
+++ b/Source/Shaders/Math.hlsl
@@ -218,10 +218,10 @@ float4 ClampedPow(float4 x, float4 y)
float4 FindQuatBetween(float3 from, float3 to)
{
+ // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final
float normAB = 1.0f;
float w = normAB + dot(from, to);
float4 result;
-
if (w >= 1e-6f * normAB)
{
result = float4
@@ -234,12 +234,10 @@ float4 FindQuatBetween(float3 from, float3 to)
}
else
{
- w = 0.f;
result = abs(from.x) > abs(from.y)
- ? float4(-from.z, 0.f, from.x, w)
- : float4(0.f, -from.z, from.y, w);
+ ? float4(-from.z, 0.f, from.x, 0.0f)
+ : float4(0.f, -from.z, from.y, 0.0f);
}
-
return normalize(result);
}
diff --git a/Source/ThirdParty/stb/stb_image_write.h b/Source/ThirdParty/stb/stb_image_write.h
index 95943eb60..8cae247eb 100644
--- a/Source/ThirdParty/stb/stb_image_write.h
+++ b/Source/ThirdParty/stb/stb_image_write.h
@@ -758,6 +758,8 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f
#ifdef __STDC_WANT_SECURE_LIB__
len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
+#elif __APPLE__
+ len = snprintf(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#else
len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#endif
diff --git a/Source/ThirdParty/tracy/TracyClient.cpp b/Source/ThirdParty/tracy/TracyClient.cpp
index 3548c5752..1fbd78f96 100644
--- a/Source/ThirdParty/tracy/TracyClient.cpp
+++ b/Source/ThirdParty/tracy/TracyClient.cpp
@@ -11,10 +11,10 @@
// Define TRACY_ENABLE to enable profiler.
-#ifdef TRACY_ENABLE
-
#include "common/TracySystem.cpp"
+#ifdef TRACY_ENABLE
+
#ifdef _MSC_VER
# pragma warning(push, 0)
#endif
@@ -22,12 +22,13 @@
#include
#include "client/TracyProfiler.cpp"
#include "client/TracyCallstack.cpp"
+#include "client/TracySysPower.cpp"
#include "client/TracySysTime.cpp"
#include "client/TracySysTrace.cpp"
#include "common/TracySocket.cpp"
#include "client/tracy_rpmalloc.cpp"
#include "client/TracyAlloc.cpp"
-
+#include "client/TracyOverride.cpp"
#if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6
# include "libbacktrace/alloc.cpp"
diff --git a/Source/ThirdParty/tracy/client/TracyCallstack.cpp b/Source/ThirdParty/tracy/client/TracyCallstack.cpp
index ca19a543b..0de7c9d2e 100644
--- a/Source/ThirdParty/tracy/client/TracyCallstack.cpp
+++ b/Source/ThirdParty/tracy/client/TracyCallstack.cpp
@@ -154,7 +154,6 @@ void InitCallstack()
DBGHELP_LOCK;
#endif
- //SymInitialize( GetCurrentProcess(), "C:\\Flax\\FlaxEngine\\Binaries\\Editor\\Win64\\Debug;C:\\Flax\\FlaxEngine\\Cache\\Projects", true );
SymInitialize( GetCurrentProcess(), nullptr, true );
SymSetOptions( SYMOPT_LOAD_LINES );
@@ -228,6 +227,10 @@ void InitCallstack()
const auto res = GetModuleFileNameA( mod[i], name, 1021 );
if( res > 0 )
{
+ // This may be a new module loaded since our call to SymInitialize.
+ // Just in case, force DbgHelp to load its pdb !
+ SymLoadModuleEx(proc, NULL, name, NULL, (DWORD64)info.lpBaseOfDll, info.SizeOfImage, NULL, 0);
+
auto ptr = name + res;
while( ptr > name && *ptr != '\\' && *ptr != '/' ) ptr--;
if( ptr > name ) ptr++;
@@ -683,7 +686,9 @@ void InitCallstackCritical()
void InitCallstack()
{
cb_bts = backtrace_create_state( nullptr, 0, nullptr, nullptr );
+#ifndef TRACY_DEMANGLE
___tracy_init_demangle_buffer();
+#endif
#ifdef __linux
InitKernelSymbols();
@@ -758,7 +763,9 @@ debuginfod_client* GetDebuginfodClient()
void EndCallstack()
{
+#ifndef TRACY_DEMANGLE
___tracy_free_demangle_buffer();
+#endif
#ifdef TRACY_DEBUGINFOD
ClearDebugInfoVector( s_di_known );
debuginfod_end( s_debuginfod );
diff --git a/Source/ThirdParty/tracy/client/TracyCallstack.hpp b/Source/ThirdParty/tracy/client/TracyCallstack.hpp
index 8cfede8fb..96bee3f51 100644
--- a/Source/ThirdParty/tracy/client/TracyCallstack.hpp
+++ b/Source/ThirdParty/tracy/client/TracyCallstack.hpp
@@ -10,7 +10,14 @@
#endif
-#ifdef TRACY_HAS_CALLSTACK
+#ifndef TRACY_HAS_CALLSTACK
+
+namespace tracy
+{
+static tracy_force_inline void* Callstack( int depth ) { return nullptr; }
+}
+
+#else
#ifdef TRACY_DEBUGINFOD
# include
diff --git a/Source/ThirdParty/tracy/client/TracyLock.hpp b/Source/ThirdParty/tracy/client/TracyLock.hpp
index 296a41ba1..d12a3c16d 100644
--- a/Source/ThirdParty/tracy/client/TracyLock.hpp
+++ b/Source/ThirdParty/tracy/client/TracyLock.hpp
@@ -21,7 +21,7 @@ public:
, m_active( false )
#endif
{
- assert( m_id != std::numeric_limits::max() );
+ assert( m_id != (std::numeric_limits::max)() );
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockAnnounce );
@@ -154,7 +154,7 @@ public:
tracy_force_inline void CustomName( const char* name, size_t size )
{
- assert( size < std::numeric_limits::max() );
+ assert( size < (std::numeric_limits::max)() );
auto ptr = (char*)tracy_malloc( size );
memcpy( ptr, name, size );
auto item = Profiler::QueueSerial();
@@ -235,7 +235,7 @@ public:
, m_active( false )
#endif
{
- assert( m_id != std::numeric_limits::max() );
+ assert( m_id != (std::numeric_limits::max)() );
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockAnnounce );
@@ -450,7 +450,7 @@ public:
tracy_force_inline void CustomName( const char* name, size_t size )
{
- assert( size < std::numeric_limits::max() );
+ assert( size < (std::numeric_limits::max)() );
auto ptr = (char*)tracy_malloc( size );
memcpy( ptr, name, size );
auto item = Profiler::QueueSerial();
diff --git a/Source/ThirdParty/tracy/client/TracyOverride.cpp b/Source/ThirdParty/tracy/client/TracyOverride.cpp
new file mode 100644
index 000000000..591508a7f
--- /dev/null
+++ b/Source/ThirdParty/tracy/client/TracyOverride.cpp
@@ -0,0 +1,26 @@
+#ifdef TRACY_ENABLE
+# ifdef __linux__
+# include "TracyDebug.hpp"
+# ifdef TRACY_VERBOSE
+# include
+# include
+# endif
+
+extern "C" int dlclose( void* hnd )
+{
+#ifdef TRACY_VERBOSE
+ struct link_map* lm;
+ if( dlinfo( hnd, RTLD_DI_LINKMAP, &lm ) == 0 )
+ {
+ TracyDebug( "Overriding dlclose for %s\n", lm->l_name );
+ }
+ else
+ {
+ TracyDebug( "Overriding dlclose for unknown object (%s)\n", dlerror() );
+ }
+#endif
+ return 0;
+}
+
+# endif
+#endif
diff --git a/Source/ThirdParty/tracy/client/TracyProfiler.cpp b/Source/ThirdParty/tracy/client/TracyProfiler.cpp
index dfbb22a83..59c31d6f8 100644
--- a/Source/ThirdParty/tracy/client/TracyProfiler.cpp
+++ b/Source/ThirdParty/tracy/client/TracyProfiler.cpp
@@ -81,7 +81,9 @@
#endif
#ifdef __APPLE__
-# define TRACY_DELAYED_INIT
+# ifndef TRACY_DELAYED_INIT
+# define TRACY_DELAYED_INIT
+# endif
#else
# ifdef __GNUC__
# define init_order( val ) __attribute__ ((init_priority(val)))
@@ -1074,7 +1076,9 @@ static void CrashHandler( int signal, siginfo_t* info, void* /*ucontext*/ )
}
closedir( dp );
+#ifdef TRACY_HAS_CALLSTACK
if( selfTid == s_symbolTid ) s_symbolThreadGone.store( true, std::memory_order_release );
+#endif
TracyLfqPrepare( QueueType::Crash );
TracyLfqCommit;
@@ -1355,6 +1359,7 @@ Profiler::Profiler()
, m_queryImage( nullptr )
, m_queryData( nullptr )
, m_crashHandlerInstalled( false )
+ , m_programName( nullptr )
{
assert( !s_instance );
s_instance = this;
@@ -1417,7 +1422,9 @@ void Profiler::SpawnWorkerThreads()
#if defined _WIN32 && !defined TRACY_UWP && !defined TRACY_NO_CRASH_HANDLER
s_profilerThreadId = GetThreadId( s_thread->Handle() );
+# ifdef TRACY_HAS_CALLSTACK
s_symbolThreadId = GetThreadId( s_symbolThread->Handle() );
+# endif
m_exceptionHandler = AddVectoredExceptionHandler( 1, CrashFilter );
#endif
@@ -1456,7 +1463,7 @@ Profiler::~Profiler()
if( m_crashHandlerInstalled ) RemoveVectoredExceptionHandler( m_exceptionHandler );
#endif
-#ifdef __linux__
+#if defined __linux__ && !defined TRACY_NO_CRASH_HANDLER
if( m_crashHandlerInstalled )
{
sigaction( TRACY_CRASH_SIGNAL, &m_prevSignal.pwr, nullptr );
@@ -1522,7 +1529,7 @@ bool Profiler::ShouldExit()
void Profiler::Worker()
{
-#ifdef __linux__
+#if defined __linux__ && !defined TRACY_NO_CRASH_HANDLER
s_profilerTid = syscall( SYS_gettid );
#endif
@@ -1711,6 +1718,9 @@ void Profiler::Worker()
if( m_sock ) break;
#ifndef TRACY_ON_DEMAND
ProcessSysTime();
+# ifdef TRACY_HAS_SYSPOWER
+ m_sysPower.Tick();
+# endif
#endif
if( m_broadcast )
@@ -1718,6 +1728,14 @@ void Profiler::Worker()
const auto t = std::chrono::high_resolution_clock::now().time_since_epoch().count();
if( t - lastBroadcast > 3000000000 ) // 3s
{
+ m_programNameLock.lock();
+ if( m_programName )
+ {
+ broadcastMsg = GetBroadcastMessage( m_programName, strlen( m_programName ), broadcastLen, dataPort );
+ m_programName = nullptr;
+ }
+ m_programNameLock.unlock();
+
lastBroadcast = t;
const auto ts = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count();
broadcastMsg.activeTime = int32_t( ts - m_epoch );
@@ -1828,6 +1846,9 @@ void Profiler::Worker()
for(;;)
{
ProcessSysTime();
+#ifdef TRACY_HAS_SYSPOWER
+ m_sysPower.Tick();
+#endif
const auto status = Dequeue( token );
const auto serialStatus = DequeueSerial();
if( status == DequeueStatus::ConnectionLost || serialStatus == DequeueStatus::ConnectionLost )
@@ -3026,9 +3047,9 @@ void Profiler::SendSourceLocation( uint64_t ptr )
MemWrite( &item.srcloc.file, (uint64_t)srcloc->file );
MemWrite( &item.srcloc.function, (uint64_t)srcloc->function );
MemWrite( &item.srcloc.line, srcloc->line );
- MemWrite( &item.srcloc.r, uint8_t( ( srcloc->color ) & 0xFF ) );
+ MemWrite( &item.srcloc.b, uint8_t( ( srcloc->color ) & 0xFF ) );
MemWrite( &item.srcloc.g, uint8_t( ( srcloc->color >> 8 ) & 0xFF ) );
- MemWrite( &item.srcloc.b, uint8_t( ( srcloc->color >> 16 ) & 0xFF ) );
+ MemWrite( &item.srcloc.r, uint8_t( ( srcloc->color >> 16 ) & 0xFF ) );
AppendData( &item, QueueDataSize[(int)QueueType::SourceLocation] );
}
@@ -3331,10 +3352,8 @@ bool Profiler::HandleServerQuery()
uint8_t type;
uint64_t ptr;
- uint32_t extra;
memcpy( &type, &payload.type, sizeof( payload.type ) );
memcpy( &ptr, &payload.ptr, sizeof( payload.ptr ) );
- memcpy( &extra, &payload.extra, sizeof( payload.extra ) );
switch( type )
{
@@ -3381,7 +3400,7 @@ bool Profiler::HandleServerQuery()
break;
#ifndef TRACY_NO_CODE_TRANSFER
case ServerQuerySymbolCode:
- HandleSymbolCodeQuery( ptr, extra );
+ HandleSymbolCodeQuery( ptr, payload.extra );
break;
#endif
case ServerQuerySourceCode:
@@ -3398,7 +3417,7 @@ bool Profiler::HandleServerQuery()
break;
case ServerQueryDataTransferPart:
memcpy( m_queryDataPtr, &ptr, 8 );
- memcpy( m_queryDataPtr+8, &extra, 4 );
+ memcpy( m_queryDataPtr+8, &payload.extra, 4 );
m_queryDataPtr += 12;
AckServerQuery();
break;
@@ -3753,7 +3772,7 @@ void Profiler::SendFrameImage( const void* image, uint16_t w, uint16_t h, uint8_
{
#ifndef TRACY_NO_FRAME_IMAGE
auto& profiler = GetProfiler();
- assert( profiler.m_frameCount.load( std::memory_order_relaxed ) < std::numeric_limits::max() );
+ assert( profiler.m_frameCount.load( std::memory_order_relaxed ) < (std::numeric_limits::max)() );
# ifdef TRACY_ON_DEMAND
if( !profiler.IsConnected() ) return;
# endif
@@ -3770,6 +3789,12 @@ void Profiler::SendFrameImage( const void* image, uint16_t w, uint16_t h, uint8_
fi->flip = flip;
profiler.m_fiQueue.commit_next();
profiler.m_fiLock.unlock();
+#else
+ static_cast(image); // unused
+ static_cast(w); // unused
+ static_cast(h); // unused
+ static_cast(offset); // unused
+ static_cast(flip); // unused
#endif
}
@@ -3827,7 +3852,7 @@ void Profiler::ConfigurePlot( const char* name, PlotFormatType type, bool step,
void Profiler::Message( const char* txt, size_t size, int callstack )
{
- assert( size < std::numeric_limits::max() );
+ assert( size < (std::numeric_limits::max)() );
#ifdef TRACY_ON_DEMAND
if( !GetProfiler().IsConnected() ) return;
#endif
@@ -3864,7 +3889,7 @@ void Profiler::Message( const char* txt, int callstack )
void Profiler::MessageColor( const char* txt, size_t size, uint32_t color, int callstack )
{
- assert( size < std::numeric_limits::max() );
+ assert( size < (std::numeric_limits::max)() );
#ifdef TRACY_ON_DEMAND
if( !GetProfiler().IsConnected() ) return;
#endif
@@ -3879,9 +3904,9 @@ void Profiler::MessageColor( const char* txt, size_t size, uint32_t color, int c
TracyQueuePrepare( callstack == 0 ? QueueType::MessageColor : QueueType::MessageColorCallstack );
MemWrite( &item->messageColorFat.time, GetTime() );
MemWrite( &item->messageColorFat.text, (uint64_t)ptr );
- MemWrite( &item->messageColorFat.r, uint8_t( ( color ) & 0xFF ) );
+ MemWrite( &item->messageColorFat.b, uint8_t( ( color ) & 0xFF ) );
MemWrite( &item->messageColorFat.g, uint8_t( ( color >> 8 ) & 0xFF ) );
- MemWrite( &item->messageColorFat.b, uint8_t( ( color >> 16 ) & 0xFF ) );
+ MemWrite( &item->messageColorFat.r, uint8_t( ( color >> 16 ) & 0xFF ) );
MemWrite( &item->messageColorFat.size, (uint16_t)size );
TracyQueueCommit( messageColorFatThread );
}
@@ -3899,15 +3924,15 @@ void Profiler::MessageColor( const char* txt, uint32_t color, int callstack )
TracyQueuePrepare( callstack == 0 ? QueueType::MessageLiteralColor : QueueType::MessageLiteralColorCallstack );
MemWrite( &item->messageColorLiteral.time, GetTime() );
MemWrite( &item->messageColorLiteral.text, (uint64_t)txt );
- MemWrite( &item->messageColorLiteral.r, uint8_t( ( color ) & 0xFF ) );
+ MemWrite( &item->messageColorLiteral.b, uint8_t( ( color ) & 0xFF ) );
MemWrite( &item->messageColorLiteral.g, uint8_t( ( color >> 8 ) & 0xFF ) );
- MemWrite( &item->messageColorLiteral.b, uint8_t( ( color >> 16 ) & 0xFF ) );
+ MemWrite( &item->messageColorLiteral.r, uint8_t( ( color >> 16 ) & 0xFF ) );
TracyQueueCommit( messageColorLiteralThread );
}
void Profiler::MessageAppInfo( const char* txt, size_t size )
{
- assert( size < std::numeric_limits::max() );
+ assert( size < (std::numeric_limits::max)() );
auto ptr = (char*)tracy_malloc( size );
memcpy( ptr, txt, size );
TracyLfqPrepare( QueueType::MessageAppInfo );
@@ -3922,7 +3947,7 @@ void Profiler::MessageAppInfo( const char* txt, size_t size )
TracyLfqCommit;
}
-void Profiler::MemAlloc(const void* ptr, size_t size, bool secure)
+void Profiler::MemAlloc( const void* ptr, size_t size, bool secure )
{
if( secure && !ProfilerAvailable() ) return;
#ifdef TRACY_ON_DEMAND
@@ -4085,7 +4110,6 @@ void Profiler::SendCallstack( int depth )
#endif
}
-void Profiler::ParameterRegister( ParameterCallback cb ) { GetProfiler().m_paramCallback = cb; }
void Profiler::ParameterRegister( ParameterCallback cb, void* data )
{
auto& profiler = GetProfiler();
@@ -4301,486 +4325,4 @@ int64_t Profiler::GetTimeQpc()
}
-#if 0
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin( const struct ___tracy_source_location_data* srcloc, int active )
-{
- ___tracy_c_zone_context ctx;
-#ifdef TRACY_ON_DEMAND
- ctx.active = active && tracy::GetProfiler().IsConnected();
-#else
- ctx.active = active;
-#endif
- if( !ctx.active ) return ctx;
- const auto id = tracy::GetProfiler().GetNextZoneId();
- ctx.id = id;
-
-#ifndef TRACY_NO_VERIFY
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneValidation );
- tracy::MemWrite( &item->zoneValidation.id, id );
- TracyQueueCommitC( zoneValidationThread );
- }
-#endif
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneBegin );
- tracy::MemWrite( &item->zoneBegin.time, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc );
- TracyQueueCommitC( zoneBeginThread );
- }
- return ctx;
-}
-
-TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_callstack( const struct ___tracy_source_location_data* srcloc, int depth, int active )
-{
- ___tracy_c_zone_context ctx;
-#ifdef TRACY_ON_DEMAND
- ctx.active = active && tracy::GetProfiler().IsConnected();
-#else
- ctx.active = active;
-#endif
- if( !ctx.active ) return ctx;
- const auto id = tracy::GetProfiler().GetNextZoneId();
- ctx.id = id;
-
-#ifndef TRACY_NO_VERIFY
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneValidation );
- tracy::MemWrite( &item->zoneValidation.id, id );
- TracyQueueCommitC( zoneValidationThread );
- }
-#endif
- tracy::GetProfiler().SendCallstack( depth );
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneBeginCallstack );
- tracy::MemWrite( &item->zoneBegin.time, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc );
- TracyQueueCommitC( zoneBeginThread );
- }
- return ctx;
-}
-
-TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_alloc( uint64_t srcloc, int active )
-{
- ___tracy_c_zone_context ctx;
-#ifdef TRACY_ON_DEMAND
- ctx.active = active && tracy::GetProfiler().IsConnected();
-#else
- ctx.active = active;
-#endif
- if( !ctx.active )
- {
- tracy::tracy_free( (void*)srcloc );
- return ctx;
- }
- const auto id = tracy::GetProfiler().GetNextZoneId();
- ctx.id = id;
-
-#ifndef TRACY_NO_VERIFY
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneValidation );
- tracy::MemWrite( &item->zoneValidation.id, id );
- TracyQueueCommitC( zoneValidationThread );
- }
-#endif
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneBeginAllocSrcLoc );
- tracy::MemWrite( &item->zoneBegin.time, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->zoneBegin.srcloc, srcloc );
- TracyQueueCommitC( zoneBeginThread );
- }
- return ctx;
-}
-
-TRACY_API TracyCZoneCtx ___tracy_emit_zone_begin_alloc_callstack( uint64_t srcloc, int depth, int active )
-{
- ___tracy_c_zone_context ctx;
-#ifdef TRACY_ON_DEMAND
- ctx.active = active && tracy::GetProfiler().IsConnected();
-#else
- ctx.active = active;
-#endif
- if( !ctx.active )
- {
- tracy::tracy_free( (void*)srcloc );
- return ctx;
- }
- const auto id = tracy::GetProfiler().GetNextZoneId();
- ctx.id = id;
-
-#ifndef TRACY_NO_VERIFY
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneValidation );
- tracy::MemWrite( &item->zoneValidation.id, id );
- TracyQueueCommitC( zoneValidationThread );
- }
-#endif
- tracy::GetProfiler().SendCallstack( depth );
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneBeginAllocSrcLocCallstack );
- tracy::MemWrite( &item->zoneBegin.time, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->zoneBegin.srcloc, srcloc );
- TracyQueueCommitC( zoneBeginThread );
- }
- return ctx;
-}
-
-TRACY_API void ___tracy_emit_zone_end( TracyCZoneCtx ctx )
-{
- if( !ctx.active ) return;
-#ifndef TRACY_NO_VERIFY
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneValidation );
- tracy::MemWrite( &item->zoneValidation.id, ctx.id );
- TracyQueueCommitC( zoneValidationThread );
- }
-#endif
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneEnd );
- tracy::MemWrite( &item->zoneEnd.time, tracy::Profiler::GetTime() );
- TracyQueueCommitC( zoneEndThread );
- }
-}
-
-TRACY_API void ___tracy_emit_zone_text( TracyCZoneCtx ctx, const char* txt, size_t size )
-{
- assert( size < std::numeric_limits::max() );
- if( !ctx.active ) return;
- auto ptr = (char*)tracy::tracy_malloc( size );
- memcpy( ptr, txt, size );
-#ifndef TRACY_NO_VERIFY
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneValidation );
- tracy::MemWrite( &item->zoneValidation.id, ctx.id );
- TracyQueueCommitC( zoneValidationThread );
- }
-#endif
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneText );
- tracy::MemWrite( &item->zoneTextFat.text, (uint64_t)ptr );
- tracy::MemWrite( &item->zoneTextFat.size, (uint16_t)size );
- TracyQueueCommitC( zoneTextFatThread );
- }
-}
-
-TRACY_API void ___tracy_emit_zone_name( TracyCZoneCtx ctx, const char* txt, size_t size )
-{
- assert( size < std::numeric_limits::max() );
- if( !ctx.active ) return;
- auto ptr = (char*)tracy::tracy_malloc( size );
- memcpy( ptr, txt, size );
-#ifndef TRACY_NO_VERIFY
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneValidation );
- tracy::MemWrite( &item->zoneValidation.id, ctx.id );
- TracyQueueCommitC( zoneValidationThread );
- }
-#endif
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneName );
- tracy::MemWrite( &item->zoneTextFat.text, (uint64_t)ptr );
- tracy::MemWrite( &item->zoneTextFat.size, (uint16_t)size );
- TracyQueueCommitC( zoneTextFatThread );
- }
-}
-
-TRACY_API void ___tracy_emit_zone_color( TracyCZoneCtx ctx, uint32_t color ) {
- if( !ctx.active ) return;
-#ifndef TRACY_NO_VERIFY
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneValidation );
- tracy::MemWrite( &item->zoneValidation.id, ctx.id );
- TracyQueueCommitC( zoneValidationThread );
- }
-#endif
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneColor );
- tracy::MemWrite( &item->zoneColor.r, uint8_t( ( color ) & 0xFF ) );
- tracy::MemWrite( &item->zoneColor.g, uint8_t( ( color >> 8 ) & 0xFF ) );
- tracy::MemWrite( &item->zoneColor.b, uint8_t( ( color >> 16 ) & 0xFF ) );
- TracyQueueCommitC( zoneColorThread );
- }
-}
-
-TRACY_API void ___tracy_emit_zone_value( TracyCZoneCtx ctx, uint64_t value )
-{
- if( !ctx.active ) return;
-#ifndef TRACY_NO_VERIFY
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneValidation );
- tracy::MemWrite( &item->zoneValidation.id, ctx.id );
- TracyQueueCommitC( zoneValidationThread );
- }
-#endif
- {
- TracyQueuePrepareC( tracy::QueueType::ZoneValue );
- tracy::MemWrite( &item->zoneValue.value, value );
- TracyQueueCommitC( zoneValueThread );
- }
-}
-
-TRACY_API void ___tracy_emit_memory_alloc( const void* ptr, size_t size, int secure ) { tracy::Profiler::MemAlloc( ptr, size, secure != 0 ); }
-TRACY_API void ___tracy_emit_memory_alloc_callstack( const void* ptr, size_t size, int depth, int secure ) { tracy::Profiler::MemAllocCallstack( ptr, size, depth, secure != 0 ); }
-TRACY_API void ___tracy_emit_memory_free( const void* ptr, int secure ) { tracy::Profiler::MemFree( ptr, secure != 0 ); }
-TRACY_API void ___tracy_emit_memory_free_callstack( const void* ptr, int depth, int secure ) { tracy::Profiler::MemFreeCallstack( ptr, depth, secure != 0 ); }
-TRACY_API void ___tracy_emit_memory_alloc_named( const void* ptr, size_t size, int secure, const char* name ) { tracy::Profiler::MemAllocNamed( ptr, size, secure != 0, name ); }
-TRACY_API void ___tracy_emit_memory_alloc_callstack_named( const void* ptr, size_t size, int depth, int secure, const char* name ) { tracy::Profiler::MemAllocCallstackNamed( ptr, size, depth, secure != 0, name ); }
-TRACY_API void ___tracy_emit_memory_free_named( const void* ptr, int secure, const char* name ) { tracy::Profiler::MemFreeNamed( ptr, secure != 0, name ); }
-TRACY_API void ___tracy_emit_memory_free_callstack_named( const void* ptr, int depth, int secure, const char* name ) { tracy::Profiler::MemFreeCallstackNamed( ptr, depth, secure != 0, name ); }
-TRACY_API void ___tracy_emit_frame_mark( const char* name ) { tracy::Profiler::SendFrameMark( name ); }
-TRACY_API void ___tracy_emit_frame_mark_start( const char* name ) { tracy::Profiler::SendFrameMark( name, tracy::QueueType::FrameMarkMsgStart ); }
-TRACY_API void ___tracy_emit_frame_mark_end( const char* name ) { tracy::Profiler::SendFrameMark( name, tracy::QueueType::FrameMarkMsgEnd ); }
-TRACY_API void ___tracy_emit_frame_image( const void* image, uint16_t w, uint16_t h, uint8_t offset, int flip ) { tracy::Profiler::SendFrameImage( image, w, h, offset, flip ); }
-TRACY_API void ___tracy_emit_plot( const char* name, double val ) { tracy::Profiler::PlotData( name, val ); }
-TRACY_API void ___tracy_emit_message( const char* txt, size_t size, int callstack ) { tracy::Profiler::Message( txt, size, callstack ); }
-TRACY_API void ___tracy_emit_messageL( const char* txt, int callstack ) { tracy::Profiler::Message( txt, callstack ); }
-TRACY_API void ___tracy_emit_messageC( const char* txt, size_t size, uint32_t color, int callstack ) { tracy::Profiler::MessageColor( txt, size, color, callstack ); }
-TRACY_API void ___tracy_emit_messageLC( const char* txt, uint32_t color, int callstack ) { tracy::Profiler::MessageColor( txt, color, callstack ); }
-TRACY_API void ___tracy_emit_message_appinfo( const char* txt, size_t size ) { tracy::Profiler::MessageAppInfo( txt, size ); }
-
-TRACY_API uint64_t ___tracy_alloc_srcloc( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz ) {
- return tracy::Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz );
-}
-
-TRACY_API uint64_t ___tracy_alloc_srcloc_name( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz ) {
- return tracy::Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz );
-}
-
-TRACY_API void ___tracy_emit_gpu_zone_begin( const struct ___tracy_gpu_zone_begin_data data )
-{
- TracyLfqPrepareC( tracy::QueueType::GpuZoneBegin );
- tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuNewContext.thread, tracy::GetThreadHandle() );
- tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc );
- tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId );
- tracy::MemWrite( &item->gpuZoneBegin.context, data.context );
- TracyLfqCommitC;
-}
-
-TRACY_API void ___tracy_emit_gpu_zone_begin_callstack( const struct ___tracy_gpu_zone_begin_callstack_data data )
-{
- tracy::GetProfiler().SendCallstack( data.depth );
- TracyLfqPrepareC( tracy::QueueType::GpuZoneBeginCallstack );
- tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() );
- tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId );
- tracy::MemWrite( &item->gpuZoneBegin.context, data.context );
- tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc );
- TracyLfqCommitC;
-}
-
-TRACY_API void ___tracy_emit_gpu_zone_begin_alloc( const struct ___tracy_gpu_zone_begin_data data )
-{
- TracyLfqPrepareC( tracy::QueueType::GpuZoneBeginAllocSrcLoc );
- tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuNewContext.thread, tracy::GetThreadHandle() );
- tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc );
- tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId );
- tracy::MemWrite( &item->gpuZoneBegin.context, data.context );
- TracyLfqCommitC;
-}
-
-TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_callstack( const struct ___tracy_gpu_zone_begin_callstack_data data )
-{
- tracy::GetProfiler().SendCallstack( data.depth );
- TracyLfqPrepareC( tracy::QueueType::GpuZoneBeginAllocSrcLocCallstack );
- tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuNewContext.thread, tracy::GetThreadHandle() );
- tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc );
- tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId );
- tracy::MemWrite( &item->gpuZoneBegin.context, data.context );
- TracyLfqCommitC;
-}
-
-TRACY_API void ___tracy_emit_gpu_time( const struct ___tracy_gpu_time_data data )
-{
- TracyLfqPrepareC( tracy::QueueType::GpuTime );
- tracy::MemWrite( &item->gpuTime.gpuTime, data.gpuTime );
- tracy::MemWrite( &item->gpuTime.queryId, data.queryId );
- tracy::MemWrite( &item->gpuTime.context, data.context );
- TracyLfqCommitC;
-}
-
-TRACY_API void ___tracy_emit_gpu_zone_end( const struct ___tracy_gpu_zone_end_data data )
-{
- TracyLfqPrepareC( tracy::QueueType::GpuZoneEnd );
- tracy::MemWrite( &item->gpuZoneEnd.cpuTime, tracy::Profiler::GetTime() );
- memset( &item->gpuZoneEnd.thread, 0, sizeof( item->gpuZoneEnd.thread ) );
- tracy::MemWrite( &item->gpuZoneEnd.queryId, data.queryId );
- tracy::MemWrite( &item->gpuZoneEnd.context, data.context );
- TracyLfqCommitC;
-}
-
-TRACY_API void ___tracy_emit_gpu_new_context( ___tracy_gpu_new_context_data data )
-{
- TracyLfqPrepareC( tracy::QueueType::GpuNewContext );
- tracy::MemWrite( &item->gpuNewContext.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuNewContext.thread, tracy::GetThreadHandle() );
- tracy::MemWrite( &item->gpuNewContext.gpuTime, data.gpuTime );
- tracy::MemWrite( &item->gpuNewContext.period, data.period );
- tracy::MemWrite( &item->gpuNewContext.context, data.context );
- tracy::MemWrite( &item->gpuNewContext.flags, data.flags );
- tracy::MemWrite( &item->gpuNewContext.type, data.type );
- TracyLfqCommitC;
-}
-
-TRACY_API void ___tracy_emit_gpu_context_name( const struct ___tracy_gpu_context_name_data data )
-{
- auto ptr = (char*)tracy::tracy_malloc( data.len );
- memcpy( ptr, data.name, data.len );
-
- TracyLfqPrepareC( tracy::QueueType::GpuContextName );
- tracy::MemWrite( &item->gpuContextNameFat.context, data.context );
- tracy::MemWrite( &item->gpuContextNameFat.ptr, (uint64_t)ptr );
- tracy::MemWrite( &item->gpuContextNameFat.size, data.len );
- TracyLfqCommitC;
-}
-
-TRACY_API void ___tracy_emit_gpu_calibration( const struct ___tracy_gpu_calibration_data data )
-{
- TracyLfqPrepareC( tracy::QueueType::GpuCalibration );
- tracy::MemWrite( &item->gpuCalibration.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuCalibration.gpuTime, data.gpuTime );
- tracy::MemWrite( &item->gpuCalibration.cpuDelta, data.cpuDelta );
- tracy::MemWrite( &item->gpuCalibration.context, data.context );
- TracyLfqCommitC;
-}
-
-TRACY_API void ___tracy_emit_gpu_zone_begin_serial( const struct ___tracy_gpu_zone_begin_data data )
-{
- auto item = tracy::Profiler::QueueSerial();
- tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginSerial );
- tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc );
- tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() );
- tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId );
- tracy::MemWrite( &item->gpuZoneBegin.context, data.context );
- tracy::Profiler::QueueSerialFinish();
-}
-
-TRACY_API void ___tracy_emit_gpu_zone_begin_callstack_serial( const struct ___tracy_gpu_zone_begin_callstack_data data )
-{
- auto item = tracy::Profiler::QueueSerialCallstack( tracy::Callstack( data.depth ) );
- tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginCallstackSerial );
- tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc );
- tracy::MemWrite( &item->gpuZoneBegin.thread, tracy::GetThreadHandle() );
- tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId );
- tracy::MemWrite( &item->gpuZoneBegin.context, data.context );
- tracy::Profiler::QueueSerialFinish();
-}
-
-TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_serial( const struct ___tracy_gpu_zone_begin_data data )
-{
- auto item = tracy::Profiler::QueueSerial();
- tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginAllocSrcLocSerial );
- tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuNewContext.thread, tracy::GetThreadHandle() );
- tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc );
- tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId );
- tracy::MemWrite( &item->gpuZoneBegin.context, data.context );
- tracy::Profiler::QueueSerialFinish();
-}
-
-TRACY_API void ___tracy_emit_gpu_zone_begin_alloc_callstack_serial( const struct ___tracy_gpu_zone_begin_callstack_data data )
-{
- auto item = tracy::Profiler::QueueSerialCallstack( tracy::Callstack( data.depth ) );
- tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneBeginAllocSrcLocCallstackSerial );
- tracy::MemWrite( &item->gpuZoneBegin.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuNewContext.thread, tracy::GetThreadHandle() );
- tracy::MemWrite( &item->gpuZoneBegin.srcloc, data.srcloc );
- tracy::MemWrite( &item->gpuZoneBegin.queryId, data.queryId );
- tracy::MemWrite( &item->gpuZoneBegin.context, data.context );
- tracy::Profiler::QueueSerialFinish();
-}
-
-TRACY_API void ___tracy_emit_gpu_time_serial( const struct ___tracy_gpu_time_data data )
-{
- auto item = tracy::Profiler::QueueSerial();
- tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuTime );
- tracy::MemWrite( &item->gpuTime.gpuTime, data.gpuTime );
- tracy::MemWrite( &item->gpuTime.queryId, data.queryId );
- tracy::MemWrite( &item->gpuTime.context, data.context );
- tracy::Profiler::QueueSerialFinish();
-}
-
-TRACY_API void ___tracy_emit_gpu_zone_end_serial( const struct ___tracy_gpu_zone_end_data data )
-{
- auto item = tracy::Profiler::QueueSerial();
- tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuZoneEndSerial );
- tracy::MemWrite( &item->gpuZoneEnd.cpuTime, tracy::Profiler::GetTime() );
- memset( &item->gpuZoneEnd.thread, 0, sizeof( item->gpuZoneEnd.thread ) );
- tracy::MemWrite( &item->gpuZoneEnd.queryId, data.queryId );
- tracy::MemWrite( &item->gpuZoneEnd.context, data.context );
- tracy::Profiler::QueueSerialFinish();
-}
-
-TRACY_API void ___tracy_emit_gpu_new_context_serial( ___tracy_gpu_new_context_data data )
-{
- auto item = tracy::Profiler::QueueSerial();
- tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuNewContext );
- tracy::MemWrite( &item->gpuNewContext.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuNewContext.thread, tracy::GetThreadHandle() );
- tracy::MemWrite( &item->gpuNewContext.gpuTime, data.gpuTime );
- tracy::MemWrite( &item->gpuNewContext.period, data.period );
- tracy::MemWrite( &item->gpuNewContext.context, data.context );
- tracy::MemWrite( &item->gpuNewContext.flags, data.flags );
- tracy::MemWrite( &item->gpuNewContext.type, data.type );
- tracy::Profiler::QueueSerialFinish();
-}
-
-TRACY_API void ___tracy_emit_gpu_context_name_serial( const struct ___tracy_gpu_context_name_data data )
-{
- auto ptr = (char*)tracy::tracy_malloc( data.len );
- memcpy( ptr, data.name, data.len );
-
- auto item = tracy::Profiler::QueueSerial();
- tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuContextName );
- tracy::MemWrite( &item->gpuContextNameFat.context, data.context );
- tracy::MemWrite( &item->gpuContextNameFat.ptr, (uint64_t)ptr );
- tracy::MemWrite( &item->gpuContextNameFat.size, data.len );
- tracy::Profiler::QueueSerialFinish();
-}
-
-TRACY_API void ___tracy_emit_gpu_calibration_serial( const struct ___tracy_gpu_calibration_data data )
-{
- auto item = tracy::Profiler::QueueSerial();
- tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuCalibration );
- tracy::MemWrite( &item->gpuCalibration.cpuTime, tracy::Profiler::GetTime() );
- tracy::MemWrite( &item->gpuCalibration.gpuTime, data.gpuTime );
- tracy::MemWrite( &item->gpuCalibration.cpuDelta, data.cpuDelta );
- tracy::MemWrite( &item->gpuCalibration.context, data.context );
- tracy::Profiler::QueueSerialFinish();
-}
-
-TRACY_API int ___tracy_connected( void )
-{
- return tracy::GetProfiler().IsConnected();
-}
-
-#ifdef TRACY_FIBERS
-TRACY_API void ___tracy_fiber_enter( const char* fiber ){ tracy::Profiler::EnterFiber( fiber ); }
-TRACY_API void ___tracy_fiber_leave( void ){ tracy::Profiler::LeaveFiber(); }
-#endif
-
-# ifdef TRACY_MANUAL_LIFETIME
-TRACY_API void ___tracy_startup_profiler( void )
-{
- tracy::StartupProfiler();
-}
-
-TRACY_API void ___tracy_shutdown_profiler( void )
-{
- tracy::ShutdownProfiler();
-}
-# endif
-
-#ifdef __cplusplus
-}
-#endif
-#endif
-
#endif
diff --git a/Source/ThirdParty/tracy/client/TracyProfiler.hpp b/Source/ThirdParty/tracy/client/TracyProfiler.hpp
index 99ae63e4f..8892fb14f 100644
--- a/Source/ThirdParty/tracy/client/TracyProfiler.hpp
+++ b/Source/ThirdParty/tracy/client/TracyProfiler.hpp
@@ -10,6 +10,7 @@
#include "tracy_concurrentqueue.h"
#include "tracy_SPSCQueue.h"
#include "TracyCallstack.hpp"
+#include "TracySysPower.hpp"
#include "TracySysTime.hpp"
#include "TracyFastVector.hpp"
#include "../common/TracyQueue.hpp"
@@ -190,7 +191,22 @@ public:
if( HardwareSupportsInvariantTSC() )
{
uint64_t rax, rdx;
+#ifdef TRACY_PATCHABLE_NOPSLEDS
+ // Some external tooling (such as rr) wants to patch our rdtsc and replace it by a
+ // branch to control the external input seen by a program. This kind of patching is
+ // not generally possible depending on the surrounding code and can lead to significant
+ // slowdowns if the compiler generated unlucky code and rr and tracy are used together.
+ // To avoid this, use the rr-safe `nopl 0(%rax, %rax, 1); rdtsc` instruction sequence,
+ // which rr promises will be patchable independent of the surrounding code.
+ asm volatile (
+ // This is nopl 0(%rax, %rax, 1), but assemblers are inconsistent about whether
+ // they emit that as a 4 or 5 byte sequence and we need to be guaranteed to use
+ // the 5 byte one.
+ ".byte 0x0f, 0x1f, 0x44, 0x00, 0x00\n\t"
+ "rdtsc" : "=a" (rax), "=d" (rdx) );
+#else
asm volatile ( "rdtsc" : "=a" (rax), "=d" (rdx) );
+#endif
return (int64_t)(( rdx << 32 ) + rax);
}
# else
@@ -240,6 +256,30 @@ public:
p.m_serialLock.unlock();
}
+ static void SendFrameMark( const char* name );
+ static void SendFrameMark( const char* name, QueueType type );
+ static void SendFrameImage( const void* image, uint16_t w, uint16_t h, uint8_t offset, bool flip );
+ static void PlotData( const char* name, int64_t val );
+ static void PlotData( const char* name, float val );
+ static void PlotData( const char* name, double val );
+ static void ConfigurePlot( const char* name, PlotFormatType type, bool step, bool fill, uint32_t color );
+ static void Message( const char* txt, size_t size, int callstack );
+ static void Message( const char* txt, int callstack );
+ static void MessageColor( const char* txt, size_t size, uint32_t color, int callstack );
+ static void MessageColor( const char* txt, uint32_t color, int callstack );
+ static void MessageAppInfo( const char* txt, size_t size );
+ static void MemAlloc( const void* ptr, size_t size, bool secure );
+ static void MemFree( const void* ptr, bool secure );
+ static void MemAllocCallstack( const void* ptr, size_t size, int depth, bool secure );
+ static void MemFreeCallstack( const void* ptr, int depth, bool secure );
+ static void MemAllocNamed( const void* ptr, size_t size, bool secure, const char* name );
+ static void MemFreeNamed( const void* ptr, bool secure, const char* name );
+ static void MemAllocCallstackNamed( const void* ptr, size_t size, int depth, bool secure, const char* name );
+ static void MemFreeCallstackNamed( const void* ptr, int depth, bool secure, const char* name );
+ static void SendCallstack( int depth );
+ static void ParameterRegister( ParameterCallback cb, void* data );
+ static void ParameterSetup( uint32_t idx, const char* name, bool isBool, int32_t val );
+
static tracy_force_inline void SourceCallbackRegister( SourceContentsCallback cb, void* data )
{
auto& profiler = GetProfiler();
@@ -264,31 +304,6 @@ public:
}
#endif
- static void SendFrameMark( const char* name );
- static void SendFrameMark( const char* name, QueueType type );
- static void SendFrameImage( const void* image, uint16_t w, uint16_t h, uint8_t offset, bool flip );
- static void PlotData( const char* name, int64_t val );
- static void PlotData( const char* name, float val );
- static void PlotData( const char* name, double val );
- static void ConfigurePlot( const char* name, PlotFormatType type, bool step, bool fill, uint32_t color );
- static void Message( const char* txt, size_t size, int callstack );
- static void Message( const char* txt, int callstack );
- static void MessageColor( const char* txt, size_t size, uint32_t color, int callstack );
- static void MessageColor( const char* txt, uint32_t color, int callstack );
- static void MessageAppInfo( const char* txt, size_t size );
- static void MemAlloc( const void* ptr, size_t size, bool secure );
- static void MemFree( const void* ptr, bool secure );
- static void MemAllocCallstack( const void* ptr, size_t size, int depth, bool secure );
- static void MemFreeCallstack( const void* ptr, int depth, bool secure );
- static void MemAllocNamed( const void* ptr, size_t size, bool secure, const char* name );
- static void MemFreeNamed( const void* ptr, bool secure, const char* name );
- static void MemAllocCallstackNamed( const void* ptr, size_t size, int depth, bool secure, const char* name );
- static void MemFreeCallstackNamed( const void* ptr, int depth, bool secure, const char* name );
- static void SendCallstack( int depth );
- static void ParameterRegister( ParameterCallback cb );
- static void ParameterRegister( ParameterCallback cb, void* data );
- static void ParameterSetup( uint32_t idx, const char* name, bool isBool, int32_t val );
-
void SendCallstack( int depth, const char* skipBefore );
static void CutCallstack( void* callstack, const char* skipBefore );
@@ -299,6 +314,13 @@ public:
return m_isConnected.load( std::memory_order_acquire );
}
+ tracy_force_inline void SetProgramName( const char* name )
+ {
+ m_programNameLock.lock();
+ m_programName = name;
+ m_programNameLock.unlock();
+ }
+
#ifdef TRACY_ON_DEMAND
tracy_force_inline uint64_t ConnectionId() const
{
@@ -347,13 +369,13 @@ public:
static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz )
{
- return AllocSourceLocation( line, source, sourceSz, function, functionSz, (const char*)nullptr, 0 );
+ return AllocSourceLocation( line, source, sourceSz, function, functionSz, nullptr, 0 );
}
static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz )
{
const auto sz32 = uint32_t( 2 + 4 + 4 + functionSz + 1 + sourceSz + 1 + nameSz );
- assert( sz32 <= std::numeric_limits::max() );
+ assert( sz32 <= (std::numeric_limits::max)() );
const auto sz = uint16_t( sz32 );
auto ptr = (char*)tracy_malloc( sz );
memcpy( ptr, &sz, 2 );
@@ -370,28 +392,6 @@ public:
return uint64_t( ptr );
}
- static tracy_force_inline uint64_t AllocSourceLocation( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const Char* name, size_t nameSz )
- {
- const auto sz32 = uint32_t( 2 + 4 + 4 + functionSz + 1 + sourceSz + 1 + nameSz );
- assert( sz32 <= std::numeric_limits::max() );
- const auto sz = uint16_t( sz32 );
- auto ptr = (char*)tracy_malloc( sz );
- memcpy( ptr, &sz, 2 );
- memset( ptr + 2, 0, 4 );
- memcpy( ptr + 6, &line, 4 );
- memcpy( ptr + 10, function, functionSz );
- ptr[10 + functionSz] = '\0';
- memcpy( ptr + 10 + functionSz + 1, source, sourceSz );
- ptr[10 + functionSz + 1 + sourceSz] = '\0';
- if( nameSz != 0 )
- {
- char* dst = ptr + 10 + functionSz + 1 + sourceSz + 1;
- for ( size_t i = 0; i < nameSz; i++)
- dst[i] = (char)name[i];
- }
- return uint64_t( ptr );
- }
-
private:
enum class DequeueStatus { DataDequeued, ConnectionLost, QueueEmpty };
enum class ThreadCtxStatus { Same, Changed, ConnectionLost };
@@ -586,6 +586,10 @@ private:
void ProcessSysTime() {}
#endif
+#ifdef TRACY_HAS_SYSPOWER
+ SysPower m_sysPower;
+#endif
+
ParameterCallback m_paramCallback;
void* m_paramCallbackData;
SourceContentsCallback m_sourceCallback;
@@ -604,6 +608,9 @@ private:
} m_prevSignal;
#endif
bool m_crashHandlerInstalled;
+
+ const char* m_programName;
+ TracyMutex m_programNameLock;
};
}
diff --git a/Source/ThirdParty/tracy/client/TracyScoped.hpp b/Source/ThirdParty/tracy/client/TracyScoped.hpp
index 1e5b6c809..bb916aa57 100644
--- a/Source/ThirdParty/tracy/client/TracyScoped.hpp
+++ b/Source/ThirdParty/tracy/client/TracyScoped.hpp
@@ -20,19 +20,7 @@ void ScopedZone::Begin(const SourceLocationData* srcloc)
TracyLfqPrepare( QueueType::ZoneBegin );
MemWrite( &item->zoneBegin.time, Profiler::GetTime() );
MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc );
- TracyLfqCommit;
-}
-
-void ScopedZone::Begin(uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const Char* name, size_t nameSz)
-{
-#ifdef TRACY_ON_DEMAND
- if (!GetProfiler().IsConnected()) return;
-#endif
- TracyLfqPrepare( QueueType::ZoneBeginAllocSrcLoc );
- const auto srcloc = Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz );
- MemWrite( &item->zoneBegin.time, Profiler::GetTime() );
- MemWrite( &item->zoneBegin.srcloc, srcloc );
- TracyLfqCommit;
+ TracyQueueCommit( zoneBeginThread );
}
void ScopedZone::End()
@@ -42,7 +30,7 @@ void ScopedZone::End()
#endif
TracyLfqPrepare( QueueType::ZoneEnd );
MemWrite( &item->zoneEnd.time, Profiler::GetTime() );
- TracyLfqCommit;
+ TracyQueueCommit( zoneEndThread );
}
ScopedZone::ScopedZone( const SourceLocationData* srcloc, bool is_active )
@@ -132,7 +120,7 @@ ScopedZone::~ScopedZone()
void ScopedZone::Text( const char* txt, size_t size )
{
- assert( size < std::numeric_limits::max() );
+ assert( size < (std::numeric_limits::max)() );
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
if( GetProfiler().ConnectionId() != m_connectionId ) return;
@@ -147,7 +135,7 @@ void ScopedZone::Text( const char* txt, size_t size )
void ScopedZone::Text( const Char* txt, size_t size )
{
- assert( size < std::numeric_limits::max() );
+ assert( size < (std::numeric_limits::max)() );
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
if( GetProfiler().ConnectionId() != m_connectionId ) return;
@@ -163,7 +151,7 @@ void ScopedZone::Text( const Char* txt, size_t size )
void ScopedZone::Name( const char* txt, size_t size )
{
- assert( size < std::numeric_limits::max() );
+ assert( size < (std::numeric_limits::max)() );
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
if( GetProfiler().ConnectionId() != m_connectionId ) return;
@@ -178,7 +166,7 @@ void ScopedZone::Name( const char* txt, size_t size )
void ScopedZone::Name( const Char* txt, size_t size )
{
- assert( size < std::numeric_limits::max() );
+ assert( size < (std::numeric_limits::max)() );
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
if( GetProfiler().ConnectionId() != m_connectionId ) return;
@@ -199,9 +187,9 @@ void ScopedZone::Color( uint32_t color )
if( GetProfiler().ConnectionId() != m_connectionId ) return;
#endif
TracyQueuePrepare( QueueType::ZoneColor );
- MemWrite( &item->zoneColor.r, uint8_t( ( color ) & 0xFF ) );
+ MemWrite( &item->zoneColor.b, uint8_t( ( color ) & 0xFF ) );
MemWrite( &item->zoneColor.g, uint8_t( ( color >> 8 ) & 0xFF ) );
- MemWrite( &item->zoneColor.b, uint8_t( ( color >> 16 ) & 0xFF ) );
+ MemWrite( &item->zoneColor.r, uint8_t( ( color >> 16 ) & 0xFF ) );
TracyQueueCommit( zoneColorThread );
}
diff --git a/Source/ThirdParty/tracy/client/TracySysPower.cpp b/Source/ThirdParty/tracy/client/TracySysPower.cpp
new file mode 100644
index 000000000..bd5939da2
--- /dev/null
+++ b/Source/ThirdParty/tracy/client/TracySysPower.cpp
@@ -0,0 +1,164 @@
+#include "TracySysPower.hpp"
+
+#ifdef TRACY_HAS_SYSPOWER
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "TracyDebug.hpp"
+#include "TracyProfiler.hpp"
+#include "../common/TracyAlloc.hpp"
+
+namespace tracy
+{
+
+SysPower::SysPower()
+ : m_domains( 4 )
+ , m_lastTime( 0 )
+{
+ ScanDirectory( "/sys/devices/virtual/powercap/intel-rapl", -1 );
+}
+
+SysPower::~SysPower()
+{
+ for( auto& v : m_domains )
+ {
+ fclose( v.handle );
+ // Do not release v.name, as it may be still needed
+ }
+}
+
+void SysPower::Tick()
+{
+ auto t = std::chrono::high_resolution_clock::now().time_since_epoch().count();
+ if( t - m_lastTime > 10000000 ) // 10 ms
+ {
+ m_lastTime = t;
+ for( auto& v : m_domains )
+ {
+ char tmp[32];
+ if( fread( tmp, 1, 32, v.handle ) > 0 )
+ {
+ rewind( v.handle );
+ auto p = (uint64_t)atoll( tmp );
+ uint64_t delta;
+ if( p >= v.value )
+ {
+ delta = p - v.value;
+ }
+ else
+ {
+ delta = v.overflow - v.value + p;
+ }
+ v.value = p;
+
+ TracyLfqPrepare( QueueType::SysPowerReport );
+ MemWrite( &item->sysPower.time, Profiler::GetTime() );
+ MemWrite( &item->sysPower.delta, delta );
+ MemWrite( &item->sysPower.name, (uint64_t)v.name );
+ TracyLfqCommit;
+ }
+ }
+ }
+}
+
+void SysPower::ScanDirectory( const char* path, int parent )
+{
+ DIR* dir = opendir( path );
+ if( !dir ) return;
+ struct dirent* ent;
+ uint64_t maxRange = 0;
+ char* name = nullptr;
+ FILE* handle = nullptr;
+ while( ( ent = readdir( dir ) ) )
+ {
+ if( ent->d_type == DT_REG )
+ {
+ if( strcmp( ent->d_name, "max_energy_range_uj" ) == 0 )
+ {
+ char tmp[PATH_MAX];
+ snprintf( tmp, PATH_MAX, "%s/max_energy_range_uj", path );
+ FILE* f = fopen( tmp, "r" );
+ if( f )
+ {
+ fscanf( f, "%" PRIu64, &maxRange );
+ fclose( f );
+ }
+ }
+ else if( strcmp( ent->d_name, "name" ) == 0 )
+ {
+ char tmp[PATH_MAX];
+ snprintf( tmp, PATH_MAX, "%s/name", path );
+ FILE* f = fopen( tmp, "r" );
+ if( f )
+ {
+ char ntmp[128];
+ if( fgets( ntmp, 128, f ) )
+ {
+ // Last character is newline, skip it
+ const auto sz = strlen( ntmp ) - 1;
+ if( parent < 0 )
+ {
+ name = (char*)tracy_malloc( sz + 1 );
+ memcpy( name, ntmp, sz );
+ name[sz] = '\0';
+ }
+ else
+ {
+ const auto p = m_domains[parent];
+ const auto psz = strlen( p.name );
+ name = (char*)tracy_malloc( psz + sz + 2 );
+ memcpy( name, p.name, psz );
+ name[psz] = ':';
+ memcpy( name+psz+1, ntmp, sz );
+ name[psz+sz+1] = '\0';
+ }
+ }
+ fclose( f );
+ }
+ }
+ else if( strcmp( ent->d_name, "energy_uj" ) == 0 )
+ {
+ char tmp[PATH_MAX];
+ snprintf( tmp, PATH_MAX, "%s/energy_uj", path );
+ handle = fopen( tmp, "r" );
+ }
+ }
+ if( name && handle && maxRange > 0 ) break;
+ }
+ if( name && handle && maxRange > 0 )
+ {
+ parent = (int)m_domains.size();
+ Domain* domain = m_domains.push_next();
+ domain->value = 0;
+ domain->overflow = maxRange;
+ domain->handle = handle;
+ domain->name = name;
+ TracyDebug( "Power domain id %i, %s found at %s\n", parent, name, path );
+ }
+ else
+ {
+ if( name ) tracy_free( name );
+ if( handle ) fclose( handle );
+ }
+
+ rewinddir( dir );
+ while( ( ent = readdir( dir ) ) )
+ {
+ if( ent->d_type == DT_DIR && strncmp( ent->d_name, "intel-rapl:", 11 ) == 0 )
+ {
+ char tmp[PATH_MAX];
+ snprintf( tmp, PATH_MAX, "%s/%s", path, ent->d_name );
+ ScanDirectory( tmp, parent );
+ }
+ }
+ closedir( dir );
+}
+
+}
+
+#endif
diff --git a/Source/ThirdParty/tracy/client/TracySysPower.hpp b/Source/ThirdParty/tracy/client/TracySysPower.hpp
new file mode 100644
index 000000000..210123bce
--- /dev/null
+++ b/Source/ThirdParty/tracy/client/TracySysPower.hpp
@@ -0,0 +1,44 @@
+#ifndef __TRACYSYSPOWER_HPP__
+#define __TRACYSYSPOWER_HPP__
+
+#if defined __linux__
+# define TRACY_HAS_SYSPOWER
+#endif
+
+#ifdef TRACY_HAS_SYSPOWER
+
+#include
+#include
+
+#include "TracyFastVector.hpp"
+
+namespace tracy
+{
+
+class SysPower
+{
+ struct Domain
+ {
+ uint64_t value;
+ uint64_t overflow;
+ FILE* handle;
+ const char* name;
+ };
+
+public:
+ SysPower();
+ ~SysPower();
+
+ void Tick();
+
+private:
+ void ScanDirectory( const char* path, int parent );
+
+ FastVector m_domains;
+ uint64_t m_lastTime;
+};
+
+}
+#endif
+
+#endif
diff --git a/Source/ThirdParty/tracy/client/TracySysTrace.cpp b/Source/ThirdParty/tracy/client/TracySysTrace.cpp
index 23b1020a5..af0641fef 100644
--- a/Source/ThirdParty/tracy/client/TracySysTrace.cpp
+++ b/Source/ThirdParty/tracy/client/TracySysTrace.cpp
@@ -409,6 +409,7 @@ bool SysTraceStart( int64_t& samplingPeriod )
return false;
}
+#ifndef TRACY_NO_SAMPLING
if( isOs64Bit )
{
CLASSIC_EVENT_ID stackId[2] = {};
@@ -423,6 +424,7 @@ bool SysTraceStart( int64_t& samplingPeriod )
return false;
}
}
+#endif
#ifdef UNICODE
WCHAR KernelLoggerName[sizeof( KERNEL_LOGGER_NAME )];
@@ -768,6 +770,13 @@ bool SysTraceStart( int64_t& samplingPeriod )
TracyDebug( "sched_wakeup id: %i\n", wakeupId );
TracyDebug( "drm_vblank_event id: %i\n", vsyncId );
+#ifdef TRACY_NO_SAMPLING
+ const bool noSoftwareSampling = true;
+#else
+ const char* noSoftwareSamplingEnv = GetEnvVar( "TRACY_NO_SAMPLING" );
+ const bool noSoftwareSampling = noSoftwareSamplingEnv && noSoftwareSamplingEnv[0] == '1';
+#endif
+
#ifdef TRACY_NO_SAMPLE_RETIREMENT
const bool noRetirement = true;
#else
@@ -837,28 +846,31 @@ bool SysTraceStart( int64_t& samplingPeriod )
pe.clockid = CLOCK_MONOTONIC_RAW;
#endif
- TracyDebug( "Setup software sampling\n" );
- ProbePreciseIp( pe, currentPid );
- for( int i=0; i
static inline bool circular_less_than(T a, T b)
{
static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types");
- return static_cast(a - b) > (static_cast