From 475453aa601d4f8ced173ae4a05c913e21ac1c0f Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Tue, 28 Nov 2023 00:06:28 +0100 Subject: [PATCH 01/72] add load scene add. to context menus --- Source/Editor/Content/Proxy/SceneProxy.cs | 7 ++++++ .../Windows/SceneTreeWindow.ContextMenu.cs | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Source/Editor/Content/Proxy/SceneProxy.cs b/Source/Editor/Content/Proxy/SceneProxy.cs index 004c2aed7..a232c672b 100644 --- a/Source/Editor/Content/Proxy/SceneProxy.cs +++ b/Source/Editor/Content/Proxy/SceneProxy.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Windows; using FlaxEngine; @@ -68,5 +69,11 @@ namespace FlaxEditor.Content { return new SceneItem(path, id); } + + /// + public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item) + { + menu.AddButton("Open additionally", () => { Editor.Instance.Scene.OpenScene(((SceneItem)item).ID, true); }); + } } } diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index a99c3ac7d..3e078bcb8 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -1,6 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEngine; @@ -148,6 +150,26 @@ namespace FlaxEditor.Windows // Spawning actors options + if (!hasSthSelected) + { + var allScenes = FlaxEngine.Content.GetAllAssetsByType(typeof(SceneAsset)); + var loadedSceneIds = Editor.Instance.Scene.Root.ChildNodes.Select(node => node.ID).ToList(); + var unloadedScenes = allScenes.Where(sceneId => !loadedSceneIds.Contains(sceneId)).ToList(); + if (unloadedScenes.Count > 0) + { + contextMenu.AddSeparator(); + var childCM = contextMenu.GetOrAddChildMenu("Open Scene additionally"); + foreach (var sceneGuid in unloadedScenes.Where(sceneGuid => !Level.FindScene(sceneGuid))) + { + if (FlaxEngine.Content.GetAssetInfo(sceneGuid, out var unloadedScene)) + { + var splitPath = unloadedScene.Path.Split('/'); + childCM.ContextMenu.AddButton(splitPath[^1], () => { Editor.Instance.Scene.OpenScene(sceneGuid, true); }); + } + } + } + } + contextMenu.AddSeparator(); // go through each actor and add it to the context menu if it has the ActorContextMenu attribute From a06a0798041e22ee6f48a9b5195f33be75982190 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Tue, 28 Nov 2023 00:37:29 +0100 Subject: [PATCH 02/72] change submenu name to the shorter and less complicated "Add Scene" --- Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 3e078bcb8..6261c0c07 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -158,13 +158,16 @@ namespace FlaxEditor.Windows if (unloadedScenes.Count > 0) { contextMenu.AddSeparator(); - var childCM = contextMenu.GetOrAddChildMenu("Open Scene additionally"); + var childCM = contextMenu.GetOrAddChildMenu("Add Scene"); foreach (var sceneGuid in unloadedScenes.Where(sceneGuid => !Level.FindScene(sceneGuid))) { if (FlaxEngine.Content.GetAssetInfo(sceneGuid, out var unloadedScene)) { var splitPath = unloadedScene.Path.Split('/'); - childCM.ContextMenu.AddButton(splitPath[^1], () => { Editor.Instance.Scene.OpenScene(sceneGuid, true); }); + var sceneName = splitPath[^1]; + if (splitPath[^1].EndsWith(".scene")) + sceneName = sceneName[..^6]; + childCM.ContextMenu.AddButton(sceneName, () => { Editor.Instance.Scene.OpenScene(sceneGuid, true); }); } } } From 84f3d509252bac9d351376c3bb0e2d7e39092846 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Tue, 28 Nov 2023 00:43:55 +0100 Subject: [PATCH 03/72] moved a comment line back to the suitable place --- Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 6261c0c07..ea4714df8 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -148,7 +148,7 @@ namespace FlaxEditor.Windows contextMenu.AddButton("Break Prefab Link", Editor.Prefabs.BreakLinks); } - // Spawning actors options + // Load additional scenes option if (!hasSthSelected) { @@ -173,6 +173,8 @@ namespace FlaxEditor.Windows } } + // Spawning actors options + contextMenu.AddSeparator(); // go through each actor and add it to the context menu if it has the ActorContextMenu attribute From a3f1dc269474eaee3de64334d9ddf6d0319fcf76 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Tue, 28 Nov 2023 00:50:44 +0100 Subject: [PATCH 04/72] removed unnecessary check --- Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index ea4714df8..8b5318190 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -159,7 +159,7 @@ namespace FlaxEditor.Windows { contextMenu.AddSeparator(); var childCM = contextMenu.GetOrAddChildMenu("Add Scene"); - foreach (var sceneGuid in unloadedScenes.Where(sceneGuid => !Level.FindScene(sceneGuid))) + foreach (var sceneGuid in unloadedScenes) { if (FlaxEngine.Content.GetAssetInfo(sceneGuid, out var unloadedScene)) { From 78d9262b0515588769276aea142d8198fa626b81 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Fri, 1 Dec 2023 21:25:00 +0100 Subject: [PATCH 05/72] skip WM for non-regular windows and add mouse tracking --- .../Engine/Platform/Linux/LinuxPlatform.cpp | 33 ++++++++++++++++--- Source/Engine/Platform/Linux/LinuxPlatform.h | 2 ++ Source/Engine/Platform/Linux/LinuxWindow.cpp | 19 ++++++++--- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index a7634710f..83c3e3ae6 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -94,6 +94,7 @@ X11::XcursorImage* CursorsImg[(int32)CursorType::MAX]; Dictionary KeyNameMap; Array KeyCodeMap; Delegate LinuxPlatform::xEventRecieved; +const Window* mouseTrackingWindow; // Message boxes configuration #define LINUX_DIALOG_MIN_BUTTON_WIDTH 64 @@ -1917,6 +1918,8 @@ bool LinuxPlatform::Init() if (PlatformBase::Init()) return true; + mouseTrackingWindow = nullptr; + char fileNameBuffer[1024]; // Init timing @@ -2260,6 +2263,17 @@ bool LinuxPlatform::Init() return false; } +void LinuxPlatform::StartTrackingMouse(const Window* window) +{ + mouseTrackingWindow = window; +} + +void LinuxPlatform::EndTrackingMouse(const Window* window) +{ + if (mouseTrackingWindow == window) + mouseTrackingWindow = nullptr; +} + void LinuxPlatform::BeforeRun() { } @@ -2398,7 +2412,7 @@ void LinuxPlatform::Tick() // Update input context focus X11::XSetICFocus(IC); window = WindowsManager::GetByNativePtr((void*)event.xfocus.window); - if (window) + if (window && mouseTrackingWindow == nullptr) { window->OnGotFocus(); } @@ -2407,7 +2421,7 @@ void LinuxPlatform::Tick() // Update input context focus X11::XUnsetICFocus(IC); window = WindowsManager::GetByNativePtr((void*)event.xfocus.window); - if (window) + if (window && mouseTrackingWindow == nullptr) { window->OnLostFocus(); } @@ -2514,23 +2528,32 @@ void LinuxPlatform::Tick() break; case ButtonPress: window = WindowsManager::GetByNativePtr((void*)event.xbutton.window); - if (window) + if (mouseTrackingWindow) + ((LinuxWindow*)mouseTrackingWindow)->OnButtonPress(&event.xbutton); + else if (window) window->OnButtonPress(&event.xbutton); break; case ButtonRelease: window = WindowsManager::GetByNativePtr((void*)event.xbutton.window); - if (window) + if (mouseTrackingWindow) + ((LinuxWindow*)mouseTrackingWindow)->OnButtonRelease(&event.xbutton); + else if (window) window->OnButtonRelease(&event.xbutton); break; case MotionNotify: window = WindowsManager::GetByNativePtr((void*)event.xmotion.window); - if (window) + if (mouseTrackingWindow) + ((LinuxWindow*)mouseTrackingWindow)->OnMotionNotify(&event.xmotion); + else if (window) window->OnMotionNotify(&event.xmotion); break; case EnterNotify: + // nothing? break; case LeaveNotify: window = WindowsManager::GetByNativePtr((void*)event.xcrossing.window); + if (mouseTrackingWindow) + ((LinuxWindow*)mouseTrackingWindow)->OnLeaveNotify(&event.xcrossing); if (window) window->OnLeaveNotify(&event.xcrossing); break; diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h index 54590adf2..00e7743d7 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.h +++ b/Source/Engine/Platform/Linux/LinuxPlatform.h @@ -139,6 +139,8 @@ public: static String GetWorkingDirectory(); static bool SetWorkingDirectory(const String& path); static Window* CreateWindow(const CreateWindowSettings& settings); + static void StartTrackingMouse(const Window* window); + static void EndTrackingMouse(const Window *window); static void GetEnvironmentVariables(Dictionary& result); static bool GetEnvironmentVariable(const String& name, String& value); static bool SetEnvironmentVariable(const String& name, const String& value); diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index b3bae0276..9168beb29 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -54,7 +54,7 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) return; auto screen = XDefaultScreen(display); - // Cache data + // Cache data int32 width = Math::TruncToInt(settings.Size.X); int32 height = Math::TruncToInt(settings.Size.Y); _clientSize = Float2((float)width, (float)height); @@ -111,6 +111,12 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) windowAttributes.border_pixel = XBlackPixel(display, screen); windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; + if (!settings.IsRegularWindow) + { + windowAttributes.save_under = true; + windowAttributes.override_redirect = true; + } + // TODO: implement all window settings /* bool Fullscreen; @@ -118,11 +124,16 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) bool AllowMaximize; */ + unsigned long valueMask = CWBackPixel | CWBorderPixel | CWEventMask | CWColormap; + if (!settings.IsRegularWindow) + { + valueMask |= CWOverrideRedirect | CWSaveUnder; + } const X11::Window window = X11::XCreateWindow( display, X11::XRootWindow(display, screen), x, y, width, height, 0, visualInfo->depth, InputOutput, visualInfo->visual, - CWBackPixel | CWBorderPixel | CWEventMask | CWColormap, &windowAttributes); + valueMask, &windowAttributes); _window = window; LinuxWindow::SetTitle(settings.Title); @@ -811,12 +822,12 @@ void LinuxWindow::SetTitle(const StringView& title) void LinuxWindow::StartTrackingMouse(bool useMouseScreenOffset) { - // TODO: impl this + LinuxPlatform::StartTrackingMouse(this); } void LinuxWindow::EndTrackingMouse() { - // TODO: impl this + LinuxPlatform::EndTrackingMouse(this); } void LinuxWindow::SetCursor(CursorType type) From f24b335c458dd6b6aca9288a833e7c1967147550 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 8 Dec 2023 09:19:05 -0600 Subject: [PATCH 06/72] Add invert green channel option to texture tool. --- .../TextureTool/TextureTool.DirectXTex.cpp | 31 ++++++++++++++++++- .../Engine/Tools/TextureTool/TextureTool.cpp | 7 ++++- Source/Engine/Tools/TextureTool/TextureTool.h | 4 +++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp index 6588257c2..ab3533508 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp @@ -625,7 +625,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path } bool keepAsIs = false; - if (!options.FlipY && options.Compress && type == ImageType::DDS && mipLevels == sourceMipLevels && DirectX::IsCompressed(sourceDxgiFormat) && !DirectX::IsSRGB(sourceDxgiFormat)) + if (!options.FlipY && !options.InvertGreenChannel && options.Compress && type == ImageType::DDS && mipLevels == sourceMipLevels && DirectX::IsCompressed(sourceDxgiFormat) && !DirectX::IsSRGB(sourceDxgiFormat)) { // Keep image in the current compressed format (artist choice) so we don't have to run the slow mipmap generation keepAsIs = true; @@ -700,6 +700,35 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path SET_CURRENT_IMG(tmpImg); } + if (!keepAsIs && options.InvertGreenChannel) + { + auto& timage = GET_TMP_IMG(); + + result = TransformImage(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), + [&](DirectX::XMVECTOR* outPixels, const DirectX::XMVECTOR* inPixels, size_t w, size_t y) + { + static const DirectX::XMVECTORU32 s_selecty = { { { DirectX::XM_SELECT_0, DirectX::XM_SELECT_1, DirectX::XM_SELECT_0, DirectX::XM_SELECT_0 } } }; + + UNREFERENCED_PARAMETER(y); + + for (size_t j = 0; j < w; ++j) + { + const DirectX::XMVECTOR value = inPixels[j]; + + const DirectX::XMVECTOR inverty = DirectX::XMVectorSubtract(DirectX::g_XMOne, value); + + outPixels[j] = DirectX::XMVectorSelect(value, inverty, s_selecty); + } + }, timage); + if (FAILED(result)) + { + errorMsg = String::Format(TEXT("Cannot invert green channel in texture, error: {0:x}"), static_cast(result)); + return true; + } + // Use converted image + SET_CURRENT_IMG(timage); + } + // Generate mip maps chain if (!keepAsIs && useMipLevels && options.GenerateMipMaps) { diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp index 0f9c8729c..c64134e46 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp @@ -26,7 +26,7 @@ namespace String TextureTool::Options::ToString() const { - return String::Format(TEXT("Type: {}, IsAtlas: {}, NeverStream: {}, IndependentChannels: {}, sRGB: {}, GenerateMipMaps: {}, FlipY: {}, Scale: {}, MaxSize: {}, Resize: {}, PreserveAlphaCoverage: {}, PreserveAlphaCoverageReference: {}, SizeX: {}, SizeY: {}"), + return String::Format(TEXT("Type: {}, IsAtlas: {}, NeverStream: {}, IndependentChannels: {}, sRGB: {}, GenerateMipMaps: {}, FlipY: {}, InvertGreen: {} Scale: {}, MaxSize: {}, Resize: {}, PreserveAlphaCoverage: {}, PreserveAlphaCoverageReference: {}, SizeX: {}, SizeY: {}"), ScriptingEnum::ToString(Type), IsAtlas, NeverStream, @@ -34,6 +34,7 @@ String TextureTool::Options::ToString() const sRGB, GenerateMipMaps, FlipY, + InvertGreenChannel, Scale, MaxSize, MaxSize, @@ -71,6 +72,9 @@ void TextureTool::Options::Serialize(SerializeStream& stream, const void* otherO stream.JKEY("FlipY"); stream.Bool(FlipY); + stream.JKEY("InvertGreenChannel"); + stream.Bool(InvertGreenChannel); + stream.JKEY("Resize"); stream.Bool(Resize); @@ -128,6 +132,7 @@ void TextureTool::Options::Deserialize(DeserializeStream& stream, ISerializeModi sRGB = JsonTools::GetBool(stream, "sRGB", sRGB); GenerateMipMaps = JsonTools::GetBool(stream, "GenerateMipMaps", GenerateMipMaps); FlipY = JsonTools::GetBool(stream, "FlipY", FlipY); + InvertGreenChannel = JsonTools::GetBool(stream, "InvertGreenChannel", InvertGreenChannel); Resize = JsonTools::GetBool(stream, "Resize", Resize); PreserveAlphaCoverage = JsonTools::GetBool(stream, "PreserveAlphaCoverage", PreserveAlphaCoverage); PreserveAlphaCoverageReference = JsonTools::GetFloat(stream, "PreserveAlphaCoverageReference", PreserveAlphaCoverageReference); diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h index 60e9df3af..351167b3c 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.h +++ b/Source/Engine/Tools/TextureTool/TextureTool.h @@ -57,6 +57,10 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool API_FIELD(Attributes="EditorOrder(70)") bool FlipY = false; + // True if to invert the green channel on a normal map. Good for OpenGL to DirectX conversion. + API_FIELD(Attributes = "EditorOrder(71)") + bool InvertGreenChannel = false; + // Texture size scale. Allows increasing or decreasing the imported texture resolution. Default is 1. API_FIELD(Attributes="EditorOrder(80), Limit(0.0001f, 1000.0f, 0.01f)") float Scale = 1.0f; From f346dbc9bf32cc1be7401b906f282a250c1f34bd Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 8 Dec 2023 09:39:21 -0600 Subject: [PATCH 07/72] add comment --- Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp index ab3533508..475fa50de 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp @@ -700,6 +700,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path SET_CURRENT_IMG(tmpImg); } + // Check if it invert green channel if (!keepAsIs && options.InvertGreenChannel) { auto& timage = GET_TMP_IMG(); From a32effff1cf2c649aaa762c0e06aae3271cff611 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 8 Dec 2023 09:44:40 -0600 Subject: [PATCH 08/72] add missing result in texture tool --- Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp index 475fa50de..dc5b0be49 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp @@ -663,7 +663,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path { auto& tmpImg = GET_TMP_IMG(); - TransformImage(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), + result = TransformImage(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), [](DirectX::XMVECTOR* outPixels, const DirectX::XMVECTOR* inPixels, size_t width, size_t y) { UNREFERENCED_PARAMETER(y); From 4707f98fab98e6155e97eb9bb5b3b8029d8581b5 Mon Sep 17 00:00:00 2001 From: Diego Fonseca Date: Fri, 8 Dec 2023 14:13:50 -0500 Subject: [PATCH 09/72] Update Time.cpp --- Source/Engine/Engine/Time.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index a7c4bf80d..8c31d7014 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -160,7 +160,7 @@ bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime) if (targetFps > ZeroTolerance) { - int skip = (int)(1 + (time - NextBegin) / (1.0 / targetFps)); + int skip = (int)(1 + (time - NextBegin) * targetFps); NextBegin += (1.0 / targetFps) * skip; } } From c17ff3926a0f26c1569466f755e23493c2474e1c Mon Sep 17 00:00:00 2001 From: Diego Fonseca Date: Fri, 8 Dec 2023 14:35:10 -0500 Subject: [PATCH 10/72] Update Time.cpp --- Source/Engine/Engine/Time.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index 8c31d7014..3adccdbd9 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -104,7 +104,7 @@ bool Time::TickData::OnTickBegin(float targetFps, float maxDeltaTime) if (targetFps > ZeroTolerance) { - int skip = (int)(1 + (time - NextBegin) / (1.0 / targetFps)); + int skip = (int)(1 + (time - NextBegin) * targetFps); NextBegin += (1.0 / targetFps) * skip; } } From 86fbf05b0937d22cd329ba54f39c4142d4ae3faf Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak Date: Fri, 8 Dec 2023 20:49:47 +0100 Subject: [PATCH 11/72] Fix general swapping function --- Source/Engine/Core/Templates.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Core/Templates.h b/Source/Engine/Core/Templates.h index 75674e2d0..876a4db83 100644 --- a/Source/Engine/Core/Templates.h +++ b/Source/Engine/Core/Templates.h @@ -304,7 +304,7 @@ template inline void Swap(T& a, T& b) noexcept { T tmp = MoveTemp(a); - a = b; + a = MoveTemp(b); b = MoveTemp(tmp); } From f3497a2a553e3557cc179894354dff0258daa1bb Mon Sep 17 00:00:00 2001 From: Mateusz Karbowiak Date: Fri, 8 Dec 2023 20:50:52 +0100 Subject: [PATCH 12/72] Fix swapping core collections --- Source/Engine/Core/Collections/Array.h | 4 +--- Source/Engine/Core/Collections/Dictionary.h | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index cf45ed060..a8390f051 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -734,9 +734,7 @@ public: } else { - Array tmp = MoveTemp(other); - other = *this; - *this = MoveTemp(tmp); + ::Swap(other, *this); } } diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index dd73be390..d2a840fff 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -616,9 +616,7 @@ public: } else { - Dictionary tmp = MoveTemp(other); - other = *this; - *this = MoveTemp(tmp); + ::Swap(other, *this); } } From b1cbaf7e13a5b8c749b7987906fb2fe41f90c022 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Fri, 8 Dec 2023 15:46:09 -0500 Subject: [PATCH 13/72] Fix default prefab instance not taking into account root position. --- Source/Engine/Level/Prefabs/Prefab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index 65da270c0..902bcf06c 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -76,7 +76,7 @@ Actor* Prefab::GetDefaultInstance() _isCreatingDefaultInstance = true; // Instantiate objects from prefab (default spawning logic) - _defaultInstance = PrefabManager::SpawnPrefab(this, Transform::Identity, nullptr, &ObjectsCache); + _defaultInstance = PrefabManager::SpawnPrefab(this, Transform(Vector3::Minimum), nullptr, &ObjectsCache); _isCreatingDefaultInstance = false; return _defaultInstance; From df83491313cb971e4ec408096a3f87386501808d Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 8 Dec 2023 15:48:43 -0600 Subject: [PATCH 14/72] Add ability to change default Android screen orientation. --- .../Platform/Android/AndroidPlatformTools.cpp | 25 +++++++++++++ .../Android/AndroidPlatformSettings.h | 37 +++++++++++++++++++ .../Project/app/src/main/AndroidManifest.xml | 2 +- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp index 995f19fce..d1f4dc6fb 100644 --- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp @@ -169,6 +169,30 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) permissions += String::Format(TEXT("\n "), e.Item); } + // Setup default Android screen orientation + auto defaultOrienation = platformSettings->DefaultOrientation; + String orientation = String("fullSensor"); + switch (defaultOrienation) + { + case AndroidScreenOrientation::Portrait: + orientation = String("portrait"); + break; + case AndroidScreenOrientation::PortraitReverse: + orientation = String("reversePortrait"); + break; + case AndroidScreenOrientation::LandscapeRight: + orientation = String("landscape"); + break; + case AndroidScreenOrientation::LandscapeLeft: + orientation = String("reverseLandscape"); + break; + case AndroidScreenOrientation::AutoRotation: + orientation = String("fullSensor"); + break; + default: + break; + } + // Setup Android application attributes String attributes; if (data.Configuration != BuildConfiguration::Release) @@ -223,6 +247,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) EditorUtilities::ReplaceInFile(manifestPath, TEXT("${PackageName}"), packageName); EditorUtilities::ReplaceInFile(manifestPath, TEXT("${ProjectVersion}"), projectVersion); EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidPermissions}"), permissions); + EditorUtilities::ReplaceInFile(manifestPath, TEXT("${DefaultOrientation}"), orientation); EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidAttributes}"), attributes); const String stringsPath = data.OriginalOutputPath / TEXT("app/src/main/res/values/strings.xml"); EditorUtilities::ReplaceInFile(stringsPath, TEXT("${ProjectName}"), gameSettings->ProductName); diff --git a/Source/Engine/Platform/Android/AndroidPlatformSettings.h b/Source/Engine/Platform/Android/AndroidPlatformSettings.h index 9cfe7f880..f9f12d811 100644 --- a/Source/Engine/Platform/Android/AndroidPlatformSettings.h +++ b/Source/Engine/Platform/Android/AndroidPlatformSettings.h @@ -9,6 +9,37 @@ class Texture; +/// +/// Android screen orientation options. +/// +API_ENUM() enum class FLAXENGINE_API AndroidScreenOrientation +{ + /// + /// "portrait" mode + /// + Portrait, + + /// + /// "reversePortrait" mode + /// + PortraitReverse, + + /// + /// "landscape" mode + /// + LandscapeRight, + + /// + /// "reverseLandscape" mode + /// + LandscapeLeft, + + /// + /// "fullSensor" mode + /// + AutoRotation, +}; + /// /// Android platform settings. /// @@ -29,6 +60,12 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"General\")") Array Permissions; + /// + /// The default screen orientation. + /// + API_FIELD(Attributes = "EditorOrder(110), EditorDisplay(\"General\")") + AndroidScreenOrientation DefaultOrientation = AndroidScreenOrientation::AutoRotation; + /// /// Custom icon texture to use for the application (overrides the default one). /// diff --git a/Source/Platforms/Android/Binaries/Project/app/src/main/AndroidManifest.xml b/Source/Platforms/Android/Binaries/Project/app/src/main/AndroidManifest.xml index e535cef40..5169e3846 100644 --- a/Source/Platforms/Android/Binaries/Project/app/src/main/AndroidManifest.xml +++ b/Source/Platforms/Android/Binaries/Project/app/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ + android:screenOrientation="${DefaultOrientation}"> From b8ce9e8c5904ef676bd01769e9deabcc119df01a Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 8 Dec 2023 17:01:48 -0600 Subject: [PATCH 15/72] Scale up drop down items with text height as needed. --- Source/Engine/UI/GUI/Common/Dropdown.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index ecca2978f..31ea87948 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -469,7 +469,13 @@ namespace FlaxEngine.GUI var itemsHeight = 20.0f; var itemsMargin = 20.0f; - + // Scale height and margive with text height if needed + var textHeight = Font.GetFont().Height; + if (textHeight > itemsHeight) + { + itemsHeight = textHeight; + itemsMargin = textHeight; + } /* var itemsWidth = 40.0f; var font = Font.GetFont(); From 0cb064bfb3320c3e5fc748614eb6030fb364a8fa Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Sat, 9 Dec 2023 23:41:17 +0100 Subject: [PATCH 16/72] fixed Blend Additive problem mesh was just exploding + code should be faster --- .../Animations/Graph/AnimGroup.Animation.cpp | 42 +++++++------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index d514a928c..8a7751ecb 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -1070,38 +1070,26 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu else { const auto nodes = node->GetNodes(this); - const auto nodesA = static_cast(valueA.AsPointer); - const auto nodesB = static_cast(valueB.AsPointer); - const auto& baseNodes = _graph.BaseModel.Get()->GetNodes(); - Transform t, tA, tB; + const auto basePoseNodes = static_cast(valueA.AsPointer); + const auto blendPoseNodes = static_cast(valueB.AsPointer); + const auto& refrenceNodes = _graph.BaseModel.Get()->GetNodes(); + Transform t, basePoseTransform, blendPoseTransform, refrenceTransform; for (int32 i = 0; i < nodes->Nodes.Count(); i++) { - tA = nodesA->Nodes[i]; - tB = nodesB->Nodes[i]; - const auto& baseNode = baseNodes[i]; + basePoseTransform = basePoseNodes->Nodes[i]; + blendPoseTransform = blendPoseNodes->Nodes[i]; + refrenceTransform = refrenceNodes[i].LocalTransform; - t.Translation = tA.Translation + (tB.Translation - baseNode.LocalTransform.Translation) * alpha; + // base + (blend - refrence) = transform + t.Translation = basePoseTransform.Translation + (blendPoseTransform.Translation - refrenceTransform.Translation); + auto diff = Quaternion::Invert(refrenceTransform.Orientation) * blendPoseTransform.Orientation; + t.Orientation = basePoseTransform.Orientation * diff; + t.Scale = basePoseTransform.Scale + (blendPoseTransform.Scale - refrenceTransform.Scale); - //auto baseOrientation = tA.Orientation; - //Quaternion additiveOrientation = alpha * (tB.Orientation - baseNode.LocalTransform.Orientation); - //t.Orientation = baseOrientation + additiveOrientation; - auto m1 = Matrix::RotationQuaternion(tA.Orientation); - auto m2 = Matrix::RotationQuaternion(alpha * tB.Orientation); - auto m3 = Matrix::RotationQuaternion(alpha * baseNode.LocalTransform.Orientation); - Matrix m4; - Matrix::Subtract(m2, m3, m4); - Matrix m5; - Matrix::Add(m1, m4, m5); - - t.SetRotation(m5); - t.Orientation.Normalize(); - - t.Scale = tA.Scale * tB.Scale; - - //nodes->Nodes[i] = t; - Transform::Lerp(tA, t, alpha, nodes->Nodes[i]); + //lerp base and transform + Transform::Lerp(basePoseTransform, t, alpha, nodes->Nodes[i]); } - Transform::Lerp(nodesA->RootMotion, nodesA->RootMotion + nodesB->RootMotion, alpha, nodes->RootMotion); + Transform::Lerp(basePoseNodes->RootMotion, basePoseNodes->RootMotion + blendPoseNodes->RootMotion, alpha, nodes->RootMotion); value = nodes; } } From 4e54e945efb0898ba20ff9a0de61f4f9593cb036 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sat, 9 Dec 2023 17:43:06 -0500 Subject: [PATCH 17/72] Implement auto-sizing for box colliders when they are added to the scene. --- Source/Editor/Modules/SceneModule.cs | 2 ++ Source/Engine/Level/Actor.cs | 9 ++++++++ Source/Engine/Level/Actors/BoxCollider.cs | 27 +++++++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 Source/Engine/Level/Actors/BoxCollider.cs diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 954e3bd50..f5471ec33 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -534,6 +534,8 @@ namespace FlaxEditor.Modules { node.ParentNode = parentNode; } + + actor.OnActorSpawned(); } private void OnActorDeleted(Actor actor) diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index dbe8a89b5..7c34b5d7e 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -116,6 +116,15 @@ namespace FlaxEngine LocalTransform = Transform.Identity; } + /// + /// Called in-editor when an actor is added to the scene. + /// If not in the editor, this function will not be called. + /// + public virtual void OnActorSpawned() + { + + } + /// /// Creates a new child actor of the given type. /// diff --git a/Source/Engine/Level/Actors/BoxCollider.cs b/Source/Engine/Level/Actors/BoxCollider.cs new file mode 100644 index 000000000..f9343eb74 --- /dev/null +++ b/Source/Engine/Level/Actors/BoxCollider.cs @@ -0,0 +1,27 @@ +using System; + +namespace FlaxEngine +{ + partial class BoxCollider + { + /// + public override void OnActorSpawned() + { + base.OnActorSpawned(); + if (Parent is StaticModel model) + { + Vector3 modelScale = model.Scale; + Vector3 modelSize = model.Box.Size; + Vector3 modelCenter = model.Box.Center - model.Position; + Vector3 colliderSize = modelSize / modelScale; + Vector3 colliderCenter = modelCenter / modelScale; + + Size = colliderSize; + Center = colliderCenter; + + // Undo Rotation + Orientation *= Quaternion.Invert(Orientation); + } + } + } +} From 2f500425239d206f9afd0afa43656999d9e35027 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sat, 9 Dec 2023 17:56:09 -0500 Subject: [PATCH 18/72] Simplify code and allow any actor as parent. --- Source/Engine/Level/Actors/BoxCollider.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Level/Actors/BoxCollider.cs b/Source/Engine/Level/Actors/BoxCollider.cs index f9343eb74..8f2d0e366 100644 --- a/Source/Engine/Level/Actors/BoxCollider.cs +++ b/Source/Engine/Level/Actors/BoxCollider.cs @@ -8,20 +8,15 @@ namespace FlaxEngine public override void OnActorSpawned() { base.OnActorSpawned(); - if (Parent is StaticModel model) - { - Vector3 modelScale = model.Scale; - Vector3 modelSize = model.Box.Size; - Vector3 modelCenter = model.Box.Center - model.Position; - Vector3 colliderSize = modelSize / modelScale; - Vector3 colliderCenter = modelCenter / modelScale; + Vector3 parentScale = Parent.Scale; + Vector3 parentSize = Parent.Box.Size; + Vector3 parentCenter = Parent.Box.Center - Parent.Position; - Size = colliderSize; - Center = colliderCenter; + Size = parentSize / parentScale; + Center = parentCenter / parentScale; - // Undo Rotation - Orientation *= Quaternion.Invert(Orientation); - } + // Undo Rotation + Orientation *= Quaternion.Invert(Orientation); } } } From e508fb8cd013e007fa41a05dd6ea371a90d5ea1e Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Sun, 10 Dec 2023 00:23:43 +0100 Subject: [PATCH 19/72] added Note and Tip for blender users to Description --- Source/Editor/Surface/Archetypes/Animation.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index 40a3d2a63..446883406 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -493,7 +493,15 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 10, Title = "Blend Additive", - Description = "Blend animation poses (with additive mode)", + Description = + "Blend animation poses (with additive mode)" + + "\n" + + "\nNote: " + + "\nOrder of nodes matters, because Additive animation is appplayed on top of curent frame." + + "\n" + + "\nTip for blender users:" + + "\nInside NLA the the order is bottom (first node in flax) to the top (last node in flax)" + + "\n​u need to place it in this order to get correct resoults", Flags = NodeFlags.AnimGraph, Size = new Float2(170, 80), DefaultValues = new object[] From c895e310cbec7a6fbca418ff419cd64e08b13cb4 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sat, 9 Dec 2023 19:19:03 -0500 Subject: [PATCH 20/72] Improve box collider creation behavior to account for child actors of the collider's parent. --- Source/Engine/Level/Actors/BoxCollider.cs | 29 ++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Level/Actors/BoxCollider.cs b/Source/Engine/Level/Actors/BoxCollider.cs index 8f2d0e366..a21f30aeb 100644 --- a/Source/Engine/Level/Actors/BoxCollider.cs +++ b/Source/Engine/Level/Actors/BoxCollider.cs @@ -1,16 +1,39 @@ -using System; +using FlaxEditor.Windows; namespace FlaxEngine { partial class BoxCollider { + private void BoxExcluding(Actor target, ref BoundingBox output, Actor excluded) + { + foreach (Actor child in target.Children) + { + if (child == excluded) + { + continue; + } + + output = BoundingBox.Merge(output, child.Box); + BoxExcluding(child, ref output, excluded); + } + } + /// public override void OnActorSpawned() { base.OnActorSpawned(); Vector3 parentScale = Parent.Scale; - Vector3 parentSize = Parent.Box.Size; - Vector3 parentCenter = Parent.Box.Center - Parent.Position; + BoundingBox parentBox = Parent.Box; + BoxExcluding(Parent, ref parentBox, this); + + Vector3 parentSize = parentBox.Size; + Vector3 parentCenter = parentBox.Center - Parent.Position; + + // Avoid division by zero + if (parentScale.X == 0 || parentScale.Y == 0 || parentScale.Z == 0) + { + return; + } Size = parentSize / parentScale; Center = parentCenter / parentScale; From 01b233af109171b1ce8b9e317ded04d869e5fe77 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sat, 9 Dec 2023 19:57:52 -0500 Subject: [PATCH 21/72] Factor out AutoResize() function for external use. Fix build regression due to unwanted import. --- Source/Engine/Level/Actors/BoxCollider.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Level/Actors/BoxCollider.cs b/Source/Engine/Level/Actors/BoxCollider.cs index a21f30aeb..9c816eac4 100644 --- a/Source/Engine/Level/Actors/BoxCollider.cs +++ b/Source/Engine/Level/Actors/BoxCollider.cs @@ -1,5 +1,3 @@ -using FlaxEditor.Windows; - namespace FlaxEngine { partial class BoxCollider @@ -18,10 +16,16 @@ namespace FlaxEngine } } - /// - public override void OnActorSpawned() + /// + /// Resizes the box collider based on the bounds of it's parent. + /// + public void AutoResize() { - base.OnActorSpawned(); + if (Parent is Scene) + { + return; + } + Vector3 parentScale = Parent.Scale; BoundingBox parentBox = Parent.Box; BoxExcluding(Parent, ref parentBox, this); @@ -41,5 +45,12 @@ namespace FlaxEngine // Undo Rotation Orientation *= Quaternion.Invert(Orientation); } + + /// + public override void OnActorSpawned() + { + base.OnActorSpawned(); + AutoResize(); + } } } From 19dbd3c4e4f6c562adc9dde920dca5b825e7a264 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sat, 9 Dec 2023 21:00:45 -0500 Subject: [PATCH 22/72] Add button to resize collider manually. --- Source/Engine/Level/Actors/BoxCollider.cs | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Source/Engine/Level/Actors/BoxCollider.cs b/Source/Engine/Level/Actors/BoxCollider.cs index 9c816eac4..ff560aa64 100644 --- a/Source/Engine/Level/Actors/BoxCollider.cs +++ b/Source/Engine/Level/Actors/BoxCollider.cs @@ -1,5 +1,30 @@ +#if FLAX_EDITOR +using FlaxEditor.CustomEditors; +using FlaxEditor.CustomEditors.Dedicated; +#endif + namespace FlaxEngine { +#if FLAX_EDITOR + /// + /// Dedicated custom editor for BoxCollider objects. + /// + [CustomEditor(typeof(BoxCollider)), DefaultEditor] + public class BoxColliderEditor : ActorEditor + { + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + layout.Space(20f); + var autoResizeButton = layout.Button("Resize to Fit", "Resize the box collider to fit it's parent's bounds."); + + BoxCollider collider = Values[0] as BoxCollider; + autoResizeButton.Button.Clicked += collider.AutoResize; + } + } +#endif + partial class BoxCollider { private void BoxExcluding(Actor target, ref BoundingBox output, Actor excluded) @@ -26,6 +51,8 @@ namespace FlaxEngine return; } + LocalPosition = Vector3.Zero; + Vector3 parentScale = Parent.Scale; BoundingBox parentBox = Parent.Box; BoxExcluding(Parent, ref parentBox, this); From fe19ffddd982b20d34107a79e36707eceee28c7e Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sun, 10 Dec 2023 18:12:41 -0500 Subject: [PATCH 23/72] Fix freeze when prefab ID is empty. --- Source/Editor/CustomEditors/Dedicated/ModelPrefabEditor.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Editor/CustomEditors/Dedicated/ModelPrefabEditor.cs b/Source/Editor/CustomEditors/Dedicated/ModelPrefabEditor.cs index 0a42b952e..1e41fcc32 100644 --- a/Source/Editor/CustomEditors/Dedicated/ModelPrefabEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ModelPrefabEditor.cs @@ -31,6 +31,11 @@ public class ModelPrefabEditor : GenericEditor _prefabId = modelPrefab.PrefabID; while (true) { + if (_prefabId == Guid.Empty) + { + break; + } + var prefab = FlaxEngine.Content.Load(_prefabId); if (prefab) { From 0df00fd88127034eb94b3efa0efdce4d27fda338 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Mon, 11 Dec 2023 04:14:17 +0100 Subject: [PATCH 24/72] wait for git processes to end --- Source/Editor/Windows/PluginsWindow.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index f43fb0342..eb04f07b8 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -381,6 +381,7 @@ namespace FlaxEditor.Windows Arguments = $"clone {gitPath} \"{clonePath}\"", ShellExecute = false, LogOutput = true, + WaitForEnd = true }; Platform.CreateProcess(ref settings); } @@ -402,6 +403,7 @@ namespace FlaxEditor.Windows Arguments = "submodule update --init", ShellExecute = false, LogOutput = true, + WaitForEnd = true }; Platform.CreateProcess(ref settings); } From 6dd72cdf32a252f94d337b0593caf91f987f4dd2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 11 Dec 2023 14:14:55 +0100 Subject: [PATCH 25/72] Add removing thumbnails for deleted assets #1729 --- Source/Editor/Content/PreviewsCache.cpp | 19 ++++++++++++------ .../Content/Thumbnails/ThumbnailsModule.cs | 20 ++----------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/Source/Editor/Content/PreviewsCache.cpp b/Source/Editor/Content/PreviewsCache.cpp index 2d62e8170..b0f3619a5 100644 --- a/Source/Editor/Content/PreviewsCache.cpp +++ b/Source/Editor/Content/PreviewsCache.cpp @@ -5,6 +5,7 @@ #include "Engine/Graphics/GPUContext.h" #include "Engine/Threading/Threading.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Content/Content.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/Content/Upgraders/TextureAssetUpgrader.h" @@ -92,15 +93,12 @@ SpriteHandle PreviewsCache::FindSlot(const Guid& id) { if (WaitForLoaded()) return SpriteHandle::Invalid; - - // Find entry int32 index; if (_assets.Find(id, index)) { const String spriteName = StringUtils::ToString(index); return FindSprite(spriteName); } - return SpriteHandle::Invalid; } @@ -114,6 +112,17 @@ Asset::LoadResult PreviewsCache::load() return LoadResult::Failed; _assets.Set(previewsMetaChunk->Get(), ASSETS_ICONS_PER_ATLAS); + // Verify if cached assets still exist (don't store thumbnails for removed files) + AssetInfo assetInfo; + for (Guid& id : _assets) + { + if (id.IsValid() && Content::GetAsset(id) == nullptr && !Content::GetAssetInfo(id, assetInfo)) + { + // Free slot (no matter the texture contents) + id = Guid::Empty; + } + } + // Setup atlas sprites array Sprite sprite; sprite.Area.Size = static_cast(ASSET_ICON_SIZE) / ASSETS_ICONS_ATLAS_SIZE; @@ -162,7 +171,7 @@ SpriteHandle PreviewsCache::OccupySlot(GPUTexture* source, const Guid& id) if (WaitForLoaded()) return SpriteHandle::Invalid; - // Find free slot and for that asset + // Find this asset slot or use the first empty int32 index = _assets.Find(id); if (index == INVALID_INDEX) index = _assets.Find(Guid::Empty); @@ -201,14 +210,12 @@ bool PreviewsCache::ReleaseSlot(const Guid& id) { bool result = false; ScopeLock lock(Locker); - int32 index = _assets.Find(id); if (index != INVALID_INDEX) { _assets[index] = Guid::Empty; result = true; } - return result; } diff --git a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs index 1282e4daa..2b830c933 100644 --- a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs +++ b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs @@ -21,15 +21,11 @@ namespace FlaxEditor.Content.Thumbnails /// public const float MinimumRequiredResourcesQuality = 0.8f; - // TODO: free atlas slots for deleted assets - private readonly List _cache = new List(4); private readonly string _cacheFolder; - - private DateTime _lastFlushTime; - private readonly List _requests = new List(128); private readonly PreviewRoot _guiRoot = new PreviewRoot(); + private DateTime _lastFlushTime; private RenderTask _task; private GPUTexture _output; @@ -88,7 +84,6 @@ namespace FlaxEditor.Content.Thumbnails } } - // Add request AddRequest(assetItem, proxy); } } @@ -118,9 +113,7 @@ namespace FlaxEditor.Content.Thumbnails for (int i = 0; i < _cache.Count; i++) { if (_cache[i].ReleaseSlot(assetItem.ID)) - { break; - } } } } @@ -198,13 +191,7 @@ namespace FlaxEditor.Content.Thumbnails /// void IContentItemOwner.OnItemDeleted(ContentItem item) { - if (item is AssetItem assetItem) - { - lock (_requests) - { - RemoveRequest(assetItem); - } - } + DeletePreview(item); } /// @@ -494,10 +481,7 @@ namespace FlaxEditor.Content.Thumbnails { // Wait some frames before start generating previews (late init feature) if (Time.TimeSinceStartup < 1.0f || HasAllAtlasesLoaded() == false) - { - // Back return; - } lock (_requests) { From 4725f51431e6cdeb9205de92e7ca607006835a87 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 11 Dec 2023 10:00:19 -0600 Subject: [PATCH 26/72] Move android screen orientation into platform settings --- .../Platform/Android/AndroidPlatformTools.cpp | 10 +-- .../Android/AndroidPlatformSettings.h | 64 +++++++++---------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp index d1f4dc6fb..88f993f20 100644 --- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp @@ -174,19 +174,19 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) String orientation = String("fullSensor"); switch (defaultOrienation) { - case AndroidScreenOrientation::Portrait: + case AndroidPlatformSettings::ScreenOrientation::Portrait: orientation = String("portrait"); break; - case AndroidScreenOrientation::PortraitReverse: + case AndroidPlatformSettings::ScreenOrientation::PortraitReverse: orientation = String("reversePortrait"); break; - case AndroidScreenOrientation::LandscapeRight: + case AndroidPlatformSettings::ScreenOrientation::LandscapeRight: orientation = String("landscape"); break; - case AndroidScreenOrientation::LandscapeLeft: + case AndroidPlatformSettings::ScreenOrientation::LandscapeLeft: orientation = String("reverseLandscape"); break; - case AndroidScreenOrientation::AutoRotation: + case AndroidPlatformSettings::ScreenOrientation::AutoRotation: orientation = String("fullSensor"); break; default: diff --git a/Source/Engine/Platform/Android/AndroidPlatformSettings.h b/Source/Engine/Platform/Android/AndroidPlatformSettings.h index f9f12d811..974a4c488 100644 --- a/Source/Engine/Platform/Android/AndroidPlatformSettings.h +++ b/Source/Engine/Platform/Android/AndroidPlatformSettings.h @@ -9,37 +9,6 @@ class Texture; -/// -/// Android screen orientation options. -/// -API_ENUM() enum class FLAXENGINE_API AndroidScreenOrientation -{ - /// - /// "portrait" mode - /// - Portrait, - - /// - /// "reversePortrait" mode - /// - PortraitReverse, - - /// - /// "landscape" mode - /// - LandscapeRight, - - /// - /// "reverseLandscape" mode - /// - LandscapeLeft, - - /// - /// "fullSensor" mode - /// - AutoRotation, -}; - /// /// Android platform settings. /// @@ -48,6 +17,37 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API DECLARE_SCRIPTING_TYPE_MINIMAL(AndroidPlatformSettings); API_AUTO_SERIALIZATION(); + /// + /// Android screen orientation options. + /// + API_ENUM() enum class FLAXENGINE_API ScreenOrientation + { + /// + /// "portrait" mode + /// + Portrait, + + /// + /// "reversePortrait" mode + /// + PortraitReverse, + + /// + /// "landscape" mode + /// + LandscapeRight, + + /// + /// "reverseLandscape" mode + /// + LandscapeLeft, + + /// + /// "fullSensor" mode + /// + AutoRotation, + }; + /// /// The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}. /// @@ -64,7 +64,7 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API /// The default screen orientation. /// API_FIELD(Attributes = "EditorOrder(110), EditorDisplay(\"General\")") - AndroidScreenOrientation DefaultOrientation = AndroidScreenOrientation::AutoRotation; + ScreenOrientation DefaultOrientation = ScreenOrientation::AutoRotation; /// /// Custom icon texture to use for the application (overrides the default one). From 80b5e9d02a476491880defd6b15f04f8f82ae712 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 11 Dec 2023 19:48:06 +0100 Subject: [PATCH 27/72] Fix doc code example https://forum.flaxengine.com/t/could-be-a-deprecated-document-hint-of-custompostfx-on-mainrendertask/1510 --- Source/Engine/Graphics/PostProcessEffect.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Graphics/PostProcessEffect.h b/Source/Engine/Graphics/PostProcessEffect.h index 2e444c0ea..1513ad64e 100644 --- a/Source/Engine/Graphics/PostProcessEffect.h +++ b/Source/Engine/Graphics/PostProcessEffect.h @@ -13,7 +13,7 @@ struct RenderContext; /// Custom PostFx which can modify final image by processing it with material based filters. The base class for all post process effects used by the graphics pipeline. Allows to extend frame rendering logic and apply custom effects such as outline, night vision, contrast etc. /// /// -/// Override this class and implement custom post fx logic. Use MainRenderTask.Instance.CustomPostFx.Add(myPostFx) to attach your script to rendering or add script to camera actor. +/// Override this class and implement custom post fx logic. Use MainRenderTask.Instance.AddCustomPostFx(myPostFx) to attach your script to rendering or add script to camera actor. /// API_CLASS(Abstract) class FLAXENGINE_API PostProcessEffect : public Script { From ff195eeccba237f98b66a504359877f53eb3b2f8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 11 Dec 2023 22:35:51 +0100 Subject: [PATCH 28/72] Fix deadlock in `Asset.WaitForLoaded` when loading task hangs in the loading queue for a main thread sync #2057 --- Source/Engine/Content/Asset.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 93d904d5e..d1c2a57d7 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -438,12 +438,15 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const // Note: to reproduce this case just include material into material (use layering). // So during loading first material it will wait for child materials loaded calling this function + const double timeoutInSeconds = timeoutInMilliseconds * 0.001; + const double startTime = Platform::GetTimeSeconds(); Task* task = loadingTask; Array> localQueue; - while (!Engine::ShouldExit()) +#define CHECK_CONDITIONS() (!Engine::ShouldExit() && (timeoutInSeconds <= 0.0 || Platform::GetTimeSeconds() - startTime < timeoutInSeconds)) + do { // Try to execute content tasks - while (task->IsQueued() && !Engine::ShouldExit()) + while (task->IsQueued() && CHECK_CONDITIONS()) { // Dequeue task from the loading queue ContentLoadTask* tmp; @@ -494,7 +497,8 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const break; } } - } + } while (CHECK_CONDITIONS()); +#undef CHECK_CONDITIONS } else { From 778dd2d3f00a89898dc3ce3506665412702f61ae Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 11 Dec 2023 22:55:36 +0100 Subject: [PATCH 29/72] Fix shader file include path resolve on cache load --- .../Shaders/Cache/ShaderAssetBase.cpp | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index caebcfc3c..033d50466 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -115,27 +115,20 @@ bool ShaderAssetBase::Save() bool IsValidShaderCache(DataContainer& shaderCache, Array& includes) { if (shaderCache.Length() == 0) - { return false; - } - MemoryReadStream stream(shaderCache.Get(), shaderCache.Length()); // Read cache format version int32 version; stream.ReadInt32(&version); if (version != GPU_SHADER_CACHE_VERSION) - { return false; - } // Read the location of additional data that contains list of included source files int32 additionalDataStart; stream.ReadInt32(&additionalDataStart); stream.SetPosition(additionalDataStart); - bool result = true; - // Read all includes int32 includesCount; stream.ReadInt32(&includesCount); @@ -144,28 +137,16 @@ bool IsValidShaderCache(DataContainer& shaderCache, Array& include { String& include = includes.AddOne(); stream.ReadString(&include, 11); + include = ShadersCompilation::ResolveShaderPath(include); DateTime lastEditTime; stream.Read(lastEditTime); // Check if included file exists locally and has been modified since last compilation if (FileSystem::FileExists(include) && FileSystem::GetFileLastEditTime(include) > lastEditTime) - { - result = false; - } + return false; } -#if 0 - // Check duplicates - for (int32 i = 0; i < includes.Count(); i++) - { - if (includes.FindLast(includes[i]) != i) - { - CRASH; - } - } -#endif - - return result; + return true; } #endif From 8aaa5710df0115e093f7c6d86c603be4f699ac39 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 12 Dec 2023 16:02:53 +0100 Subject: [PATCH 30/72] Fix dark outline around Screen Space Reflections alpha blending area --- Content/Shaders/SSR.flax | 4 ++-- Source/Shaders/SSR.shader | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Content/Shaders/SSR.flax b/Content/Shaders/SSR.flax index a422ce5c0..d7cfdca1a 100644 --- a/Content/Shaders/SSR.flax +++ b/Content/Shaders/SSR.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0317fb2ca888fee6d32c7754d6f83e2f1e58924725a8221c5131e974501168ac -size 10912 +oid sha256:d3c3ceba0aa0b79f5619147a0ec8ac30d87c142322bd256dc235b7b4f890fc94 +size 11142 diff --git a/Source/Shaders/SSR.shader b/Source/Shaders/SSR.shader index 999dd93a3..91122f2ee 100644 --- a/Source/Shaders/SSR.shader +++ b/Source/Shaders/SSR.shader @@ -106,17 +106,20 @@ float4 PS_RayTracePass(Quad_VS2PS input) : SV_Target0 // SRV 7-8 Global SDF // SRV 9-13 Global Surface Atlas + // Base layer color with reflections from probes but empty alpha so SSR blur will have valid bacground values to smooth with + float4 base = float4(Texture0.SampleLevel(SamplerLinearClamp, input.TexCoord, 0).rgb, 0); + // Sample GBuffer GBufferData gBufferData = GetGBufferData(); GBufferSample gBuffer = SampleGBuffer(gBufferData, input.TexCoord); // Reject invalid pixels if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT || gBuffer.Roughness > RoughnessFade || gBuffer.ViewPos.z > FadeOutDistance) - return 0; + return base; // Trace depth buffer to find intersection float3 screenHit = TraceScreenSpaceReflection(input.TexCoord, gBuffer, Depth, gBufferData.ViewPos, ViewMatrix, ViewProjectionMatrix, RayTraceStep, MaxTraceSamples, TemporalEffect, TemporalTime, WorldAntiSelfOcclusionBias, BRDFBias, FadeOutDistance, RoughnessFade, EdgeFadeFactor); - float4 result = 0; + float4 result = base; if (screenHit.z > 0) { float3 viewVector = normalize(gBufferData.ViewPos - gBuffer.WorldPos); From b297b9f185735ec67db4ec7a500700556439f338 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 12 Dec 2023 18:59:52 +0100 Subject: [PATCH 31/72] Fix not supported dragging prefab actors between windows #2065 --- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 14f11ceef..9cfe5b7bd 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -698,6 +698,10 @@ namespace FlaxEditor.SceneGraph.GUI if (thisHasScene != otherHasScene) return false; + // Reject dragging actors between prefab windows (different roots) + if (!thisHasScene && ActorNode.Root != actorNode.Root) + return false; + // Reject dragging parents and itself return actorNode.Actor != null && actorNode != ActorNode && actorNode.Find(Actor) == null; } From d26b9818d881bfa7723837fc79e26dcd3fcc233c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 12 Dec 2023 19:13:47 +0100 Subject: [PATCH 32/72] Fix spawned prefab name after drag&drop into prefab window #1865 --- Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index be8f09f3a..478439e0e 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -368,7 +368,7 @@ namespace FlaxEditor.Windows.Assets actor.Layer = parentActor.Layer; // Rename actor to identify it easily - actor.Name = Utilities.Utils.IncrementNameNumber(actor.GetType().Name, x => parentActor.GetChild(x) == null); + actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parentActor.GetChild(x) == null); } // Spawn it From 945438568341cf16988910d62e5103652d8cd266 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Tue, 12 Dec 2023 15:46:15 -0500 Subject: [PATCH 33/72] Use the BoxColliderNode class for handling actor spawn events. --- Source/Editor/Modules/SceneModule.cs | 2 -- .../SceneGraph/Actors/BoxColliderNode.cs | 33 +++++++++++++++++++ Source/Engine/Level/Actor.cs | 9 ----- Source/Engine/Level/Actors/BoxCollider.cs | 27 --------------- 4 files changed, 33 insertions(+), 38 deletions(-) diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index f5471ec33..954e3bd50 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -534,8 +534,6 @@ namespace FlaxEditor.Modules { node.ParentNode = parentNode; } - - actor.OnActorSpawned(); } private void OnActorDeleted(Actor actor) diff --git a/Source/Editor/SceneGraph/Actors/BoxColliderNode.cs b/Source/Editor/SceneGraph/Actors/BoxColliderNode.cs index 1f92eceea..8c00318db 100644 --- a/Source/Editor/SceneGraph/Actors/BoxColliderNode.cs +++ b/Source/Editor/SceneGraph/Actors/BoxColliderNode.cs @@ -8,8 +8,33 @@ using Real = System.Single; using FlaxEngine; +#if FLAX_EDITOR +using FlaxEditor.CustomEditors.Dedicated; +using FlaxEditor.CustomEditors; +#endif + namespace FlaxEditor.SceneGraph.Actors { +#if FLAX_EDITOR + /// + /// Dedicated custom editor for BoxCollider objects. + /// + [CustomEditor(typeof(BoxCollider)), DefaultEditor] + public class BoxColliderEditor : ActorEditor + { + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + layout.Space(20f); + var autoResizeButton = layout.Button("Resize to Fit", "Resize the box collider to fit it's parent's bounds."); + + BoxCollider collider = Values[0] as BoxCollider; + autoResizeButton.Button.Clicked += collider.AutoResize; + } + } +#endif + /// /// Scene tree node for actor type. /// @@ -37,5 +62,13 @@ namespace FlaxEditor.SceneGraph.Actors return base.RayCastSelf(ref ray, out distance, out normal); } + + /// + public override void PostSpawn() + { + base.PostSpawn(); + BoxCollider boxCollider = Actor as BoxCollider; + boxCollider.AutoResize(); + } } } diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index 7c34b5d7e..dbe8a89b5 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -116,15 +116,6 @@ namespace FlaxEngine LocalTransform = Transform.Identity; } - /// - /// Called in-editor when an actor is added to the scene. - /// If not in the editor, this function will not be called. - /// - public virtual void OnActorSpawned() - { - - } - /// /// Creates a new child actor of the given type. /// diff --git a/Source/Engine/Level/Actors/BoxCollider.cs b/Source/Engine/Level/Actors/BoxCollider.cs index ff560aa64..cfb6e9dde 100644 --- a/Source/Engine/Level/Actors/BoxCollider.cs +++ b/Source/Engine/Level/Actors/BoxCollider.cs @@ -5,26 +5,6 @@ using FlaxEditor.CustomEditors.Dedicated; namespace FlaxEngine { -#if FLAX_EDITOR - /// - /// Dedicated custom editor for BoxCollider objects. - /// - [CustomEditor(typeof(BoxCollider)), DefaultEditor] - public class BoxColliderEditor : ActorEditor - { - /// - public override void Initialize(LayoutElementsContainer layout) - { - base.Initialize(layout); - layout.Space(20f); - var autoResizeButton = layout.Button("Resize to Fit", "Resize the box collider to fit it's parent's bounds."); - - BoxCollider collider = Values[0] as BoxCollider; - autoResizeButton.Button.Clicked += collider.AutoResize; - } - } -#endif - partial class BoxCollider { private void BoxExcluding(Actor target, ref BoundingBox output, Actor excluded) @@ -72,12 +52,5 @@ namespace FlaxEngine // Undo Rotation Orientation *= Quaternion.Invert(Orientation); } - - /// - public override void OnActorSpawned() - { - base.OnActorSpawned(); - AutoResize(); - } } } From 1874382816b27439bb01cff9689573d8b07e2841 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 13 Dec 2023 11:05:29 +0100 Subject: [PATCH 34/72] Various fixes to prefabs --- Source/Engine/Level/Actor.cpp | 5 ++++- Source/Engine/Level/Prefabs/Prefab.Apply.cpp | 4 ++-- Source/Engine/Level/Prefabs/Prefab.cpp | 2 +- Source/Engine/Level/Prefabs/PrefabManager.cpp | 4 ++-- Source/Engine/Networking/NetworkReplicator.cpp | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index bc568458e..00ca485d2 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1034,7 +1034,10 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) } else if (!parent && parentId.IsValid()) { - LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString()); + if (_prefabObjectID.IsValid()) + LOG(Warning, "Missing parent actor {0} for \'{1}\', prefab object {2}", parentId, ToString(), _prefabObjectID); + else + LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString()); } } } diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index 569a7ce9b..35b02bf40 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -1230,14 +1230,14 @@ bool Prefab::SyncChangesInternal(PrefabInstancesData& prefabInstancesData) { ScopeLock lock(Locker); _isCreatingDefaultInstance = true; - _defaultInstance = PrefabManager::SpawnPrefab(this, Transform::Identity, nullptr, &ObjectsCache, true); + _defaultInstance = PrefabManager::SpawnPrefab(this, nullptr, &ObjectsCache, true); _isCreatingDefaultInstance = false; } // Instantiate prefab instance from prefab (default spawning logic) // Note: it will get any added or removed objects from the nested prefabs // TODO: try to optimize by using recreated default instance to ApplyAllInternal (will need special path there if apply is done with default instance to unlink it instead of destroying) - const auto targetActor = PrefabManager::SpawnPrefab(this, Transform::Identity, nullptr, nullptr, true); + const auto targetActor = PrefabManager::SpawnPrefab(this, nullptr, nullptr, true); if (targetActor == nullptr) { LOG(Warning, "Failed to instantiate default prefab instance from changes synchronization."); diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index 902bcf06c..56bf6a1e2 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -76,7 +76,7 @@ Actor* Prefab::GetDefaultInstance() _isCreatingDefaultInstance = true; // Instantiate objects from prefab (default spawning logic) - _defaultInstance = PrefabManager::SpawnPrefab(this, Transform(Vector3::Minimum), nullptr, &ObjectsCache); + _defaultInstance = PrefabManager::SpawnPrefab(this, nullptr, &ObjectsCache); _isCreatingDefaultInstance = false; return _defaultInstance; diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index bb4710bc7..d014f58d7 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -157,7 +157,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac // Pick prefab root object if (sceneObjects->IsEmpty()) { - LOG(Warning, "No valid objects in prefab."); + LOG(Warning, "No valid objects in prefab. {0}", prefab->ToString()); return nullptr; } Actor* root = nullptr; @@ -172,7 +172,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac } if (!root) { - LOG(Warning, "Missing prefab root object."); + LOG(Warning, "Missing prefab root object. {0}", prefab->ToString()); return nullptr; } diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 1e9ea5b79..c6311cd7a 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -817,7 +817,7 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMe NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", msgData.PrefabId.ToString()); return; } - prefabInstance = PrefabManager::SpawnPrefab(prefab, Transform::Identity, nullptr, nullptr); + prefabInstance = PrefabManager::SpawnPrefab(prefab, nullptr, nullptr); if (!prefabInstance) { NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", msgData.PrefabId.ToString()); From e0a085adfef9a6955528b28a567dd3bc4f276834 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 10:47:22 +0100 Subject: [PATCH 35/72] Add support for loading prefab instance if the root was changed or deleted #2050 --- Source/Engine/Content/Asset.cpp | 4 + Source/Engine/Level/Level.cpp | 16 +- Source/Engine/Level/Prefabs/Prefab.cpp | 5 - Source/Engine/Level/Prefabs/PrefabManager.cpp | 42 +-- Source/Engine/Level/Prefabs/PrefabManager.h | 4 +- Source/Engine/Level/SceneObjectsFactory.cpp | 251 ++++++++++++------ Source/Engine/Level/SceneObjectsFactory.h | 6 + Source/Engine/Serialization/JsonWriter.cpp | 2 +- Source/Engine/Tests/TestPrefabs.cpp | 145 ++++++++++ 9 files changed, 360 insertions(+), 115 deletions(-) diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index d1c2a57d7..de710cb7d 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -310,6 +310,10 @@ void Asset::ChangeID(const Guid& newId) if (!IsVirtual()) CRASH; + // ID has to be unique + if (Content::GetAsset(newId) != nullptr) + CRASH; + const Guid oldId = _id; ManagedScriptingObject::ChangeID(newId); Content::onAssetChangeId(this, oldId, newId); diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 5f1432d31..cc91e48ca 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -934,13 +934,13 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou // Loaded scene objects list CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); - const int32 objectsCount = (int32)data.Size(); - sceneObjects->Resize(objectsCount); + const int32 dataCount = (int32)data.Size(); + sceneObjects->Resize(dataCount); sceneObjects->At(0) = scene; // Spawn all scene objects SceneObjectsFactory::Context context(modifier.Value); - context.Async = JobSystem::GetThreadsCount() > 1 && objectsCount > 10; + context.Async = JobSystem::GetThreadsCount() > 1 && dataCount > 10; { PROFILE_CPU_NAMED("Spawn"); SceneObject** objects = sceneObjects->Get(); @@ -963,12 +963,12 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou } else SceneObjectsFactory::HandleObjectDeserializationError(stream); - }, objectsCount - 1); + }, dataCount - 1); ScenesLock.Lock(); } else { - for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene + for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene { auto& stream = data[i]; auto obj = SceneObjectsFactory::Spawn(context, stream); @@ -1012,13 +1012,13 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou SceneObjectsFactory::Deserialize(context, obj, data[i]); idMapping = nullptr; } - }, objectsCount - 1); + }, dataCount - 1); ScenesLock.Lock(); } else { Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); - for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene + for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene { auto& objData = data[i]; auto obj = objects[i]; @@ -1049,7 +1049,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou PROFILE_CPU_NAMED("Initialize"); SceneObject** objects = sceneObjects->Get(); - for (int32 i = 0; i < objectsCount; i++) + for (int32 i = 0; i < dataCount; i++) { SceneObject* obj = objects[i]; if (obj) diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index 56bf6a1e2..25a2bd086 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -87,17 +87,12 @@ SceneObject* Prefab::GetDefaultInstance(const Guid& objectId) const auto result = GetDefaultInstance(); if (!result) return nullptr; - if (objectId.IsValid()) { SceneObject* object; if (ObjectsCache.TryGet(objectId, object)) - { - // Actor or Script return object; - } } - return result; } diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index d014f58d7..d99a89e39 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -94,8 +94,8 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac LOG(Warning, "Waiting for prefab asset be loaded failed. {0}", prefab->ToString()); return nullptr; } - const int32 objectsCount = prefab->ObjectsCount; - if (objectsCount == 0) + const int32 dataCount = prefab->ObjectsCount; + if (dataCount == 0) { LOG(Warning, "Prefab has no objects. {0}", prefab->ToString()); return nullptr; @@ -107,7 +107,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac // Prepare CollectionPoolCache::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get(); - sceneObjects->Resize(objectsCount); + sceneObjects->Resize(dataCount); CollectionPoolCache::ScopeCache modifier = Cache::ISerializeModifier.Get(); modifier->EngineBuild = prefab->DataEngineBuild; modifier->IdsMapping.EnsureCapacity(prefab->ObjectsIds.Count() * 4); @@ -126,7 +126,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac // Deserialize prefab objects auto prevIdMapping = Scripting::ObjectsLookupIdMapping.Get(); Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); - for (int32 i = 0; i < objectsCount; i++) + for (int32 i = 0; i < dataCount; i++) { auto& stream = data[i]; SceneObject* obj = SceneObjectsFactory::Spawn(context, stream); @@ -145,7 +145,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac SceneObjectsFactory::SynchronizeNewPrefabInstances(context, prefabSyncData); Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); } - for (int32 i = 0; i < objectsCount; i++) + for (int32 i = 0; i < dataCount; i++) { auto& stream = data[i]; SceneObject* obj = sceneObjects->At(i); @@ -154,20 +154,29 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac } Scripting::ObjectsLookupIdMapping.Set(prevIdMapping); - // Pick prefab root object - if (sceneObjects->IsEmpty()) + // Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it) + if (withSynchronization) { - LOG(Warning, "No valid objects in prefab. {0}", prefab->ToString()); - return nullptr; + // TODO: resave and force sync scenes during game cooking so this step could be skipped in game + SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData); } + + // Pick prefab root object Actor* root = nullptr; const Guid prefabRootObjectId = prefab->GetRootObjectId(); - for (int32 i = 0; i < objectsCount; i++) + for (int32 i = 0; i < dataCount && !root; i++) { if (JsonTools::GetGuid(data[i], "ID") == prefabRootObjectId) - { root = dynamic_cast(sceneObjects->At(i)); - break; + } + if (!root) + { + // Fallback to the first actor that has no parent + for (int32 i = 0; i < sceneObjects->Count() && !root; i++) + { + SceneObject* obj = sceneObjects->At(i); + if (obj && !obj->GetParent()) + root = dynamic_cast(obj); } } if (!root) @@ -176,13 +185,6 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac return nullptr; } - // Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it) - if (withSynchronization) - { - // TODO: resave and force sync scenes during game cooking so this step could be skipped in game - SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData); - } - // Prepare parent linkage for prefab root actor if (root->_parent) root->_parent->Children.Remove(root); @@ -264,7 +266,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac } // Link objects to prefab (only deserialized from prefab data) - for (int32 i = 0; i < objectsCount; i++) + for (int32 i = 0; i < dataCount; i++) { auto& stream = data[i]; SceneObject* obj = sceneObjects->At(i); diff --git a/Source/Engine/Level/Prefabs/PrefabManager.h b/Source/Engine/Level/Prefabs/PrefabManager.h index e5600bac4..ad990da9f 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.h +++ b/Source/Engine/Level/Prefabs/PrefabManager.h @@ -89,7 +89,7 @@ API_CLASS(Static) class FLAXENGINE_API PrefabManager /// The options output objects cache that can be filled with prefab object id mapping to deserialized object (actor or script). /// True if perform prefab changes synchronization for the spawned objects. It will check if need to add new objects due to nested prefab modifications. /// The created actor (root) or null if failed. - static Actor* SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary* objectsCache, bool withSynchronization = false); + static Actor* SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary* objectsCache, bool withSynchronization = true); /// /// Spawns the instance of the prefab objects. If parent actor is specified then created actors are fully initialized (OnLoad event and BeginPlay is called if parent actor is already during gameplay). @@ -100,7 +100,7 @@ API_CLASS(Static) class FLAXENGINE_API PrefabManager /// The options output objects cache that can be filled with prefab object id mapping to deserialized object (actor or script). /// True if perform prefab changes synchronization for the spawned objects. It will check if need to add new objects due to nested prefab modifications. /// The created actor (root) or null if failed. - static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary* objectsCache, bool withSynchronization = false); + static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary* objectsCache, bool withSynchronization = true); #if USE_EDITOR diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index df30257f9..eeac38ccf 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -138,7 +138,7 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, const ISerializable::D auto prefab = Content::LoadAsync(prefabId); if (prefab == nullptr) { - LOG(Warning, "Missing prefab with id={0}.", prefabId); + LOG(Warning, "Missing prefab {0}.", prefabId); return nullptr; } if (prefab->WaitForLoaded()) @@ -264,7 +264,7 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria auto prefab = Content::LoadAsync(prefabId); if (prefab == nullptr) { - LOG(Warning, "Missing prefab with id={0}.", prefabId); + LOG(Warning, "Missing prefab {0}.", prefabId); return; } if (prefab->WaitForLoaded()) @@ -424,9 +424,6 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn ASSERT(count <= data.SceneObjects.Count()); for (int32 i = 0; i < count; i++) { - const SceneObject* obj = data.SceneObjects[i]; - if (!obj) - continue; const auto& stream = data.Data[i]; Guid prefabObjectId, prefabId; if (!JsonTools::GetGuidIfValid(prefabObjectId, stream, "PrefabObjectID")) @@ -436,14 +433,15 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn Guid parentId = JsonTools::GetGuid(stream, "ParentID"); for (int32 j = i - 1; j >= 0; j--) { - // Find instance ID of the parent to this object (use data in json for relationship) + // Find ID of the parent to this object (use data in json for relationship) if (parentId == JsonTools::GetGuid(data.Data[j], "ID") && data.SceneObjects[j]) { parentId = data.SceneObjects[j]->GetID(); break; } } - const Guid id = obj->GetID(); + const SceneObject* obj = data.SceneObjects[i]; + const Guid id = obj ? obj->GetID() : JsonTools::GetGuid(stream, "ID"); auto prefab = Content::LoadAsync(prefabId); // Check if it's parent is in the same prefab @@ -459,6 +457,8 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn auto& e = context.Instances.AddOne(); e.Prefab = prefab; e.RootId = id; + e.RootIndex = i; + e.StatIndex = i; } context.ObjectToInstance[id] = index; @@ -490,6 +490,85 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping); data.InitialCount = data.SceneObjects.Count(); + // Recreate any missing prefab root objects that were deleted (eg. spawned prefab got its root changed and deleted so old prefab instance needs to respawn it) + for (int32 instanceIndex = 0; instanceIndex < context.Instances.Count(); instanceIndex++) + { + PrefabInstance& instance = context.Instances[instanceIndex]; + SceneObject* root = data.SceneObjects[instance.RootIndex]; + if (!root && instance.Prefab) + { + instance.FixRootParent = true; + + // Check if current prefab root existed in the deserialized data + const auto& oldRootData = data.Data[instance.RootIndex]; + const Guid oldRootId = JsonTools::GetGuid(oldRootData, "ID"); + const Guid prefabObjectId = JsonTools::GetGuid(oldRootData, "PrefabObjectID"); + const Guid prefabRootId = instance.Prefab->GetRootObjectId(); + Guid id; + int32 idInstance = -1; + bool syncNewRoot = false; + if (instance.IdsMapping.TryGet(prefabRootId, id) && context.ObjectToInstance.TryGet(id, idInstance) && idInstance == instanceIndex) + { + // Update the missing root with the valid object from this prefab instance + LOG(Warning, "Changed prefab instance root from ID={0}, PrefabObjectID={1} to ID={2}, PrefabObjectID={3} ({4})", instance.RootId, prefabObjectId, id, prefabRootId, instance.Prefab->ToString()); + } + else + { + LOG(Warning, "Missing prefab instance root (ID={0}, PrefabObjectID={1}, {2})", instance.RootId, prefabObjectId, instance.Prefab->ToString()); + + // Get prefab object data from the prefab + const ISerializable::DeserializeStream* prefabData; + if (!instance.Prefab->ObjectsDataCache.TryGet(prefabRootId, prefabData)) + { + LOG(Warning, "Missing object {1} data in prefab {0}.", instance.Prefab->ToString(), prefabObjectId); + continue; + } + + // Map prefab object ID to the new prefab object instance + id = Guid::New(); + data.Modifier->IdsMapping[prefabRootId] = id; + + // Create prefab instance (recursive prefab loading to support nested prefabs) + root = Spawn(context, *prefabData); + if (!root) + { + LOG(Warning, "Failed to create object {1} from prefab {0}.", instance.Prefab->ToString(), prefabRootId); + continue; + } + + // Register object + root->RegisterObject(); + data.SceneObjects.Add(root); + auto& newObj = data.NewObjects.AddOne(); + newObj.Prefab = instance.Prefab; + newObj.PrefabData = prefabData; + newObj.PrefabObjectId = prefabRootId; + newObj.Id = id; + context.ObjectToInstance[id] = instanceIndex; + syncNewRoot = true; + } + + // Update prefab root info + instance.RootId = id; + instance.RootIndex = data.SceneObjects.Find(Scripting::FindObject(id)); + CHECK(instance.RootIndex != -1); + + // Remap removed prefab root into the current root (can be different type but is needed for proper hierarchy linkage) + instance.IdsMapping[prefabObjectId] = id; + instance.IdsMapping[prefabRootId] = id; + instance.IdsMapping[oldRootId] = id; + data.Modifier->IdsMapping[prefabObjectId] = id; + data.Modifier->IdsMapping[prefabRootId] = id; + data.Modifier->IdsMapping[oldRootId] = id; + + // Add any sub-objects that are missing (in case new root was created) + if (syncNewRoot) + { + SynchronizeNewPrefabInstances(context, data, instance.Prefab, (Actor*)root, prefabRootId, instance.RootIndex, oldRootData); + } + } + } + // Check all actors with prefab linkage for adding missing objects for (int32 i = 0; i < data.InitialCount; i++) { @@ -504,13 +583,12 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab continue; if (!JsonTools::GetGuidIfValid(actorId, stream, "ID")) continue; - const Guid actorParentId = JsonTools::GetGuid(stream, "ParentID"); // Load prefab auto prefab = Content::LoadAsync(prefabId); if (prefab == nullptr) { - LOG(Warning, "Missing prefab with id={0}.", prefabId); + LOG(Warning, "Missing prefab {0}.", prefabId); continue; } if (prefab->WaitForLoaded()) @@ -519,66 +597,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab continue; } - // Check for RemovedObjects list - const auto removedObjects = SERIALIZE_FIND_MEMBER(stream, "RemovedObjects"); - - // Check if the given actor has new children or scripts added (inside the prefab that it uses) - // TODO: consider caching prefab objects structure maybe to boost this logic? - for (auto it = prefab->ObjectsDataCache.Begin(); it.IsNotEnd(); ++it) - { - // Use only objects that are linked to the current actor - const Guid parentId = JsonTools::GetGuid(*it->Value, "ParentID"); - if (parentId != actorPrefabObjectId) - continue; - - // Skip if object was marked to be removed per instance - const Guid prefabObjectId = JsonTools::GetGuid(*it->Value, "ID"); - if (removedObjects != stream.MemberEnd()) - { - auto& list = removedObjects->value; - const int32 size = static_cast(list.Size()); - bool removed = false; - for (int32 j = 0; j < size; j++) - { - if (JsonTools::GetGuid(list[j]) == prefabObjectId) - { - removed = true; - break; - } - } - if (removed) - continue; - } - - // Use only objects that are missing - bool spawned = false; - for (int32 j = i + 1; j < data.InitialCount; j++) - { - const auto& jData = data.Data[j]; - const Guid jParentId = JsonTools::GetGuid(jData, "ParentID"); - //if (jParentId == actorParentId) - // break; - //if (jParentId != actorId) - // continue; - const Guid jPrefabObjectId = JsonTools::GetGuid(jData, "PrefabObjectID"); - if (jPrefabObjectId != prefabObjectId) - continue; - - // This object exists in the saved scene objects list - spawned = true; - break; - } - if (spawned) - continue; - - // Map prefab object id to this actor's prefab instance so the new objects gets added to it - context.SetupIdsMapping(actor, data.Modifier); - data.Modifier->IdsMapping[actorPrefabObjectId] = actor->GetID(); - Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping); - - // Create instance (including all children) - SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId); - } + SynchronizeNewPrefabInstances(context, data, prefab, actor, actorPrefabObjectId, i, stream); } Scripting::ObjectsLookupIdMapping.Set(nullptr); @@ -605,7 +624,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn auto prefab = Content::LoadAsync(prefabId); if (prefab == nullptr) { - LOG(Warning, "Missing prefab with id={0}.", prefabId); + LOG(Warning, "Missing prefab {0}.", prefabId); continue; } if (prefab->WaitForLoaded()) @@ -626,7 +645,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn // Invalid connection object found! LOG(Info, "Object {0} has invalid parent object {4} -> {5} (PrefabObjectID: {1}, PrefabID: {2}, Path: {3})", obj->GetSceneObjectId(), prefabObjectId, prefab->GetID(), prefab->GetPath(), parentPrefabObjectId, actualParentPrefabId); - // Map actual prefab object id to the current scene objects collection + // Map actual prefab object ID to the current scene objects collection context.SetupIdsMapping(obj, data.Modifier); data.Modifier->IdsMapping.TryGet(actualParentPrefabId, actualParentPrefabId); @@ -646,15 +665,12 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn if (i != 0) { const auto defaultInstance = prefab->GetDefaultInstance(obj->GetPrefabObjectID()); - if (defaultInstance) - { - obj->SetOrderInParent(defaultInstance->GetOrderInParent()); - } + const int32 order = defaultInstance ? defaultInstance->GetOrderInParent() : -1; + if (order != -1) + obj->SetOrderInParent(order); } } - Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping); - // Synchronize new prefab objects for (int32 i = 0; i < data.NewObjects.Count(); i++) { @@ -662,20 +678,97 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn auto& newObj = data.NewObjects[i]; // Deserialize object with prefab data + Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping); Deserialize(context, obj, *(ISerializable::DeserializeStream*)newObj.PrefabData); obj->LinkPrefab(newObj.Prefab->GetID(), newObj.PrefabObjectId); // Preserve order in parent (values from prefab are used) const auto defaultInstance = newObj.Prefab->GetDefaultInstance(newObj.PrefabObjectId); - if (defaultInstance) + const int32 order = defaultInstance ? defaultInstance->GetOrderInParent() : -1; + if (order != -1) + obj->SetOrderInParent(order); + } + + // Setup hierarchy for the prefab instances (ensure any new objects are connected) + for (const auto& instance : context.Instances) + { + const auto& prefabStartData = data.Data[instance.StatIndex]; + Guid prefabStartParentId; + if (instance.FixRootParent && JsonTools::GetGuidIfValid(prefabStartParentId, prefabStartData, "ParentID")) { - obj->SetOrderInParent(defaultInstance->GetOrderInParent()); + auto* root = data.SceneObjects[instance.RootIndex]; + const auto rootParent = Scripting::FindObject(prefabStartParentId); + root->SetParent(rootParent, false); } } Scripting::ObjectsLookupIdMapping.Set(nullptr); } +void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& actorPrefabObjectId, int32 i, const ISerializable::DeserializeStream& stream) +{ + // Check for RemovedObjects list + const auto removedObjects = SERIALIZE_FIND_MEMBER(stream, "RemovedObjects"); + + // Check if the given actor has new children or scripts added (inside the prefab that it uses) + // TODO: consider caching prefab objects structure maybe to boost this logic? + for (auto it = prefab->ObjectsDataCache.Begin(); it.IsNotEnd(); ++it) + { + // Use only objects that are linked to the current actor + const Guid parentId = JsonTools::GetGuid(*it->Value, "ParentID"); + if (parentId != actorPrefabObjectId) + continue; + + // Skip if object was marked to be removed per instance + const Guid prefabObjectId = JsonTools::GetGuid(*it->Value, "ID"); + if (removedObjects != stream.MemberEnd()) + { + auto& list = removedObjects->value; + const int32 size = static_cast(list.Size()); + bool removed = false; + for (int32 j = 0; j < size; j++) + { + if (JsonTools::GetGuid(list[j]) == prefabObjectId) + { + removed = true; + break; + } + } + if (removed) + continue; + } + + // Use only objects that are missing + bool spawned = false; + int32 childSearchStart = i + 1; // Objects are serialized with parent followed by its children + int32 instanceIndex = -1; + if (context.ObjectToInstance.TryGet(actor->GetID(), instanceIndex) && context.Instances[instanceIndex].Prefab == prefab) + { + // Start searching from the beginning of that prefab instance (eg. in case prefab objects were reordered) + childSearchStart = Math::Min(childSearchStart, context.Instances[instanceIndex].StatIndex); + } + for (int32 j = childSearchStart; j < data.InitialCount; j++) + { + if (JsonTools::GetGuid(data.Data[j], "PrefabObjectID") == prefabObjectId) + { + // This object exists in the saved scene objects list + spawned = true; + break; + } + } + if (spawned) + continue; + + // Map prefab object ID to this actor's prefab instance so the new objects gets added to it + context.SetupIdsMapping(actor, data.Modifier); + data.Modifier->IdsMapping[actorPrefabObjectId] = actor->GetID(); + Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping); + + // Create instance (including all children) + SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId); + } +} + void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId) { PROFILE_CPU_NAMED("SynchronizeNewPrefabInstance"); diff --git a/Source/Engine/Level/SceneObjectsFactory.h b/Source/Engine/Level/SceneObjectsFactory.h index 86fd5364a..e152c21b2 100644 --- a/Source/Engine/Level/SceneObjectsFactory.h +++ b/Source/Engine/Level/SceneObjectsFactory.h @@ -15,8 +15,11 @@ class FLAXENGINE_API SceneObjectsFactory public: struct PrefabInstance { + int32 StatIndex; + int32 RootIndex; Guid RootId; Prefab* Prefab; + bool FixRootParent = false; Dictionary IdsMapping; }; @@ -70,6 +73,8 @@ public: struct PrefabSyncData { friend SceneObjectsFactory; + friend class PrefabManager; + // The created scene objects. Collection can be modified (eg. for spawning missing objects). Array& SceneObjects; // The scene objects data. @@ -124,5 +129,6 @@ public: static void SynchronizePrefabInstances(Context& context, PrefabSyncData& data); private: + static void SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& actorPrefabObjectId, int32 i, const ISerializable::DeserializeStream& stream); static void SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId); }; diff --git a/Source/Engine/Serialization/JsonWriter.cpp b/Source/Engine/Serialization/JsonWriter.cpp index 35e4412d4..27ecb672b 100644 --- a/Source/Engine/Serialization/JsonWriter.cpp +++ b/Source/Engine/Serialization/JsonWriter.cpp @@ -481,7 +481,7 @@ void JsonWriter::SceneObject(::SceneObject* obj) } else { - LOG(Warning, "Missing prefab with id={0}.", obj->GetPrefabID()); + LOG(Warning, "Missing prefab {0}.", obj->GetPrefabID()); } } diff --git a/Source/Engine/Tests/TestPrefabs.cpp b/Source/Engine/Tests/TestPrefabs.cpp index 211fea930..ec3eb4af6 100644 --- a/Source/Engine/Tests/TestPrefabs.cpp +++ b/Source/Engine/Tests/TestPrefabs.cpp @@ -412,4 +412,149 @@ TEST_CASE("Prefabs") Content::DeleteAsset(prefabNested); Content::DeleteAsset(prefabBase); } + SECTION("Test Loading Nested Prefab After Changing and Deleting Root") + { + // https://github.com/FlaxEngine/FlaxEngine/issues/2050 + + // Create base prefab with 1 object + AssetReference prefabBase = Content::CreateVirtualAsset(); + REQUIRE(prefabBase); + Guid id; + Guid::Parse("3b3334524c696dcfa93cabacd2a4f404", id); + prefabBase->ChangeID(id); + auto prefabBaseInit = prefabBase->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\"," + "\"TypeName\": \"FlaxEngine.DirectionalLight\"," + "\"Name\": \"New Root\"" + "}," + "{" + "\"ID\": \"f8fbee1349f749396ab6c2ad34f3afec\"," + "\"TypeName\": \"FlaxEngine.Camera\"," + "\"Name\": \"Child 1\"," + "\"ParentID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\"" + "}," + "{" + "\"ID\": \"5632561847cf96fe2e8919848b7eca79\"," + "\"TypeName\": \"FlaxEngine.EmptyActor\"," + "\"Name\": \"Child 1.Child\"," + "\"ParentID\": \"f8fbee1349f749396ab6c2ad34f3afec\"" + "}," + "{" + "\"ID\": \"4e4f3a1847cf96fe2e8919848b7eca79\"," + "\"TypeName\": \"FlaxEngine.UICanvas\"," + "\"Name\": \"Child 2\"," + "\"ParentID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\"" + "}" + "]"); + REQUIRE(!prefabBaseInit); + + // Create nested prefab but with 'old' state where root object is different + AssetReference prefabNested1 = Content::CreateVirtualAsset(); + REQUIRE(prefabNested1); + Guid::Parse("671447e947cbd2deea018a8377636ce6", id); + prefabNested1->ChangeID(id); + auto prefabNestedInit1 = prefabNested1->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"597ab8ea43a5c58b8d06f58f9364d261\"," + "\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\"," + "\"PrefabObjectID\": \"589bcfaa4bd1a53435129480e5bbdb3b\"," + "\"Name\": \"Old Root\"" + "}," + "{" + "\"ID\": \"1a6228d84897ff3b2f444ea263c3657e\"," + "\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\"," + "\"PrefabObjectID\": \"f8fbee1349f749396ab6c2ad34f3afec\"," + "\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\"" + "}," + "{" + "\"ID\": \"1212124f4d913e58eb35ab8b0b7e2eef\"," + "\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\"," + "\"PrefabObjectID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\"," + "\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\"," + "\"Name\": \"New Root\"" + "}," + "{" + "\"ID\": \"468028d84897ff3b2f444ea263c3657e\"," + "\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\"," + "\"PrefabObjectID\": \"2468902349f749396ab6c2ad34f3afec\"," + "\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\"," + "\"Name\": \"Old Child\"" + "}" + "]"); + REQUIRE(!prefabNestedInit1); + + // Create nested prefab but with 'old' state where root object is different and doesn't exist anymore + AssetReference prefabNested2 = Content::CreateVirtualAsset(); + REQUIRE(prefabNested2); + Guid::Parse("b71447e947cbd2deea018a8377636ce6", id); + prefabNested2->ChangeID(id); + auto prefabNestedInit2 = prefabNested2->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"597ab8ea43a5c58b8d06f58f9364d261\"," + "\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\"," + "\"PrefabObjectID\": \"589bcfaa4bd1a53435129480e5bbdb3b\"," + "\"Name\": \"Old Root\"" + "}," + "{" + "\"ID\": \"1a6228d84897ff3b2f444ea263c3657e\"," + "\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\"," + "\"PrefabObjectID\": \"f8fbee1349f749396ab6c2ad34f3afec\"," + "\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\"" + "}," + "{" + "\"ID\": \"468028d84897ff3b2f444ea263c3657e\"," + "\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\"," + "\"PrefabObjectID\": \"2468902349f749396ab6c2ad34f3afec\"," + "\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\"," + "\"Name\": \"Old Child\"" + "}" + "]"); + REQUIRE(!prefabNestedInit2); + + // Spawn test instances of both prefabs + ScriptingObjectReference instanceBase = PrefabManager::SpawnPrefab(prefabBase); + ScriptingObjectReference instanceNested1 = PrefabManager::SpawnPrefab(prefabNested1); + ScriptingObjectReference instanceNested2 = PrefabManager::SpawnPrefab(prefabNested2); + + // Verify scenario + REQUIRE(instanceBase); + REQUIRE(instanceBase->GetName() == TEXT("New Root")); + REQUIRE(instanceBase->GetChildrenCount() == 2); + REQUIRE(instanceBase->Children[0]->GetName() == TEXT("Child 1")); + REQUIRE(instanceBase->Children[0]->GetChildrenCount() == 1); + REQUIRE(instanceBase->Children[1]->GetName() == TEXT("Child 2")); + REQUIRE(instanceBase->Children[1]->GetChildrenCount() == 0); + REQUIRE(instanceBase->Children[0]->Children[0]->GetName() == TEXT("Child 1.Child")); + REQUIRE(instanceBase->Children[0]->Children[0]->GetChildrenCount() == 0); + REQUIRE(instanceNested1); + REQUIRE(instanceNested1->GetName() == TEXT("New Root")); + REQUIRE(instanceNested1->GetChildrenCount() == 2); + REQUIRE(instanceNested1->Children[0]->GetName() == TEXT("Child 1")); + REQUIRE(instanceNested1->Children[0]->GetChildrenCount() == 1); + REQUIRE(instanceNested1->Children[1]->GetName() == TEXT("Child 2")); + REQUIRE(instanceNested1->Children[1]->GetChildrenCount() == 0); + REQUIRE(instanceNested1->Children[0]->Children[0]->GetName() == TEXT("Child 1.Child")); + REQUIRE(instanceNested1->Children[0]->Children[0]->GetChildrenCount() == 0); + REQUIRE(instanceNested2); + REQUIRE(instanceNested2->GetName() == TEXT("New Root")); + REQUIRE(instanceNested2->GetChildrenCount() == 2); + REQUIRE(instanceNested2->Children[0]->GetName() == TEXT("Child 1")); + REQUIRE(instanceNested2->Children[0]->GetChildrenCount() == 1); + REQUIRE(instanceNested2->Children[1]->GetName() == TEXT("Child 2")); + REQUIRE(instanceNested2->Children[1]->GetChildrenCount() == 0); + REQUIRE(instanceNested2->Children[0]->Children[0]->GetName() == TEXT("Child 1.Child")); + REQUIRE(instanceNested2->Children[0]->Children[0]->GetChildrenCount() == 0); + + // Cleanup + instanceNested2->DeleteObject(); + instanceNested1->DeleteObject(); + instanceBase->DeleteObject(); + Content::DeleteAsset(prefabNested2); + Content::DeleteAsset(prefabNested1); + Content::DeleteAsset(prefabBase); + } } From a1e13cd2c8a912a600396f675682fb8315e1a642 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 10:47:49 +0100 Subject: [PATCH 36/72] Fix `MissingScript` to be added only when object type exists (skip for prefab instances) --- Source/Engine/Level/SceneObjectsFactory.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index eeac38ccf..36681d64b 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -317,11 +317,13 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable:: { #if USE_EDITOR // Add dummy script - auto* dummyScript = parent->AddScript(); const auto typeNameMember = value.FindMember("TypeName"); if (typeNameMember != value.MemberEnd() && typeNameMember->value.IsString()) + { + auto* dummyScript = parent->AddScript(); dummyScript->MissingTypeName = typeNameMember->value.GetString(); - dummyScript->Data = MoveTemp(bufferStr); + dummyScript->Data = MoveTemp(bufferStr); + } #endif LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName()); } From b87a7d16fb57f02c9430c539b2f56f6dc75bcecf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 11:03:58 +0100 Subject: [PATCH 37/72] Move managed code into native impl for #2063 --- Source/Engine/Level/Actors/BoxCollider.cs | 56 ------------------- .../Engine/Physics/Colliders/BoxCollider.cpp | 27 +++++++++ Source/Engine/Physics/Colliders/BoxCollider.h | 5 ++ 3 files changed, 32 insertions(+), 56 deletions(-) delete mode 100644 Source/Engine/Level/Actors/BoxCollider.cs diff --git a/Source/Engine/Level/Actors/BoxCollider.cs b/Source/Engine/Level/Actors/BoxCollider.cs deleted file mode 100644 index cfb6e9dde..000000000 --- a/Source/Engine/Level/Actors/BoxCollider.cs +++ /dev/null @@ -1,56 +0,0 @@ -#if FLAX_EDITOR -using FlaxEditor.CustomEditors; -using FlaxEditor.CustomEditors.Dedicated; -#endif - -namespace FlaxEngine -{ - partial class BoxCollider - { - private void BoxExcluding(Actor target, ref BoundingBox output, Actor excluded) - { - foreach (Actor child in target.Children) - { - if (child == excluded) - { - continue; - } - - output = BoundingBox.Merge(output, child.Box); - BoxExcluding(child, ref output, excluded); - } - } - - /// - /// Resizes the box collider based on the bounds of it's parent. - /// - public void AutoResize() - { - if (Parent is Scene) - { - return; - } - - LocalPosition = Vector3.Zero; - - Vector3 parentScale = Parent.Scale; - BoundingBox parentBox = Parent.Box; - BoxExcluding(Parent, ref parentBox, this); - - Vector3 parentSize = parentBox.Size; - Vector3 parentCenter = parentBox.Center - Parent.Position; - - // Avoid division by zero - if (parentScale.X == 0 || parentScale.Y == 0 || parentScale.Z == 0) - { - return; - } - - Size = parentSize / parentScale; - Center = parentCenter / parentScale; - - // Undo Rotation - Orientation *= Quaternion.Invert(Orientation); - } - } -} diff --git a/Source/Engine/Physics/Colliders/BoxCollider.cpp b/Source/Engine/Physics/Colliders/BoxCollider.cpp index b80b68de0..d74b3efba 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.cpp +++ b/Source/Engine/Physics/Colliders/BoxCollider.cpp @@ -2,6 +2,7 @@ #include "BoxCollider.h" #include "Engine/Physics/PhysicsBackend.h" +#include "Engine/Level/Scene/Scene.h" BoxCollider::BoxCollider(const SpawnParams& params) : Collider(params) @@ -19,6 +20,32 @@ void BoxCollider::SetSize(const Float3& value) UpdateBounds(); } +void BoxCollider::AutoResize() +{ + Actor* parent = GetParent(); + if (Cast(parent)) + return; + + // Get bounds of all siblings (excluding itself) + const Vector3 parentScale = parent->GetScale(); + if (parentScale.IsAnyZero()) + return; // Avoid division by zero + BoundingBox parentBox = parent->GetBox(); + for (const Actor* sibling : parent->Children) + { + if (sibling != this) + BoundingBox::Merge(parentBox, sibling->GetBoxWithChildren(), parentBox); + } + const Vector3 parentSize = parentBox.GetSize(); + const Vector3 parentCenter = parentBox.GetCenter() - parent->GetPosition(); + + // Update bounds + SetLocalPosition(Vector3::Zero); + SetSize(parentSize / parentScale); + SetCenter(parentCenter / parentScale); + SetOrientation(GetOrientation() * Quaternion::Invert(GetOrientation())); +} + #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" diff --git a/Source/Engine/Physics/Colliders/BoxCollider.h b/Source/Engine/Physics/Colliders/BoxCollider.h index 5bcc21b45..3c15ce640 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.h +++ b/Source/Engine/Physics/Colliders/BoxCollider.h @@ -43,6 +43,11 @@ public: return _bounds; } + /// + /// Resizes the collider based on the bounds of it's parent to contain it whole (including any siblings). + /// + API_FUNCTION() void AutoResize(); + public: // [Collider] #if USE_EDITOR From 51fc4c68cdb8382579af5fe07798ff703b1c586c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 11:04:24 +0100 Subject: [PATCH 38/72] Use tooltip from native function docs and support multiple colliders selected at once #2063 --- .../SceneGraph/Actors/BoxColliderNode.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/BoxColliderNode.cs b/Source/Editor/SceneGraph/Actors/BoxColliderNode.cs index 8c00318db..566cefb5e 100644 --- a/Source/Editor/SceneGraph/Actors/BoxColliderNode.cs +++ b/Source/Editor/SceneGraph/Actors/BoxColliderNode.cs @@ -7,15 +7,12 @@ using Real = System.Single; #endif using FlaxEngine; - -#if FLAX_EDITOR using FlaxEditor.CustomEditors.Dedicated; using FlaxEditor.CustomEditors; -#endif +using FlaxEditor.Scripting; namespace FlaxEditor.SceneGraph.Actors { -#if FLAX_EDITOR /// /// Dedicated custom editor for BoxCollider objects. /// @@ -26,14 +23,20 @@ namespace FlaxEditor.SceneGraph.Actors public override void Initialize(LayoutElementsContainer layout) { base.Initialize(layout); - layout.Space(20f); - var autoResizeButton = layout.Button("Resize to Fit", "Resize the box collider to fit it's parent's bounds."); - BoxCollider collider = Values[0] as BoxCollider; - autoResizeButton.Button.Clicked += collider.AutoResize; + layout.Space(20f); + layout.Button("Resize to Fit", Editor.Instance.CodeDocs.GetTooltip(new ScriptMemberInfo(typeof(BoxCollider).GetMethod("AutoResize")))).Button.Clicked += OnResizeClicked; + } + + private void OnResizeClicked() + { + foreach (var value in Values) + { + if (value is BoxCollider collider) + collider.AutoResize(); + } } } -#endif /// /// Scene tree node for actor type. @@ -67,8 +70,8 @@ namespace FlaxEditor.SceneGraph.Actors public override void PostSpawn() { base.PostSpawn(); - BoxCollider boxCollider = Actor as BoxCollider; - boxCollider.AutoResize(); + + ((BoxCollider)Actor).AutoResize(); } } } From fe1a65565420cd9b3b6e88fea00e044b1b0fe8a1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 13:57:16 +0100 Subject: [PATCH 39/72] Add support for displaying and reverting array values to prefab value in properties panel #1548 --- Source/Editor/CustomEditors/CustomEditor.cs | 77 +++++++++---------- .../CustomEditors/Editors/CollectionEditor.cs | 31 +++++++- 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index 9de330213..a519b1da1 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -7,9 +7,8 @@ using FlaxEditor.CustomEditors.GUI; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Json; using FlaxEngine.Utilities; -using Newtonsoft.Json; -using JsonSerializer = FlaxEngine.Json.JsonSerializer; namespace FlaxEditor.CustomEditors { @@ -386,22 +385,22 @@ namespace FlaxEditor.CustomEditors LinkedLabel = label; } - private void RevertDiffToDefault(CustomEditor editor) - { - if (editor.ChildrenEditors.Count == 0) - { - // Skip if no change detected - if (!editor.Values.IsDefaultValueModified) - return; + /// + /// If true, the value reverting to default/reference will be handled via iteration over children editors, instead of for a whole object at once. + /// + public virtual bool RevertValueWithChildren => ChildrenEditors.Count != 0; - editor.SetValueToDefault(); + private void RevertDiffToDefault() + { + if (RevertValueWithChildren) + { + foreach (var child in ChildrenEditors) + child.RevertDiffToDefault(); } else { - for (int i = 0; i < editor.ChildrenEditors.Count; i++) - { - RevertDiffToDefault(editor.ChildrenEditors[i]); - } + if (Values.IsDefaultValueModified) + SetValueToDefault(); } } @@ -414,11 +413,6 @@ namespace FlaxEditor.CustomEditors { if (!Values.IsDefaultValueModified) return false; - - // Skip array items (show diff only on a bottom level properties and fields) - if (ParentEditor is Editors.ArrayEditor) - return false; - return true; } } @@ -430,7 +424,7 @@ namespace FlaxEditor.CustomEditors { if (!Values.HasDefaultValue) return; - RevertDiffToDefault(this); + RevertDiffToDefault(); } /// @@ -468,22 +462,17 @@ namespace FlaxEditor.CustomEditors } } - private void RevertDiffToReference(CustomEditor editor) + private void RevertDiffToReference() { - if (editor.ChildrenEditors.Count == 0) + if (RevertValueWithChildren) { - // Skip if no change detected - if (!editor.Values.IsReferenceValueModified) - return; - - editor.SetValueToReference(); + foreach (var child in ChildrenEditors) + child.RevertDiffToReference(); } else { - for (int i = 0; i < editor.ChildrenEditors.Count; i++) - { - RevertDiffToReference(editor.ChildrenEditors[i]); - } + if (Values.IsReferenceValueModified) + SetValueToReference(); } } @@ -496,11 +485,6 @@ namespace FlaxEditor.CustomEditors { if (!Values.IsReferenceValueModified) return false; - - // Skip array items (show diff only on a bottom level properties and fields) - if (ParentEditor is Editors.ArrayEditor) - return false; - return true; } } @@ -512,7 +496,7 @@ namespace FlaxEditor.CustomEditors { if (!Values.HasReferenceValue) return; - RevertDiffToReference(this); + RevertDiffToReference(); } /// @@ -657,7 +641,7 @@ namespace FlaxEditor.CustomEditors // Default try { - obj = JsonConvert.DeserializeObject(text, TypeUtils.GetType(Values.Type), JsonSerializer.Settings); + obj = Newtonsoft.Json.JsonConvert.DeserializeObject(text, TypeUtils.GetType(Values.Type), JsonSerializer.Settings); } catch { @@ -762,7 +746,7 @@ namespace FlaxEditor.CustomEditors /// public void SetValueToDefault() { - SetValue(Values.DefaultValue); + SetValueCloned(Values.DefaultValue); } /// @@ -799,7 +783,19 @@ namespace FlaxEditor.CustomEditors return; } - SetValue(Values.ReferenceValue); + SetValueCloned(Values.ReferenceValue); + } + + private void SetValueCloned(object value) + { + // For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor + if (value != null && !value.GetType().IsValueType) + { + var json = JsonSerializer.Serialize(value); + value = JsonSerializer.Deserialize(json, value.GetType()); + } + + SetValue(value); } /// @@ -811,7 +807,6 @@ namespace FlaxEditor.CustomEditors { if (_isSetBlocked) return; - if (OnDirty(this, value, token)) { _hasValueDirty = true; diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 6f623fb23..919da4301 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -87,6 +87,7 @@ namespace FlaxEditor.CustomEditors.Editors protected bool NotNullItems; private IntegerValueElement _size; + private PropertyNameLabel _sizeLabel; private Color _background; private int _elementsCount; private bool _readOnly; @@ -109,6 +110,9 @@ namespace FlaxEditor.CustomEditors.Editors } } + /// + public override bool RevertValueWithChildren => false; // Always revert value for a whole collection + /// public override void Initialize(LayoutElementsContainer layout) { @@ -174,7 +178,9 @@ namespace FlaxEditor.CustomEditors.Editors } else { - _size = dragArea.IntegerValue("Size"); + var sizeProperty = dragArea.AddPropertyItem("Size"); + _sizeLabel = sizeProperty.Labels.Last(); + _size = sizeProperty.IntegerValue(); _size.IntValue.MinValue = 0; _size.IntValue.MaxValue = ushort.MaxValue; _size.IntValue.Value = size; @@ -274,6 +280,15 @@ namespace FlaxEditor.CustomEditors.Editors } } + /// + protected override void Deinitialize() + { + _size = null; + _sizeLabel = null; + + base.Deinitialize(); + } + /// /// Rebuilds the parent layout if its collection. /// @@ -296,7 +311,6 @@ namespace FlaxEditor.CustomEditors.Editors { if (IsSetBlocked) return; - Resize(_size.IntValue.Value); } @@ -311,11 +325,9 @@ namespace FlaxEditor.CustomEditors.Editors return; var cloned = CloneValues(); - var tmp = cloned[dstIndex]; cloned[dstIndex] = cloned[srcIndex]; cloned[srcIndex] = tmp; - SetValue(cloned); } @@ -371,6 +383,17 @@ namespace FlaxEditor.CustomEditors.Editors if (HasDifferentValues || HasDifferentTypes) return; + // Update reference/default value indicator + if (_sizeLabel != null) + { + var color = Color.Transparent; + if (Values.HasReferenceValue && Values.ReferenceValue is IList referenceValue && referenceValue.Count != Count) + color = FlaxEngine.GUI.Style.Current.BackgroundSelected; + else if (Values.HasDefaultValue && Values.DefaultValue is IList defaultValue && defaultValue.Count != Count) + color = Color.Yellow * 0.8f; + _sizeLabel.HighlightStripColor = color; + } + // Check if collection has been resized (by UI or from external source) if (Count != _elementsCount) { From 014c81190342197fe28d072b52fd1176b76f9447 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 15:02:13 +0100 Subject: [PATCH 40/72] Add `PixelFormatExtensions::ComputeBlockSize` --- .../Engine/Graphics/PixelFormatExtensions.cpp | 31 +++++++++++++++++++ .../Engine/Graphics/PixelFormatExtensions.h | 7 +++++ 2 files changed, 38 insertions(+) diff --git a/Source/Engine/Graphics/PixelFormatExtensions.cpp b/Source/Engine/Graphics/PixelFormatExtensions.cpp index 991844b22..f79530337 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.cpp +++ b/Source/Engine/Graphics/PixelFormatExtensions.cpp @@ -660,6 +660,37 @@ int PixelFormatExtensions::ComputeComponentsCount(const PixelFormat format) } } +int32 PixelFormatExtensions::ComputeBlockSize(PixelFormat format) +{ + switch (format) + { + case PixelFormat::BC1_Typeless: + case PixelFormat::BC1_UNorm: + case PixelFormat::BC1_UNorm_sRGB: + case PixelFormat::BC2_Typeless: + case PixelFormat::BC2_UNorm: + case PixelFormat::BC2_UNorm_sRGB: + case PixelFormat::BC3_Typeless: + case PixelFormat::BC3_UNorm: + case PixelFormat::BC3_UNorm_sRGB: + case PixelFormat::BC4_Typeless: + case PixelFormat::BC4_UNorm: + case PixelFormat::BC4_SNorm: + case PixelFormat::BC5_Typeless: + case PixelFormat::BC5_UNorm: + case PixelFormat::BC5_SNorm: + case PixelFormat::BC6H_Typeless: + case PixelFormat::BC6H_Uf16: + case PixelFormat::BC6H_Sf16: + case PixelFormat::BC7_Typeless: + case PixelFormat::BC7_UNorm: + case PixelFormat::BC7_UNorm_sRGB: + return 4; + default: + return 1; + } +} + PixelFormat PixelFormatExtensions::TosRGB(const PixelFormat format) { switch (format) diff --git a/Source/Engine/Graphics/PixelFormatExtensions.h b/Source/Engine/Graphics/PixelFormatExtensions.h index 604bcb2c3..362e5634f 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.h +++ b/Source/Engine/Graphics/PixelFormatExtensions.h @@ -173,6 +173,13 @@ public: /// The components count. API_FUNCTION() static int ComputeComponentsCount(PixelFormat format); + /// + /// Computes the amount of pixels per-axis stored in the a single block of the format (eg. 4 for BC-family). Returns 1 for uncompressed formats. + /// + /// The . + /// The block pixels count. + API_FUNCTION() static int32 ComputeBlockSize(PixelFormat format); + /// /// Finds the equivalent sRGB format to the provided format. /// From 60202a3addf0179d2162ea6e86176f5ee0a3dcbf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 15:14:49 +0100 Subject: [PATCH 41/72] Fix deadlock when loading block-compressed texture that is smaller than block size #2057 --- Source/Engine/Graphics/Textures/GPUTexture.cpp | 12 +++++++++++- Source/Engine/Graphics/Textures/StreamingTexture.cpp | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index ccdf4be95..403f545ba 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -480,6 +480,16 @@ bool GPUTexture::Init(const GPUTextureDescription& desc) break; } } + const bool isCompressed = PixelFormatExtensions::IsCompressed(desc.Format); + if (isCompressed) + { + const int32 blockSize = PixelFormatExtensions::ComputeBlockSize(desc.Format); + if (desc.Width < blockSize || desc.Height < blockSize) + { + LOG(Warning, "Cannot create texture. Invalid dimensions. Description: {0}", desc.ToString()); + return true; + } + } // Release previous data ReleaseGPU(); @@ -487,7 +497,7 @@ bool GPUTexture::Init(const GPUTextureDescription& desc) // Initialize _desc = desc; _sRGB = PixelFormatExtensions::IsSRGB(desc.Format); - _isBlockCompressed = PixelFormatExtensions::IsCompressed(desc.Format); + _isBlockCompressed = isCompressed; if (OnInit()) { ReleaseGPU(); diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp index 015386fff..09552ec06 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp +++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp @@ -114,9 +114,9 @@ bool StreamingTexture::Create(const TextureHeader& header) { // Ensure that streaming doesn't go too low because the hardware expects the texture to be min in size of compressed texture block int32 lastMip = header.MipLevels - 1; - while ((header.Width >> lastMip) < 4 && (header.Height >> lastMip) < 4) + while ((header.Width >> lastMip) < 4 && (header.Height >> lastMip) < 4 && lastMip > 0) lastMip--; - _minMipCountBlockCompressed = header.MipLevels - lastMip + 1; + _minMipCountBlockCompressed = Math::Min(header.MipLevels - lastMip + 1, header.MipLevels); } // Request resource streaming From d7b17ae0a6804f6d82a91b1bdfaba6be57078585 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 16:05:15 +0100 Subject: [PATCH 42/72] Fix deadlock in asset thumbnails rendering queue when texture streaming fails #2057 --- .../Content/Thumbnails/ThumbnailRequest.cs | 18 +++++++++++++----- .../Content/Thumbnails/ThumbnailsModule.cs | 7 +++++++ .../Graphics/Textures/StreamingTexture.cpp | 1 + .../Engine/Graphics/Textures/TextureBase.cpp | 5 +++++ Source/Engine/Graphics/Textures/TextureBase.h | 5 +++++ Source/Engine/Streaming/StreamableResource.h | 6 ++++-- Source/Engine/Streaming/Streaming.cpp | 3 ++- 7 files changed, 37 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Content/Thumbnails/ThumbnailRequest.cs b/Source/Editor/Content/Thumbnails/ThumbnailRequest.cs index 6e60602ff..40ad17720 100644 --- a/Source/Editor/Content/Thumbnails/ThumbnailRequest.cs +++ b/Source/Editor/Content/Thumbnails/ThumbnailRequest.cs @@ -35,6 +35,11 @@ namespace FlaxEditor.Content.Thumbnails /// The finalized state. /// Disposed, + + /// + /// The request has failed (eg. asset cannot be loaded). + /// + Failed, }; /// @@ -78,6 +83,14 @@ namespace FlaxEditor.Content.Thumbnails Proxy = proxy; } + internal void Update() + { + if (State == States.Prepared && (!Asset || Asset.LastLoadFailed)) + { + State = States.Failed; + } + } + /// /// Prepares this request. /// @@ -85,11 +98,8 @@ namespace FlaxEditor.Content.Thumbnails { if (State != States.Created) throw new InvalidOperationException(); - - // Prepare Asset = FlaxEngine.Content.LoadAsync(Item.Path); Proxy.OnThumbnailDrawPrepare(this); - State = States.Prepared; } @@ -101,9 +111,7 @@ namespace FlaxEditor.Content.Thumbnails { if (State != States.Prepared) throw new InvalidOperationException(); - Item.Thumbnail = icon; - State = States.Rendered; } diff --git a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs index 2b830c933..b213cb798 100644 --- a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs +++ b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs @@ -120,6 +120,8 @@ namespace FlaxEditor.Content.Thumbnails internal static bool HasMinimumQuality(TextureBase asset) { + if (asset.HasStreamingError) + return true; // Don't block thumbnails queue when texture fails to stream in (eg. unsupported format) var mipLevels = asset.MipLevels; var minMipLevels = Mathf.Min(mipLevels, 7); return asset.IsLoaded && asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * MinimumRequiredResourcesQuality)); @@ -499,6 +501,7 @@ namespace FlaxEditor.Content.Thumbnails var request = _requests[i]; try { + request.Update(); if (request.IsReady) { isAnyReady = true; @@ -507,6 +510,10 @@ namespace FlaxEditor.Content.Thumbnails { request.Prepare(); } + else if (request.State == ThumbnailRequest.States.Failed) + { + _requests.RemoveAt(i--); + } } catch (Exception ex) { diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp index 09552ec06..ea70a9d9e 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp +++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp @@ -296,6 +296,7 @@ Task* StreamingTexture::UpdateAllocation(int32 residency) // Setup texture if (texture->Init(desc)) { + Streaming.Error = true; LOG(Error, "Cannot allocate texture {0}.", ToString()); } if (allocatedResidency != 0) diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp index 181955fce..c3d8898a5 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.cpp +++ b/Source/Engine/Graphics/Textures/TextureBase.cpp @@ -223,6 +223,11 @@ void TextureBase::SetTextureGroup(int32 textureGroup) } } +bool TextureBase::HasStreamingError() const +{ + return _texture.Streaming.Error; +} + BytesContainer TextureBase::GetMipData(int32 mipIndex, int32& rowPitch, int32& slicePitch) { BytesContainer result; diff --git a/Source/Engine/Graphics/Textures/TextureBase.h b/Source/Engine/Graphics/Textures/TextureBase.h index e47bb548d..befe16069 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.h +++ b/Source/Engine/Graphics/Textures/TextureBase.h @@ -148,6 +148,11 @@ public: /// API_PROPERTY() void SetTextureGroup(int32 textureGroup); + /// + /// Returns true if texture streaming failed (eg. pixel format is unsupported or texture data cannot be uploaded to GPU due to memory limit). + /// + API_PROPERTY() bool HasStreamingError() const; + public: /// /// Gets the mip data. diff --git a/Source/Engine/Streaming/StreamableResource.h b/Source/Engine/Streaming/StreamableResource.h index 866ec8469..27ae0a089 100644 --- a/Source/Engine/Streaming/StreamableResource.h +++ b/Source/Engine/Streaming/StreamableResource.h @@ -111,8 +111,9 @@ public: struct StreamingCache { int64 LastUpdate = 0; - int32 TargetResidency = 0; int64 TargetResidencyChange = 0; + int32 TargetResidency = 0; + bool Error = false; SamplesBuffer QualitySamples; }; @@ -131,7 +132,8 @@ public: /// /// Stops the streaming (eg. on streaming fail). /// - void ResetStreaming(); + /// True if streaming failed. + void ResetStreaming(bool error = true); protected: diff --git a/Source/Engine/Streaming/Streaming.cpp b/Source/Engine/Streaming/Streaming.cpp index 19b845fb1..a7b2cad44 100644 --- a/Source/Engine/Streaming/Streaming.cpp +++ b/Source/Engine/Streaming/Streaming.cpp @@ -84,8 +84,9 @@ void StreamableResource::RequestStreamingUpdate() Streaming.LastUpdate = 0; } -void StreamableResource::ResetStreaming() +void StreamableResource::ResetStreaming(bool error) { + Streaming.Error = error; Streaming.TargetResidency = 0; Streaming.LastUpdate = DateTime::MaxValue().Ticks; } From df8418da2593eb0aa1439a444ec1d1e2b1bdfe4a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 17:00:55 +0100 Subject: [PATCH 43/72] Add more profiling events --- Source/Engine/Content/Content.cpp | 8 +++++++- Source/Engine/Content/Storage/FlaxStorage.cpp | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 982ae599f..897817850 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -230,6 +230,7 @@ bool Content::GetAssetInfo(const Guid& id, AssetInfo& info) // Find asset in registry if (Cache.FindAsset(id, info)) return true; + PROFILE_CPU(); // Locking injects some stalls but we need to make it safe (only one thread can pass though it at once) ScopeLock lock(WorkspaceDiscoveryLocker); @@ -276,6 +277,7 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info) // Find asset in registry if (Cache.FindAsset(path, info)) return true; + PROFILE_CPU(); const auto extension = FileSystem::GetExtension(path).ToLower(); @@ -538,6 +540,8 @@ void Content::DeleteAsset(Asset* asset) void Content::DeleteAsset(const StringView& path) { + PROFILE_CPU(); + // Try to delete already loaded asset Asset* asset = GetAsset(path); if (asset != nullptr) @@ -566,12 +570,12 @@ void Content::DeleteAsset(const StringView& path) void Content::deleteFileSafety(const StringView& path, const Guid& id) { - // Check if given id is invalid if (!id.IsValid()) { LOG(Warning, "Cannot remove file \'{0}\'. Given ID is invalid.", path); return; } + PROFILE_CPU(); // Ensure that file has the same ID (prevent from deleting different assets) auto storage = ContentStorageManager::TryGetStorage(path); @@ -678,6 +682,7 @@ bool Content::FastTmpAssetClone(const StringView& path, String& resultPath) bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId) { + PROFILE_CPU(); ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid()); LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId); @@ -796,6 +801,7 @@ Asset* Content::CreateVirtualAsset(MClass* type) Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type) { + PROFILE_CPU(); auto& assetType = type.GetType(); // Init mock asset info diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index b632fc6d3..9d4691be1 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -1305,6 +1305,8 @@ FileReadStream* FlaxStorage::OpenFile() bool FlaxStorage::CloseFileHandles() { + PROFILE_CPU(); + // Note: this is usually called by the content manager when this file is not used or on exit // In those situations all the async tasks using this storage should be cancelled externally From a5e072da3191740c9c7ad5ac5fc2ad3057c53be5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 17:04:47 +0100 Subject: [PATCH 44/72] Fix importing `.dds` files `as-is`if the compressed image is too small for the engine (block size validation) #2057 --- .../Tools/TextureTool/TextureTool.DirectXTex.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp index dc5b0be49..17929bed0 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp @@ -625,7 +625,15 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path } bool keepAsIs = false; - if (!options.FlipY && !options.InvertGreenChannel && options.Compress && type == ImageType::DDS && mipLevels == sourceMipLevels && DirectX::IsCompressed(sourceDxgiFormat) && !DirectX::IsSRGB(sourceDxgiFormat)) + if (!options.FlipY && + !options.InvertGreenChannel && + options.Compress && + type == ImageType::DDS && + mipLevels == sourceMipLevels && + DirectX::IsCompressed(sourceDxgiFormat) && + !DirectX::IsSRGB(sourceDxgiFormat) && + width >= 4 && + height >= 4) { // Keep image in the current compressed format (artist choice) so we don't have to run the slow mipmap generation keepAsIs = true; From ae5fa9619bb19d6a7f53be6cb157e0f8ee156b16 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 18:23:17 +0100 Subject: [PATCH 45/72] Remove `TextureUtils` --- .../Engine/ContentImporters/ImportTexture.cpp | 1 - .../Engine/Graphics/Textures/TextureUtils.h | 61 ------------------- .../TextureTool/TextureTool.DirectXTex.cpp | 21 ++----- .../Engine/Tools/TextureTool/TextureTool.cpp | 48 +++++++++++++++ Source/Engine/Tools/TextureTool/TextureTool.h | 4 +- .../Tools/TextureTool/TextureTool.stb.cpp | 3 +- 6 files changed, 55 insertions(+), 83 deletions(-) delete mode 100644 Source/Engine/Graphics/Textures/TextureUtils.h diff --git a/Source/Engine/ContentImporters/ImportTexture.cpp b/Source/Engine/ContentImporters/ImportTexture.cpp index 51b93420c..ebc7e514c 100644 --- a/Source/Engine/ContentImporters/ImportTexture.cpp +++ b/Source/Engine/ContentImporters/ImportTexture.cpp @@ -8,7 +8,6 @@ #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Graphics/Textures/TextureData.h" -#include "Engine/Graphics/Textures/TextureUtils.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Content/Storage/ContentStorageManager.h" #include "Engine/ContentImporters/ImportIES.h" diff --git a/Source/Engine/Graphics/Textures/TextureUtils.h b/Source/Engine/Graphics/Textures/TextureUtils.h deleted file mode 100644 index b021d27fe..000000000 --- a/Source/Engine/Graphics/Textures/TextureUtils.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Core/Types/BaseTypes.h" -#include "Types.h" - -/// -/// Texture utilities class -/// -class TextureUtils -{ -public: - static PixelFormat ToPixelFormat(const TextureFormatType format, int32 width, int32 height, bool canCompress) - { - const bool canUseBlockCompression = width % 4 == 0 && height % 4 == 0; - if (canCompress && canUseBlockCompression) - { - switch (format) - { - case TextureFormatType::ColorRGB: - return PixelFormat::BC1_UNorm; - case TextureFormatType::ColorRGBA: - return PixelFormat::BC3_UNorm; - case TextureFormatType::NormalMap: - return PixelFormat::BC5_UNorm; - case TextureFormatType::GrayScale: - return PixelFormat::BC4_UNorm; - case TextureFormatType::HdrRGBA: - return PixelFormat::BC7_UNorm; - case TextureFormatType::HdrRGB: -#if PLATFORM_LINUX - // TODO: support BC6H compression for Linux Editor - return PixelFormat::BC7_UNorm; -#else - return PixelFormat::BC6H_Uf16; -#endif - default: - return PixelFormat::Unknown; - } - } - - switch (format) - { - case TextureFormatType::ColorRGB: - return PixelFormat::R8G8B8A8_UNorm; - case TextureFormatType::ColorRGBA: - return PixelFormat::R8G8B8A8_UNorm; - case TextureFormatType::NormalMap: - return PixelFormat::R16G16_UNorm; - case TextureFormatType::GrayScale: - return PixelFormat::R8_UNorm; - case TextureFormatType::HdrRGBA: - return PixelFormat::R16G16B16A16_Float; - case TextureFormatType::HdrRGB: - return PixelFormat::R11G11B10_Float; - default: - return PixelFormat::Unknown; - } - } -}; diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp index 17929bed0..fced3423d 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp @@ -10,7 +10,6 @@ #include "Engine/Platform/ConditionVariable.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Async/GPUTask.h" -#include "Engine/Graphics/Textures/TextureUtils.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/PixelFormatExtensions.h" #if USE_EDITOR @@ -318,7 +317,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path textureData.Width = (int32)meta.width; textureData.Height = (int32)meta.height; textureData.Depth = (int32)meta.depth; - textureData.Format = ToPixelFormat(meta.format); + textureData.Format = ::ToPixelFormat(meta.format); textureData.Items.Resize(1); textureData.Items.Resize((int32)meta.arraySize); for (int32 arrayIndex = 0; arrayIndex < (int32)meta.arraySize; arrayIndex++) @@ -598,7 +597,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path float alphaThreshold = 0.3f; bool isPowerOfTwo = Math::IsPowerOfTwo(width) && Math::IsPowerOfTwo(height); DXGI_FORMAT sourceDxgiFormat = currentImage->GetMetadata().format; - PixelFormat targetFormat = TextureUtils::ToPixelFormat(options.Type, width, height, options.Compress); + PixelFormat targetFormat = TextureTool::ToPixelFormat(options.Type, width, height, options.Compress); if (options.sRGB) targetFormat = PixelFormatExtensions::TosRGB(targetFormat); DXGI_FORMAT targetDxgiFormat = ToDxgiFormat(targetFormat); @@ -638,14 +637,13 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path // Keep image in the current compressed format (artist choice) so we don't have to run the slow mipmap generation keepAsIs = true; targetDxgiFormat = sourceDxgiFormat; - targetFormat = ToPixelFormat(currentImage->GetMetadata().format); + targetFormat = ::ToPixelFormat(currentImage->GetMetadata().format); } // Decompress if texture is compressed (next steps need decompressed input data, for eg. mip maps generation or format changing) if (!keepAsIs && DirectX::IsCompressed(sourceDxgiFormat)) { auto& tmpImg = GET_TMP_IMG(); - sourceDxgiFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; result = Decompress(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), sourceDxgiFormat, tmpImg); if (FAILED(result)) @@ -653,14 +651,13 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path errorMsg = String::Format(TEXT("Cannot decompress texture, error: {0:x}"), static_cast(result)); return true; } - SET_CURRENT_IMG(tmpImg); } // Fix sRGB problem if (!keepAsIs && DirectX::IsSRGB(sourceDxgiFormat)) { - sourceDxgiFormat = ToDxgiFormat(PixelFormatExtensions::ToNonsRGB(ToPixelFormat(sourceDxgiFormat))); + sourceDxgiFormat = ToDxgiFormat(PixelFormatExtensions::ToNonsRGB(::ToPixelFormat(sourceDxgiFormat))); ((DirectX::TexMetadata&)currentImage->GetMetadata()).format = sourceDxgiFormat; for (size_t i = 0; i < currentImage->GetImageCount(); i++) ((DirectX::Image*)currentImage->GetImages())[i].format = sourceDxgiFormat; @@ -670,12 +667,10 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path if (!keepAsIs && DirectX::HasAlpha(sourceDxgiFormat) && options.Type == TextureFormatType::ColorRGB && options.Compress) { auto& tmpImg = GET_TMP_IMG(); - result = TransformImage(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), [](DirectX::XMVECTOR* outPixels, const DirectX::XMVECTOR* inPixels, size_t width, size_t y) { UNREFERENCED_PARAMETER(y); - for (size_t j = 0; j < width; j++) { outPixels[j] = DirectX::XMVectorSelect(DirectX::g_XMOne, inPixels[j], DirectX::g_XMSelect1110); @@ -686,8 +681,6 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path errorMsg = String::Format(TEXT("Cannot transform texture to remove unwanted alpha channel, error: {0:x}"), static_cast(result)); return true; } - - // Use converted image SET_CURRENT_IMG(tmpImg); } @@ -695,7 +688,6 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path if (!keepAsIs && options.FlipY) { auto& tmpImg = GET_TMP_IMG(); - DWORD flags = DirectX::TEX_FR_FLIP_VERTICAL; result = FlipRotate(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), flags, tmpImg); if (FAILED(result)) @@ -703,8 +695,6 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path errorMsg = String::Format(TEXT("Cannot rotate/flip texture, error: {0:x}"), static_cast(result)); return true; } - - // Use converted image SET_CURRENT_IMG(tmpImg); } @@ -712,7 +702,6 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path if (!keepAsIs && options.InvertGreenChannel) { auto& timage = GET_TMP_IMG(); - result = TransformImage(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), [&](DirectX::XMVECTOR* outPixels, const DirectX::XMVECTOR* inPixels, size_t w, size_t y) { @@ -734,7 +723,6 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path errorMsg = String::Format(TEXT("Cannot invert green channel in texture, error: {0:x}"), static_cast(result)); return true; } - // Use converted image SET_CURRENT_IMG(timage); } @@ -757,7 +745,6 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path errorMsg = String::Format(TEXT("Cannot generate texture mip maps chain, error: {1:x}"), *path, static_cast(result)); return true; } - SET_CURRENT_IMG(tmpImg); } diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp index c64134e46..c2f5c62dc 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp @@ -679,6 +679,54 @@ Color TextureTool::SampleLinear(const PixelFormatSampler* sampler, const Float2& return Color::Lerp(Color::Lerp(v00, v01, uvFraction.X), Color::Lerp(v10, v11, uvFraction.X), uvFraction.Y); } +PixelFormat TextureTool::ToPixelFormat(TextureFormatType format, int32 width, int32 height, bool canCompress) +{ + const bool canUseBlockCompression = width % 4 == 0 && height % 4 == 0; + if (canCompress && canUseBlockCompression) + { + switch (format) + { + case TextureFormatType::ColorRGB: + return PixelFormat::BC1_UNorm; + case TextureFormatType::ColorRGBA: + return PixelFormat::BC3_UNorm; + case TextureFormatType::NormalMap: + return PixelFormat::BC5_UNorm; + case TextureFormatType::GrayScale: + return PixelFormat::BC4_UNorm; + case TextureFormatType::HdrRGBA: + return PixelFormat::BC7_UNorm; + case TextureFormatType::HdrRGB: +#if PLATFORM_LINUX + // TODO: support BC6H compression for Linux Editor + return PixelFormat::BC7_UNorm; +#else + return PixelFormat::BC6H_Uf16; +#endif + default: + return PixelFormat::Unknown; + } + } + + switch (format) + { + case TextureFormatType::ColorRGB: + return PixelFormat::R8G8B8A8_UNorm; + case TextureFormatType::ColorRGBA: + return PixelFormat::R8G8B8A8_UNorm; + case TextureFormatType::NormalMap: + return PixelFormat::R16G16_UNorm; + case TextureFormatType::GrayScale: + return PixelFormat::R8_UNorm; + case TextureFormatType::HdrRGBA: + return PixelFormat::R16G16B16A16_Float; + case TextureFormatType::HdrRGB: + return PixelFormat::R11G11B10_Float; + default: + return PixelFormat::Unknown; + } +} + bool TextureTool::GetImageType(const StringView& path, ImageType& type) { const auto extension = FileSystem::GetExtension(path).ToLower(); diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h index 351167b3c..08d2e77e7 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.h +++ b/Source/Engine/Tools/TextureTool/TextureTool.h @@ -110,14 +110,12 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool public: #if USE_EDITOR - /// /// Checks whenever the given texture file contains alpha channel data with values different than solid fill of 1 (non fully opaque). /// /// The file path. /// True if has alpha channel, otherwise false. static bool HasAlpha(const StringView& path); - #endif /// @@ -240,6 +238,8 @@ public: /// The sampled color (linear). static Color SampleLinear(const PixelFormatSampler* sampler, const Float2& uv, const void* data, const Int2& size, int32 rowPitch); + static PixelFormat ToPixelFormat(TextureFormatType format, int32 width, int32 height, bool canCompress); + private: enum class ImageType { diff --git a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp index 9fd7f554b..97fe8c198 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp @@ -9,7 +9,6 @@ #include "Engine/Serialization/FileWriteStream.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Textures/TextureData.h" -#include "Engine/Graphics/Textures/TextureUtils.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Platform/File.h" @@ -459,7 +458,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu // Cache data float alphaThreshold = 0.3f; bool isPowerOfTwo = Math::IsPowerOfTwo(width) && Math::IsPowerOfTwo(height); - PixelFormat targetFormat = TextureUtils::ToPixelFormat(options.Type, width, height, options.Compress); + PixelFormat targetFormat = ToPixelFormat(options.Type, width, height, options.Compress); if (options.sRGB) targetFormat = PixelFormatExtensions::TosRGB(targetFormat); From 9c4857a20597db83d4d5189c3a1ac8e35ae4a000 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 15 Dec 2023 15:54:18 +0100 Subject: [PATCH 46/72] Fix typo in `Tag` comparison function #2081 --- Source/Engine/Level/Tags.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Level/Tags.cs b/Source/Engine/Level/Tags.cs index 1ff9acef4..11815aa9d 100644 --- a/Source/Engine/Level/Tags.cs +++ b/Source/Engine/Level/Tags.cs @@ -45,7 +45,7 @@ namespace FlaxEngine /// True if both values are not equal, otherwise false. public static bool operator !=(Tag left, Tag right) { - return left.Index == right.Index; + return left.Index != right.Index; } /// @@ -213,11 +213,11 @@ namespace FlaxEngine } /// - /// Checks if the list of tags contains all of the given tags (including parent tags check). For example, HasAll({"A.B", "C"}, {"A", "C"}) returns true, for exact check use HasAllExact. + /// Checks if the list of tags contains all the given tags (including parent tags check). For example, HasAll({"A.B", "C"}, {"A", "C"}) returns true, for exact check use HasAllExact. /// /// The tags list to use. /// The tags to check. - /// True if all of the given tags are contained by the list of tags. Returns true for empty list. + /// True if all the given tags are contained by the list of tags. Returns true for empty list. public static bool HasAll(this Tag[] list, Tag[] tags) { if (tags == null || tags.Length == 0) @@ -233,11 +233,11 @@ namespace FlaxEngine } /// - /// Checks if the list of tags contains all of the given tags (exact match). For example, HasAllExact({"A.B", "C"}, {"A", "C"}) returns false, for parents check use HasAll. + /// Checks if the list of tags contains all the given tags (exact match). For example, HasAllExact({"A.B", "C"}, {"A", "C"}) returns false, for parents check use HasAll. /// /// The tags list to use. /// The tags to check. - /// True if all of the given tags are contained by the list of tags. Returns true for empty list. + /// True if all the given tags are contained by the list of tags. Returns true for empty list. public static bool HasAllExact(this Tag[] list, Tag[] tags) { if (tags == null || tags.Length == 0) From c0ef2a1f5855d09d4f1a16da0d4cde01861d2bd5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 12:39:10 +0100 Subject: [PATCH 47/72] Cleamnup code for #2020 and use internal pointer for current tracking window --- .../Engine/Platform/Linux/LinuxPlatform.cpp | 36 ++++++------------- Source/Engine/Platform/Linux/LinuxPlatform.h | 2 -- Source/Engine/Platform/Linux/LinuxWindow.cpp | 6 ++-- 3 files changed, 15 insertions(+), 29 deletions(-) diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 83c3e3ae6..41a2a33f6 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -94,7 +94,7 @@ X11::XcursorImage* CursorsImg[(int32)CursorType::MAX]; Dictionary KeyNameMap; Array KeyCodeMap; Delegate LinuxPlatform::xEventRecieved; -const Window* mouseTrackingWindow; +Window* MouseTrackingWindow = nullptr; // Message boxes configuration #define LINUX_DIALOG_MIN_BUTTON_WIDTH 64 @@ -1917,9 +1917,6 @@ bool LinuxPlatform::Init() { if (PlatformBase::Init()) return true; - - mouseTrackingWindow = nullptr; - char fileNameBuffer[1024]; // Init timing @@ -2263,17 +2260,6 @@ bool LinuxPlatform::Init() return false; } -void LinuxPlatform::StartTrackingMouse(const Window* window) -{ - mouseTrackingWindow = window; -} - -void LinuxPlatform::EndTrackingMouse(const Window* window) -{ - if (mouseTrackingWindow == window) - mouseTrackingWindow = nullptr; -} - void LinuxPlatform::BeforeRun() { } @@ -2412,7 +2398,7 @@ void LinuxPlatform::Tick() // Update input context focus X11::XSetICFocus(IC); window = WindowsManager::GetByNativePtr((void*)event.xfocus.window); - if (window && mouseTrackingWindow == nullptr) + if (window && MouseTrackingWindow == nullptr) { window->OnGotFocus(); } @@ -2421,7 +2407,7 @@ void LinuxPlatform::Tick() // Update input context focus X11::XUnsetICFocus(IC); window = WindowsManager::GetByNativePtr((void*)event.xfocus.window); - if (window && mouseTrackingWindow == nullptr) + if (window && MouseTrackingWindow == nullptr) { window->OnLostFocus(); } @@ -2528,22 +2514,22 @@ void LinuxPlatform::Tick() break; case ButtonPress: window = WindowsManager::GetByNativePtr((void*)event.xbutton.window); - if (mouseTrackingWindow) - ((LinuxWindow*)mouseTrackingWindow)->OnButtonPress(&event.xbutton); + if (MouseTrackingWindow) + MouseTrackingWindow->OnButtonPress(&event.xbutton); else if (window) window->OnButtonPress(&event.xbutton); break; case ButtonRelease: window = WindowsManager::GetByNativePtr((void*)event.xbutton.window); - if (mouseTrackingWindow) - ((LinuxWindow*)mouseTrackingWindow)->OnButtonRelease(&event.xbutton); + if (MouseTrackingWindow) + MouseTrackingWindow->OnButtonRelease(&event.xbutton); else if (window) window->OnButtonRelease(&event.xbutton); break; case MotionNotify: window = WindowsManager::GetByNativePtr((void*)event.xmotion.window); - if (mouseTrackingWindow) - ((LinuxWindow*)mouseTrackingWindow)->OnMotionNotify(&event.xmotion); + if (MouseTrackingWindow) + MouseTrackingWindow->OnMotionNotify(&event.xmotion); else if (window) window->OnMotionNotify(&event.xmotion); break; @@ -2552,8 +2538,8 @@ void LinuxPlatform::Tick() break; case LeaveNotify: window = WindowsManager::GetByNativePtr((void*)event.xcrossing.window); - if (mouseTrackingWindow) - ((LinuxWindow*)mouseTrackingWindow)->OnLeaveNotify(&event.xcrossing); + if (MouseTrackingWindow) + MouseTrackingWindow->OnLeaveNotify(&event.xcrossing); if (window) window->OnLeaveNotify(&event.xcrossing); break; diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h index 00e7743d7..54590adf2 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.h +++ b/Source/Engine/Platform/Linux/LinuxPlatform.h @@ -139,8 +139,6 @@ public: static String GetWorkingDirectory(); static bool SetWorkingDirectory(const String& path); static Window* CreateWindow(const CreateWindowSettings& settings); - static void StartTrackingMouse(const Window* window); - static void EndTrackingMouse(const Window *window); static void GetEnvironmentVariables(Dictionary& result); static bool GetEnvironmentVariable(const String& name, String& value); static bool SetEnvironmentVariable(const String& name, const String& value); diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index 9168beb29..9004ed84c 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -40,6 +40,7 @@ extern X11::Atom xAtomWmName; extern Dictionary KeyNameMap; extern Array KeyCodeMap; extern X11::Cursor Cursors[(int32)CursorType::MAX]; +extern Window* MouseTrackingWindow; static constexpr uint32 MouseDoubleClickTime = 500; static constexpr uint32 MaxDoubleClickDistanceSquared = 10; @@ -822,12 +823,13 @@ void LinuxWindow::SetTitle(const StringView& title) void LinuxWindow::StartTrackingMouse(bool useMouseScreenOffset) { - LinuxPlatform::StartTrackingMouse(this); + MouseTrackingWindow = this; } void LinuxWindow::EndTrackingMouse() { - LinuxPlatform::EndTrackingMouse(this); + if (MouseTrackingWindow == this) + MouseTrackingWindow = nullptr; } void LinuxWindow::SetCursor(CursorType type) From efebb29ac0479dfdea2e307687fbbdad3be3cc57 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 12:49:39 +0100 Subject: [PATCH 48/72] Minor tweak for #2003 --- Source/Editor/Content/Proxy/SceneProxy.cs | 6 +++++- Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs | 2 +- Source/Engine/Level/Level.cpp | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Content/Proxy/SceneProxy.cs b/Source/Editor/Content/Proxy/SceneProxy.cs index a232c672b..d959b524a 100644 --- a/Source/Editor/Content/Proxy/SceneProxy.cs +++ b/Source/Editor/Content/Proxy/SceneProxy.cs @@ -73,7 +73,11 @@ namespace FlaxEditor.Content /// public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item) { - menu.AddButton("Open additionally", () => { Editor.Instance.Scene.OpenScene(((SceneItem)item).ID, true); }); + var id = ((SceneItem)item).ID; + if (Level.FindScene(id) == null) + { + menu.AddButton("Open (additive)", () => { Editor.Instance.Scene.OpenScene(id, true); }); + } } } } diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 8b5318190..0c8e0f283 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -158,7 +158,7 @@ namespace FlaxEditor.Windows if (unloadedScenes.Count > 0) { contextMenu.AddSeparator(); - var childCM = contextMenu.GetOrAddChildMenu("Add Scene"); + var childCM = contextMenu.GetOrAddChildMenu("Open Scene"); foreach (var sceneGuid in unloadedScenes) { if (FlaxEngine.Content.GetAssetInfo(sceneGuid, out var unloadedScene)) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index cc91e48ca..41033876d 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -439,7 +439,7 @@ public: bool Do() const override { - auto scene = Scripting::FindObject(TargetScene); + auto scene = Level::FindScene(TargetScene); if (!scene) return true; return unloadScene(scene); From d614232f8d556947e37ce4ba799f2e48b6255e09 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Dec 2023 14:45:28 +0200 Subject: [PATCH 49/72] Fix VS build issues with C# projects when engine path has spaces --- .../VisualStudioProjectGenerator.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index b04531d9f..5cc1b2205 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -702,7 +702,7 @@ namespace Flax.Build.Projects.VisualStudio // Override MSBuild build tasks to run Flax.Build in C#-only projects { // Build command for the build tool - var buildToolPath = Path.ChangeExtension(Utilities.MakePathRelativeTo(typeof(Builder).Assembly.Location, Path.GetDirectoryName(solution.MainProject.Path)), null); + var buildToolPath = Path.ChangeExtension(typeof(Builder).Assembly.Location, null); var targetsFileContent = new StringBuilder(); targetsFileContent.AppendLine(""); @@ -724,16 +724,16 @@ namespace Flax.Build.Projects.VisualStudio { foreach (var configuration in solution.MainProject.Configurations) { - var cmdLine = string.Format("{0} -log -mutex -workspace={1} -arch={2} -configuration={3} -platform={4} -buildTargets={5}", - FixPath(buildToolPath), - FixPath(solution.MainProject.WorkspaceRootPath), + var cmdLine = string.Format("\"{0}\" -log -mutex -workspace=\"{1}\" -arch={2} -configuration={3} -platform={4} -buildTargets={5}", + buildToolPath, + solution.MainProject.WorkspaceRootPath, configuration.Architecture, configuration.Configuration, configuration.Platform, configuration.Target); Configuration.PassArgs(ref cmdLine); - str.AppendLine(string.Format(" ", cmdLine, extraArgs, configuration.Name)); + str.AppendLine(string.Format(" ", cmdLine, extraArgs, configuration.Name)); } } } @@ -774,14 +774,5 @@ namespace Flax.Build.Projects.VisualStudio projects.Add(project); } } - - private static string FixPath(string path) - { - if (path.Contains(' ')) - { - path = "\"" + path + "\""; - } - return path; - } } } From 84249b3b57a00a54ff099544510097465145d3bd Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Dec 2023 14:48:21 +0200 Subject: [PATCH 50/72] Skip building main C#-project in VS solution when C++-project is present Flax.Build is invoked twice, once for C++-project and one more time for C#-project. Skip the C#-project by using the custom .targets file to not break Rider's solution analysis feature. --- .../Projects/VisualStudio/CSSDKProjectGenerator.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs index c678b2521..2d4b0cc02 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs @@ -91,11 +91,6 @@ namespace Flax.Build.Projects.VisualStudio var baseConfiguration = project.Configurations.First(); var baseOutputDir = Utilities.MakePathRelativeTo(project.CSharp.OutputPath ?? baseConfiguration.TargetBuildOptions.OutputFolder, projectDirectory); var baseIntermediateOutputPath = Utilities.MakePathRelativeTo(project.CSharp.IntermediateOutputPath ?? Path.Combine(baseConfiguration.TargetBuildOptions.IntermediateFolder, "CSharp"), projectDirectory); - - bool isMainProject = Globals.Project.ProjectFolderPath == project.WorkspaceRootPath && project.Name != "BuildScripts" && (Globals.Project.Name != "Flax" || project.Name != "FlaxEngine"); - var flaxBuildTargetsFilename = isMainProject ? "Flax.Build.CSharp.targets" : "Flax.Build.CSharp.SkipBuild.targets"; - var cacheProjectsPath = Utilities.MakePathRelativeTo(Path.Combine(Globals.Root, "Cache", "Projects"), projectDirectory); - var flaxBuildTargetsPath = !string.IsNullOrEmpty(cacheProjectsPath) ? Path.Combine(cacheProjectsPath, flaxBuildTargetsFilename) : flaxBuildTargetsFilename; csProjectFileContent.AppendLine($" net{dotnetSdk.Version.Major}.{dotnetSdk.Version.Minor}"); csProjectFileContent.AppendLine(" disable"); @@ -114,7 +109,11 @@ namespace Flax.Build.Projects.VisualStudio //csProjectFileContent.AppendLine(" false"); // TODO: use it to reduce burden of framework libs - // Custom .targets file for overriding MSBuild build tasks + // Custom .targets file for overriding MSBuild build tasks, only invoke Flax.Build once per Flax project + bool isMainProject = Globals.Project.IsCSharpOnlyProject && Globals.Project.ProjectFolderPath == project.WorkspaceRootPath && project.Name != "BuildScripts" && (Globals.Project.Name != "Flax" || project.Name != "FlaxEngine"); + var flaxBuildTargetsFilename = isMainProject ? "Flax.Build.CSharp.targets" : "Flax.Build.CSharp.SkipBuild.targets"; + var cacheProjectsPath = Utilities.MakePathRelativeTo(Path.Combine(Globals.Root, "Cache", "Projects"), projectDirectory); + var flaxBuildTargetsPath = !string.IsNullOrEmpty(cacheProjectsPath) ? Path.Combine(cacheProjectsPath, flaxBuildTargetsFilename) : flaxBuildTargetsFilename; csProjectFileContent.AppendLine(string.Format(" $(MSBuildThisFileDirectory){0}", flaxBuildTargetsPath)); // Hide annoying warnings during build From 639803480ed4f78a5385decd2908054f66928f93 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Dec 2023 16:11:56 +0200 Subject: [PATCH 51/72] Improve .NET related errors during cooking process --- Source/Editor/Cooker/Steps/DeployDataStep.cpp | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index 80cb73c2b..013fa0e60 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -48,21 +48,21 @@ bool DeployDataStep::Perform(CookingData& data) } if (buildSettings.SkipDotnetPackaging && data.Tools->UseSystemDotnet()) { - // Use system-installed .Net Runtime + // Use system-installed .NET Runtime FileSystem::DeleteDirectory(dstDotnet); } else { - // Deploy .Net Runtime files + // Deploy .NET Runtime files FileSystem::CreateDirectory(dstDotnet); String srcDotnet = depsRoot / TEXT("Dotnet"); if (FileSystem::DirectoryExists(srcDotnet)) { - // Use prebuilt .Net installation for that platform - LOG(Info, "Using .Net Runtime {} at {}", data.Tools->GetName(), srcDotnet); + // Use prebuilt .NET installation for that platform + LOG(Info, "Using .NET Runtime {} at {}", data.Tools->GetName(), srcDotnet); if (EditorUtilities::CopyDirectoryIfNewer(dstDotnet, srcDotnet, true)) { - data.Error(TEXT("Failed to copy .Net runtime data files.")); + data.Error(TEXT("Failed to copy .NET runtime data files.")); return true; } } @@ -85,7 +85,7 @@ bool DeployDataStep::Perform(CookingData& data) } if (canUseSystemDotnet && (aotMode == DotNetAOTModes::None || aotMode == DotNetAOTModes::ILC)) { - // Ask Flax.Build to provide .Net SDK location for the current platform + // Ask Flax.Build to provide .NET SDK location for the current platform String sdks; bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), GAME_BUILD_DOTNET_VER), data.CacheDirectory); failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks); @@ -101,7 +101,7 @@ bool DeployDataStep::Perform(CookingData& data) } if (failed || !FileSystem::DirectoryExists(srcDotnet)) { - data.Error(TEXT("Failed to get .Net SDK location for a current platform.")); + data.Error(TEXT("Failed to get .NET SDK location for the current host platform.")); return true; } @@ -110,7 +110,7 @@ bool DeployDataStep::Perform(CookingData& data) FileSystem::GetChildDirectories(versions, srcDotnet / TEXT("host/fxr")); if (versions.Count() == 0) { - data.Error(TEXT("Failed to get .Net SDK location for a current platform.")); + data.Error(TEXT("Failed to find any .NET hostfxr versions for the current host platform.")); return true; } for (String& version : versions) @@ -121,8 +121,14 @@ bool DeployDataStep::Perform(CookingData& data) } Sorting::QuickSort(versions); const String version = versions.Last(); + if (version.IsEmpty()) + { + data.Error(TEXT("Failed to find supported .NET hostfxr version for the current host platform.")); + return true; + } + FileSystem::NormalizePath(srcDotnet); - LOG(Info, "Using .Net Runtime {} at {}", version, srcDotnet); + LOG(Info, "Using .NET Runtime {} at {}", version, srcDotnet); // Check if previously deployed files are valid (eg. system-installed .NET was updated from version 7.0.3 to 7.0.5) { @@ -158,13 +164,13 @@ bool DeployDataStep::Perform(CookingData& data) } if (failed) { - data.Error(TEXT("Failed to copy .Net runtime data files.")); + data.Error(TEXT("Failed to copy .NET runtime data files.")); return true; } } else { - // Ask Flax.Build to provide .Net Host Runtime location for the target platform + // Ask Flax.Build to provide .NET Host Runtime location for the target platform String sdks; const Char *platformName, *archName; data.GetBuildPlatformName(platformName, archName); @@ -180,11 +186,11 @@ bool DeployDataStep::Perform(CookingData& data) } if (failed || !FileSystem::DirectoryExists(srcDotnet)) { - data.Error(TEXT("Failed to get .Net SDK location for a current platform.")); + data.Error(TEXT("Failed to get .NET SDK location for the current host platform.")); return true; } FileSystem::NormalizePath(srcDotnet); - LOG(Info, "Using .Net Runtime {} at {}", TEXT("Host"), srcDotnet); + LOG(Info, "Using .NET Runtime {} at {}", TEXT("Host"), srcDotnet); // Deploy runtime files const Char* corlibPrivateName = TEXT("System.Private.CoreLib.dll"); @@ -249,7 +255,7 @@ bool DeployDataStep::Perform(CookingData& data) DEPLOY_NATIVE_FILE("libmonosgen-2.0.dylib"); DEPLOY_NATIVE_FILE("libSystem.IO.Compression.Native.dylib"); DEPLOY_NATIVE_FILE("libSystem.Native.dylib"); - DEPLOY_NATIVE_FILE("libSystem.Net.Security.Native.dylib"); + DEPLOY_NATIVE_FILE("libSystem.NET.Security.Native.dylib"); DEPLOY_NATIVE_FILE("libSystem.Security.Cryptography.Native.Apple.dylib"); break; #undef DEPLOY_NATIVE_FILE @@ -257,7 +263,7 @@ bool DeployDataStep::Perform(CookingData& data) } if (failed) { - data.Error(TEXT("Failed to copy .Net runtime data files.")); + data.Error(TEXT("Failed to copy .NET runtime data files.")); return true; } } @@ -278,7 +284,7 @@ bool DeployDataStep::Perform(CookingData& data) } if (ScriptsBuilder::RunBuildTool(args)) { - data.Error(TEXT("Failed to optimize .Net class library.")); + data.Error(TEXT("Failed to optimize .NET class library.")); return true; } } From f38245b834d243f555425082354f729dec81f811 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 16 Dec 2023 16:13:49 +0200 Subject: [PATCH 52/72] Fix .NET runtime packaging with installed .NET 8 SDK --- Source/Editor/Cooker/Steps/DeployDataStep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index 013fa0e60..59ff0d744 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -116,7 +116,7 @@ bool DeployDataStep::Perform(CookingData& data) for (String& version : versions) { version = String(StringUtils::GetFileName(version)); - if (!version.StartsWith(TEXT("7."))) + if (!version.StartsWith(TEXT("7.")) && !version.StartsWith(TEXT("8."))) // .NET 7 or .NET 8 version.Clear(); } Sorting::QuickSort(versions); From 2bef880e214c2473248d72a7a413142a41a6c888 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 16:15:52 +0100 Subject: [PATCH 53/72] Fix render target pool over-allocation when changing render resolution frequently #2077 --- Source/Engine/Graphics/RenderBuffers.cpp | 3 + Source/Engine/Graphics/RenderTargetPool.cpp | 67 +++++++++------------ Source/Engine/Graphics/RenderTargetPool.h | 3 +- 3 files changed, 33 insertions(+), 40 deletions(-) diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index e0f4869ad..7ee900858 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -192,6 +192,9 @@ bool RenderBuffers::Init(int32 width, int32 height) _viewport = Viewport(0, 0, static_cast(width), static_cast(height)); LastEyeAdaptationTime = 0; + // Flush any pool render targets to prevent over-allocating GPU memory when resizing game viewport + RenderTargetPool::Flush(false, 4); + return result; } diff --git a/Source/Engine/Graphics/RenderTargetPool.cpp b/Source/Engine/Graphics/RenderTargetPool.cpp index fe22b4eaf..891610ab8 100644 --- a/Source/Engine/Graphics/RenderTargetPool.cpp +++ b/Source/Engine/Graphics/RenderTargetPool.cpp @@ -7,35 +7,31 @@ struct Entry { - bool IsOccupied; GPUTexture* RT; - uint64 LastFrameTaken; uint64 LastFrameReleased; uint32 DescriptionHash; + bool IsOccupied; }; namespace { - Array TemporaryRTs(64); + Array TemporaryRTs; } -void RenderTargetPool::Flush(bool force) +void RenderTargetPool::Flush(bool force, int32 framesOffset) { - const uint64 framesOffset = 3 * 60; + if (framesOffset < 0) + framesOffset = 3 * 60; // For how many frames RTs should be cached (by default) const uint64 maxReleaseFrame = Engine::FrameCount - framesOffset; force |= Engine::ShouldExit(); for (int32 i = 0; i < TemporaryRTs.Count(); i++) { - auto& tmp = TemporaryRTs[i]; - - if (!tmp.IsOccupied && (force || (tmp.LastFrameReleased < maxReleaseFrame))) + const auto& e = TemporaryRTs[i]; + if (!e.IsOccupied && (force || e.LastFrameReleased < maxReleaseFrame)) { - // Release - tmp.RT->DeleteObjectNow(); - TemporaryRTs.RemoveAt(i); - i--; - + e.RT->DeleteObjectNow(); + TemporaryRTs.RemoveAt(i--); if (TemporaryRTs.IsEmpty()) break; } @@ -48,19 +44,14 @@ GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc) const uint32 descHash = GetHash(desc); for (int32 i = 0; i < TemporaryRTs.Count(); i++) { - auto& tmp = TemporaryRTs[i]; - - if (!tmp.IsOccupied && tmp.DescriptionHash == descHash) + auto& e = TemporaryRTs[i]; + if (!e.IsOccupied && e.DescriptionHash == descHash) { - ASSERT(tmp.RT); - // Mark as used - tmp.IsOccupied = true; - tmp.LastFrameTaken = Engine::FrameCount; - return tmp.RT; + e.IsOccupied = true; + return e.RT; } } - #if !BUILD_RELEASE if (TemporaryRTs.Count() > 2000) { @@ -71,24 +62,23 @@ GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc) // Create new rt const String name = TEXT("TemporaryRT_") + StringUtils::ToString(TemporaryRTs.Count()); - auto newRenderTarget = GPUDevice::Instance->CreateTexture(name); - if (newRenderTarget->Init(desc)) + GPUTexture* rt = GPUDevice::Instance->CreateTexture(name); + if (rt->Init(desc)) { - Delete(newRenderTarget); + Delete(rt); LOG(Error, "Cannot create temporary render target. Description: {0}", desc.ToString()); return nullptr; } // Create temporary rt entry - Entry entry; - entry.IsOccupied = true; - entry.LastFrameReleased = 0; - entry.LastFrameTaken = Engine::FrameCount; - entry.RT = newRenderTarget; - entry.DescriptionHash = descHash; - TemporaryRTs.Add(entry); + Entry e; + e.IsOccupied = true; + e.LastFrameReleased = 0; + e.RT = rt; + e.DescriptionHash = descHash; + TemporaryRTs.Add(e); - return newRenderTarget; + return rt; } void RenderTargetPool::Release(GPUTexture* rt) @@ -98,14 +88,13 @@ void RenderTargetPool::Release(GPUTexture* rt) for (int32 i = 0; i < TemporaryRTs.Count(); i++) { - auto& tmp = TemporaryRTs[i]; - - if (tmp.RT == rt) + auto& e = TemporaryRTs[i]; + if (e.RT == rt) { // Mark as free - ASSERT(tmp.IsOccupied); - tmp.IsOccupied = false; - tmp.LastFrameReleased = Engine::FrameCount; + ASSERT(e.IsOccupied); + e.IsOccupied = false; + e.LastFrameReleased = Engine::FrameCount; return; } } diff --git a/Source/Engine/Graphics/RenderTargetPool.h b/Source/Engine/Graphics/RenderTargetPool.h index 345e5c81e..4ff69e2ea 100644 --- a/Source/Engine/Graphics/RenderTargetPool.h +++ b/Source/Engine/Graphics/RenderTargetPool.h @@ -15,7 +15,8 @@ public: /// Flushes the temporary render targets. /// /// True if release unused render targets by force, otherwise will use a few frames of delay. - static void Flush(bool force = false); + /// Amount of previous frames that should persist in the pool after flush. Resources used more than given value wil be freed. Use value of -1 to auto pick default duration. + static void Flush(bool force = false, int32 framesOffset = -1); /// /// Gets a temporary render target. From 6d5d61589423bad5489826970bceb59866b80585 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 16:38:16 +0100 Subject: [PATCH 54/72] Fix crash when drawing terrain without cached neighbor chunks #2087 --- Source/Engine/Terrain/TerrainChunk.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 737ce08f8..9bf1c1208 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -84,6 +84,8 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const DrawCall drawCall; if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, lod)) return; + if (!_neighbors[0]) + const_cast(this)->CacheNeighbors(); drawCall.InstanceCount = 1; drawCall.Material = _cachedDrawMaterial; renderContext.View.GetWorldMatrix(_transform, drawCall.World); From c145042f52f11630ecb8ecac44197ef6297c3678 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 17:08:54 +0100 Subject: [PATCH 55/72] Fix invalid BT node decorator linkage after removing it #2059 --- .../Editor/Surface/Archetypes/BehaviorTree.cs | 23 +++++++++++++++++++ .../Surface/Undo/AddRemoveNodeAction.cs | 8 ++++--- Source/Editor/Surface/VisjectSurface.cs | 2 +- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index f8cd7bb5a..07005df28 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -755,6 +755,29 @@ namespace FlaxEditor.Surface.Archetypes } } + /// + public override void OnDeleted(SurfaceNodeActions action) + { + // Unlink from the current parent (when deleted by user) + var node = Node; + if (node != null) + { + if (action == SurfaceNodeActions.User) + { + var decorators = node.DecoratorIds; + decorators.Remove(ID); + node.DecoratorIds = decorators; + } + else + { + node._decorators = null; + node.ResizeAuto(); + } + } + + base.OnDeleted(action); + } + public override void OnSurfaceCanEditChanged(bool canEdit) { base.OnSurfaceCanEditChanged(canEdit); diff --git a/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs b/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs index ba6770287..0deb66fbe 100644 --- a/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs +++ b/Source/Editor/Surface/Undo/AddRemoveNodeAction.cs @@ -19,6 +19,7 @@ namespace FlaxEditor.Surface.Undo private ushort _typeId; private Float2 _nodeLocation; private object[] _nodeValues; + private SurfaceNodeActions _actionType = SurfaceNodeActions.User; // Action usage flow is first to apply user effect such as removing/adding node, then we use Undo type so node can react to this public AddRemoveNodeAction(SurfaceNode node, bool isAdd) { @@ -38,6 +39,7 @@ namespace FlaxEditor.Surface.Undo Add(); else Remove(); + _actionType = SurfaceNodeActions.Undo; } /// @@ -67,8 +69,8 @@ namespace FlaxEditor.Surface.Undo else if (_nodeValues != null && _nodeValues.Length != 0) throw new InvalidOperationException("Invalid node values."); node.Location = _nodeLocation; - context.OnControlLoaded(node, SurfaceNodeActions.Undo); - node.OnSurfaceLoaded(SurfaceNodeActions.Undo); + context.OnControlLoaded(node, _actionType); + node.OnSurfaceLoaded(_actionType); context.MarkAsModified(); } @@ -89,7 +91,7 @@ namespace FlaxEditor.Surface.Undo // Remove node context.Nodes.Remove(node); - context.OnControlDeleted(node, SurfaceNodeActions.Undo); + context.OnControlDeleted(node, _actionType); context.MarkAsModified(); } diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 3ac702549..530f3265f 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -837,7 +837,7 @@ namespace FlaxEditor.Surface actions.Add(action); } - Undo.AddAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Remove node" : "Remove nodes")); + AddBatchedUndoAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Remove node" : "Remove nodes")); } } From 64e391df24a5e3356f115126c4ba3d776a61ed1e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 17:58:18 +0100 Subject: [PATCH 56/72] Refactor Visual Script debugger apis to use bindings generator --- Source/Editor/Editor.cs | 117 ---------------- .../Editor/Managed/ManagedEditor.Internal.cpp | 127 ------------------ Source/Editor/Managed/ManagedEditor.cpp | 89 ++++++++++++ Source/Editor/Managed/ManagedEditor.h | 25 ++++ Source/Editor/Surface/VisualScriptSurface.cs | 2 +- .../Windows/Assets/VisualScriptWindow.cs | 13 +- 6 files changed, 122 insertions(+), 251 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index b384b6515..393bf564e 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -1343,108 +1343,6 @@ namespace FlaxEditor public float AutoRebuildNavMeshTimeoutMs; } - [StructLayout(LayoutKind.Sequential)] - [NativeMarshalling(typeof(VisualScriptLocalMarshaller))] - internal struct VisualScriptLocal - { - public string Value; - public string ValueTypeName; - public uint NodeId; - public int BoxId; - } - - [CustomMarshaller(typeof(VisualScriptLocal), MarshalMode.Default, typeof(VisualScriptLocalMarshaller))] - internal static class VisualScriptLocalMarshaller - { - [StructLayout(LayoutKind.Sequential)] - internal struct VisualScriptLocalNative - { - public IntPtr Value; - public IntPtr ValueTypeName; - public uint NodeId; - public int BoxId; - } - - internal static VisualScriptLocal ConvertToManaged(VisualScriptLocalNative unmanaged) => ToManaged(unmanaged); - internal static VisualScriptLocalNative ConvertToUnmanaged(VisualScriptLocal managed) => ToNative(managed); - - internal static VisualScriptLocal ToManaged(VisualScriptLocalNative managed) - { - return new VisualScriptLocal() - { - Value = ManagedString.ToManaged(managed.Value), - ValueTypeName = ManagedString.ToManaged(managed.ValueTypeName), - NodeId = managed.NodeId, - BoxId = managed.BoxId, - }; - } - - internal static VisualScriptLocalNative ToNative(VisualScriptLocal managed) - { - return new VisualScriptLocalNative() - { - Value = ManagedString.ToNative(managed.Value), - ValueTypeName = ManagedString.ToNative(managed.ValueTypeName), - NodeId = managed.NodeId, - BoxId = managed.BoxId, - }; - } - - internal static void Free(VisualScriptLocalNative unmanaged) - { - ManagedString.Free(unmanaged.Value); - ManagedString.Free(unmanaged.ValueTypeName); - } - } - - [StructLayout(LayoutKind.Sequential)] - [NativeMarshalling(typeof(VisualScriptStackFrameMarshaller))] - internal struct VisualScriptStackFrame - { - public VisualScript Script; - public uint NodeId; - public int BoxId; - } - - [CustomMarshaller(typeof(VisualScriptStackFrame), MarshalMode.Default, typeof(VisualScriptStackFrameMarshaller))] - internal static class VisualScriptStackFrameMarshaller - { - [StructLayout(LayoutKind.Sequential)] - internal struct VisualScriptStackFrameNative - { - public IntPtr Script; - public uint NodeId; - public int BoxId; - } - - internal static VisualScriptStackFrame ConvertToManaged(VisualScriptStackFrameNative unmanaged) => ToManaged(unmanaged); - internal static VisualScriptStackFrameNative ConvertToUnmanaged(VisualScriptStackFrame managed) => ToNative(managed); - - internal static VisualScriptStackFrame ToManaged(VisualScriptStackFrameNative managed) - { - return new VisualScriptStackFrame() - { - Script = VisualScriptMarshaller.ConvertToManaged(managed.Script), - NodeId = managed.NodeId, - BoxId = managed.BoxId, - }; - } - - internal static VisualScriptStackFrameNative ToNative(VisualScriptStackFrame managed) - { - return new VisualScriptStackFrameNative() - { - Script = VisualScriptMarshaller.ConvertToUnmanaged(managed.Script), - NodeId = managed.NodeId, - BoxId = managed.BoxId, - }; - } - - internal static void Free(VisualScriptStackFrameNative unmanaged) - { - } - } - internal void BuildCommand(string arg) { if (TryBuildCommand(arg)) @@ -1723,21 +1621,6 @@ namespace FlaxEditor [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_RunVisualScriptBreakpointLoopTick", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_RunVisualScriptBreakpointLoopTick(float deltaTime); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptLocals", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - [return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "localsCount")] - internal static partial VisualScriptLocal[] Internal_GetVisualScriptLocals(out int localsCount); - - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptStackFrames", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - [return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "stackFrameCount")] - internal static partial VisualScriptStackFrame[] Internal_GetVisualScriptStackFrames(out int stackFrameCount); - - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptPreviousScopeFrame", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - internal static partial VisualScriptStackFrame Internal_GetVisualScriptPreviousScopeFrame(); - - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_EvaluateVisualScriptLocal", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - [return: MarshalAs(UnmanagedType.U1)] - internal static partial bool Internal_EvaluateVisualScriptLocal(IntPtr script, ref VisualScriptLocal local); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_DeserializeSceneObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_DeserializeSceneObject(IntPtr sceneObject, string json); diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 8855ba58a..ae8b71ee2 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -526,133 +526,6 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa Engine::OnDraw(); } -struct VisualScriptLocalManaged -{ - MString* Value; - MString* ValueTypeName; - uint32 NodeId; - int32 BoxId; -}; - -DEFINE_INTERNAL_CALL(MArray*) EditorInternal_GetVisualScriptLocals(int* localsCount) -{ - MArray* result = nullptr; - *localsCount = 0; - const auto stack = VisualScripting::GetThreadStackTop(); - if (stack && stack->Scope) - { - const int32 count = stack->Scope->Parameters.Length() + stack->Scope->ReturnedValues.Count(); - const MClass* mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptLocal"); - ASSERT(mclass); - result = MCore::Array::New(mclass, count); - VisualScriptLocalManaged local; - local.NodeId = MAX_uint32; - if (stack->Scope->Parameters.Length() != 0) - { - auto s = stack; - while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope) - s = s->PreviousFrame; - if (s) - local.NodeId = s->Node->ID; - } - VisualScriptLocalManaged* resultPtr = MCore::Array::GetAddress(result); - for (int32 i = 0; i < stack->Scope->Parameters.Length(); i++) - { - auto& v = stack->Scope->Parameters[i]; - local.BoxId = i + 1; - local.Value = MUtils::ToString(v.ToString()); - local.ValueTypeName = MUtils::ToString(v.Type.GetTypeName()); - resultPtr[i] = local; - } - for (int32 i = 0; i < stack->Scope->ReturnedValues.Count(); i++) - { - auto& v = stack->Scope->ReturnedValues[i]; - local.NodeId = v.NodeId; - local.BoxId = v.BoxId; - local.Value = MUtils::ToString(v.Value.ToString()); - local.ValueTypeName = MUtils::ToString(v.Value.Type.GetTypeName()); - resultPtr[stack->Scope->Parameters.Length() + i] = local; - } - *localsCount = count; - } - return result; -} - -struct VisualScriptStackFrameManaged -{ - MObject* Script; - uint32 NodeId; - int32 BoxId; -}; - -DEFINE_INTERNAL_CALL(MArray*) EditorInternal_GetVisualScriptStackFrames(int* stackFramesCount) -{ - MArray* result = nullptr; - *stackFramesCount = 0; - const auto stack = VisualScripting::GetThreadStackTop(); - if (stack) - { - int32 count = 0; - auto s = stack; - while (s) - { - s = s->PreviousFrame; - count++; - } - const MClass* mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptStackFrame"); - ASSERT(mclass); - result = MCore::Array::New(mclass, count); - VisualScriptStackFrameManaged* resultPtr = MCore::Array::GetAddress(result); - s = stack; - count = 0; - while (s) - { - VisualScriptStackFrameManaged frame; - frame.Script = s->Script->GetOrCreateManagedInstance(); - frame.NodeId = s->Node->ID; - frame.BoxId = s->Box ? s->Box->ID : MAX_uint32; - resultPtr[count] = frame; - s = s->PreviousFrame; - count++; - } - *stackFramesCount = count; - } - return result; -} - -DEFINE_INTERNAL_CALL(VisualScriptStackFrameManaged) EditorInternal_GetVisualScriptPreviousScopeFrame() -{ - VisualScriptStackFrameManaged frame; - Platform::MemoryClear(&frame, sizeof(frame)); - const auto stack = VisualScripting::GetThreadStackTop(); - if (stack) - { - auto s = stack; - while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope) - s = s->PreviousFrame; - if (s && s->PreviousFrame) - { - s = s->PreviousFrame; - frame.Script = s->Script->GetOrCreateManagedInstance(); - frame.NodeId = s->Node->ID; - frame.BoxId = s->Box ? s->Box->ID : MAX_uint32; - } - } - return frame; -} - -DEFINE_INTERNAL_CALL(bool) EditorInternal_EvaluateVisualScriptLocal(VisualScript* script, VisualScriptLocalManaged* local) -{ - Variant v; - if (VisualScripting::Evaluate(script, VisualScripting::GetThreadStackTop()->Instance, local->NodeId, local->BoxId, v)) - { - local->Value = MUtils::ToString(v.ToString()); - local->ValueTypeName = MUtils::ToString(v.Type.GetTypeName()); - return true; - } - return false; -} - DEFINE_INTERNAL_CALL(void) EditorInternal_DeserializeSceneObject(SceneObject* sceneObject, MString* jsonObj) { PROFILE_CPU_NAMED("DeserializeSceneObject"); diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index bed742885..f7e0c1498 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -489,6 +489,95 @@ void ManagedEditor::RequestStartPlayOnEditMode() Internal_RequestStartPlayOnEditMode->Invoke(GetManagedInstance(), nullptr, nullptr); } +Array ManagedEditor::GetVisualScriptStackFrames() +{ + Array result; + const auto stack = VisualScripting::GetThreadStackTop(); + auto s = stack; + while (s) + { + VisualScriptStackFrame& frame = result.AddOne(); + frame.Script = s->Script; + frame.NodeId = s->Node->ID; + frame.BoxId = s->Box ? s->Box->ID : MAX_uint32; + s = s->PreviousFrame; + } + return result; +} + +ManagedEditor::VisualScriptStackFrame ManagedEditor::GetVisualScriptPreviousScopeFrame() +{ + VisualScriptStackFrame frame; + Platform::MemoryClear(&frame, sizeof(frame)); + const auto stack = VisualScripting::GetThreadStackTop(); + if (stack) + { + auto s = stack; + while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope) + s = s->PreviousFrame; + if (s && s->PreviousFrame) + { + s = s->PreviousFrame; + frame.Script = s->Script; + frame.NodeId = s->Node->ID; + frame.BoxId = s->Box ? s->Box->ID : MAX_uint32; + } + } + return frame; +} + +Array ManagedEditor::GetVisualScriptLocals() +{ + Array result; + const auto stack = VisualScripting::GetThreadStackTop(); + if (stack && stack->Scope) + { + const int32 count = stack->Scope->Parameters.Length() + stack->Scope->ReturnedValues.Count(); + result.Resize(count); + VisualScriptLocal local; + local.NodeId = MAX_uint32; + if (stack->Scope->Parameters.Length() != 0) + { + auto s = stack; + while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope) + s = s->PreviousFrame; + if (s) + local.NodeId = s->Node->ID; + } + for (int32 i = 0; i < stack->Scope->Parameters.Length(); i++) + { + auto& v = stack->Scope->Parameters[i]; + local.BoxId = i + 1; + local.Value = v.ToString(); + local.ValueTypeName = v.Type.GetTypeName(); + result[i] = local; + } + for (int32 i = 0; i < stack->Scope->ReturnedValues.Count(); i++) + { + auto& v = stack->Scope->ReturnedValues[i]; + local.NodeId = v.NodeId; + local.BoxId = v.BoxId; + local.Value = v.Value.ToString(); + local.ValueTypeName = v.Value.Type.GetTypeName(); + result[stack->Scope->Parameters.Length() + i] = local; + } + } + return result; +} + +bool ManagedEditor::EvaluateVisualScriptLocal(VisualScript* script, VisualScriptLocal& local) +{ + Variant v; + const auto stack = VisualScripting::GetThreadStackTop(); + if (stack && VisualScripting::Evaluate(script, stack->Instance, local.NodeId, local.BoxId, v)) + { + local.Value = v.ToString(); + local.ValueTypeName = v.Type.GetTypeName(); + return true; + } + return false; +} + void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly) { ASSERT(!HasManagedInstance()); diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 93d1723a3..8c9571cfd 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -210,6 +210,31 @@ public: API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath); #endif +public: + API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame + { + DECLARE_SCRIPTING_TYPE_MINIMAL(VisualScriptStackFrame); + + API_FIELD() class VisualScript* Script; + API_FIELD() uint32 NodeId; + API_FIELD() int32 BoxId; + }; + + API_STRUCT(Internal, NoDefault) struct VisualScriptLocal + { + DECLARE_SCRIPTING_TYPE_MINIMAL(VisualScriptLocal); + + API_FIELD() String Value; + API_FIELD() String ValueTypeName; + API_FIELD() uint32 NodeId; + API_FIELD() int32 BoxId; + }; + + API_FUNCTION(Internal) static Array GetVisualScriptStackFrames(); + API_FUNCTION(Internal) static VisualScriptStackFrame GetVisualScriptPreviousScopeFrame(); + API_FUNCTION(Internal) static Array GetVisualScriptLocals(); + API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local); + private: void OnEditorAssemblyLoaded(MAssembly* assembly); diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 2f8d4cda8..18a4695aa 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -123,7 +123,7 @@ namespace FlaxEditor.Surface BoxId = box.ID, }; var script = ((Windows.Assets.VisualScriptWindow)box.Surface.Owner).Asset; - if (Editor.Internal_EvaluateVisualScriptLocal(Object.GetUnmanagedPtr(script), ref local)) + if (Editor.EvaluateVisualScriptLocal(script, ref local)) { text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})"; return true; diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 79157f850..802269677 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -797,11 +797,12 @@ namespace FlaxEditor.Windows.Assets } // Check if any breakpoint was hit - for (int i = 0; i < Surface.Breakpoints.Count; i++) + var breakpoints = Surface.Breakpoints; + for (int i = 0; i < breakpoints.Count; i++) { - if (Surface.Breakpoints[i].ID == flowInfo.NodeId) + if (breakpoints[i].ID == flowInfo.NodeId) { - OnDebugBreakpointHit(ref flowInfo, Surface.Breakpoints[i]); + OnDebugBreakpointHit(ref flowInfo, breakpoints[i]); break; } } @@ -820,7 +821,7 @@ namespace FlaxEditor.Windows.Assets var state = (BreakpointHangState)Editor.Instance.Simulation.BreakpointHangTag; if (state.Locals == null) { - state.Locals = Editor.Internal_GetVisualScriptLocals(out var _); + state.Locals = Editor.GetVisualScriptLocals(); Editor.Instance.Simulation.BreakpointHangTag = state; } return state; @@ -831,7 +832,7 @@ namespace FlaxEditor.Windows.Assets var state = (BreakpointHangState)Editor.Instance.Simulation.BreakpointHangTag; if (state.StackFrames == null) { - state.StackFrames = Editor.Internal_GetVisualScriptStackFrames(out var _); + state.StackFrames = Editor.GetVisualScriptStackFrames(); Editor.Instance.Simulation.BreakpointHangTag = state; } return state; @@ -976,7 +977,7 @@ namespace FlaxEditor.Windows.Assets return; // Break on any of the output connects from the previous scope node - var frame = Editor.Internal_GetVisualScriptPreviousScopeFrame(); + var frame = Editor.GetVisualScriptPreviousScopeFrame(); if (frame.Script != null) { if (_debugStepOutNodesIds == null) From 0dd7e8653750453db58039be9c6ff00f633ffa58 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 18:14:16 +0100 Subject: [PATCH 57/72] Add better Visual Script debugger tooltips display --- Source/Editor/Surface/VisualScriptSurface.cs | 43 +++++++++++++++++-- Source/Engine/Content/Assets/VisualScript.cpp | 5 +++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 18a4695aa..90312c6b7 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -62,6 +62,19 @@ namespace FlaxEditor.Surface return true; } + private string GetBoxDebuggerTooltip(ref Editor.VisualScriptLocal local) + { + if (string.IsNullOrEmpty(local.ValueTypeName)) + { + if (string.IsNullOrEmpty(local.Value)) + return string.Empty; + return local.Value; + } + if (string.IsNullOrEmpty(local.Value)) + return $"({local.ValueTypeName})"; + return $"{local.Value}\n({local.ValueTypeName})"; + } + /// public override void OnNodeBreakpointEdited(SurfaceNode node) { @@ -95,7 +108,7 @@ namespace FlaxEditor.Surface ref var local = ref state.Locals[i]; if (local.BoxId == box.ID && local.NodeId == box.ParentNode.ID) { - text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})"; + text = GetBoxDebuggerTooltip(ref local); return true; } } @@ -107,7 +120,7 @@ namespace FlaxEditor.Surface ref var local = ref state.Locals[i]; if (local.BoxId == connectedBox.ID && local.NodeId == connectedBox.ParentNode.ID) { - text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})"; + text = GetBoxDebuggerTooltip(ref local); return true; } } @@ -125,7 +138,31 @@ namespace FlaxEditor.Surface var script = ((Windows.Assets.VisualScriptWindow)box.Surface.Owner).Asset; if (Editor.EvaluateVisualScriptLocal(script, ref local)) { - text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})"; + // Check if got no value (null) + if (string.IsNullOrEmpty(local.ValueTypeName) && string.Equals(local.Value, "null", StringComparison.Ordinal)) + { + var connections = box.Connections; + if (connections.Count == 0 && box.Archetype.ValueIndex >= 0 && box.ParentNode.Values != null && box.Archetype.ValueIndex < box.ParentNode.Values.Length) + { + // Special case when there is no value but the box has no connection and uses + var defaultValue = box.ParentNode.Values[box.Archetype.ValueIndex]; + if (defaultValue != null) + { + local.Value = defaultValue.ToString(); + local.ValueTypeName = defaultValue.GetType().FullName; + } + } + else if (connections.Count == 1) + { + // Special case when there is no value but the box has a connection with valid value to try to use it instead + box = connections[0]; + local.NodeId = box.ParentNode.ID; + local.BoxId = box.ID; + Editor.EvaluateVisualScriptLocal(script, ref local); + } + } + + text = GetBoxDebuggerTooltip(ref local); return true; } } diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 9748ba60c..320116d60 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -892,6 +892,11 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& PrintStack(LogType::Error); break; } + if (boxBase->ID == 1) + { + value = instance; + break; + } // TODO: check if instance is of event type (including inheritance) // Add Visual Script method to the event bindings table From 8418ca56e8cfeb6071264fbd5b991694590492cf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 16 Dec 2023 18:16:00 +0100 Subject: [PATCH 58/72] Missing comment part --- Source/Editor/Surface/VisualScriptSurface.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 90312c6b7..4635e09d7 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Surface var connections = box.Connections; if (connections.Count == 0 && box.Archetype.ValueIndex >= 0 && box.ParentNode.Values != null && box.Archetype.ValueIndex < box.ParentNode.Values.Length) { - // Special case when there is no value but the box has no connection and uses + // Special case when there is no value but the box has no connection and uses default value var defaultValue = box.ParentNode.Values[box.Archetype.ValueIndex]; if (defaultValue != null) { From 272a147c2e12f049a7308030668054e815e4be67 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 16 Dec 2023 20:36:27 -0600 Subject: [PATCH 59/72] Add saved colors to color picker. --- .../Editor/GUI/Dialogs/ColorPickerDialog.cs | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs index 27878a763..61c60110b 100644 --- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs +++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs @@ -3,6 +3,8 @@ using FlaxEditor.GUI.Input; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Json; +using System.Collections.Generic; namespace FlaxEditor.GUI.Dialogs { @@ -30,6 +32,8 @@ namespace FlaxEditor.GUI.Dialogs private const float HSVMargin = 0.0f; private const float ChannelsMargin = 4.0f; private const float ChannelTextWidth = 12.0f; + private const float SavedColorButtonWidth = 20.0f; + private const float SavedColorButtonHeight = 20.0f; private Color _initialValue; private Color _value; @@ -52,6 +56,9 @@ namespace FlaxEditor.GUI.Dialogs private Button _cOK; private Button _cEyedropper; + private List _savedColors = new List(); + private List