From 5aa5c97e4c08e756d3d3398954de0a4598b581e9 Mon Sep 17 00:00:00 2001 From: MrCapy0 <167662176+MrCapy0@users.noreply.github.com> Date: Sat, 4 Jan 2025 09:43:33 -0400 Subject: [PATCH 01/12] Add "root" parameter to Level.GetScripts() --- Source/Engine/Level/Level.cpp | 9 +++++++-- Source/Engine/Level/Level.cs | 11 ++++++----- Source/Engine/Level/Level.h | 5 +++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index f24a853e3..db9812508 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -1613,13 +1613,18 @@ Array Level::GetActors(const MClass* type, bool activeOnly) return result; } -Array Level::GetScripts(const MClass* type) +Array Level::GetScripts(const MClass* type, Actor* root) { Array result; + CHECK_RETURN(type, result); ScopeLock lock(ScenesLock); - for (int32 i = 0; i < Scenes.Count(); i++) + + if (root) + ::GetScripts(type, root, result); + else for (int32 i = 0; i < Scenes.Count(); i++) ::GetScripts(type, Scenes[i], result); + return result; } diff --git a/Source/Engine/Level/Level.cs b/Source/Engine/Level/Level.cs index ae596e794..9efa496ef 100644 --- a/Source/Engine/Level/Level.cs +++ b/Source/Engine/Level/Level.cs @@ -66,7 +66,7 @@ namespace FlaxEngine { return FindActor(typeof(T)) as T; } - + /// /// Tries to find actor of the given type and name in all loaded scenes. /// @@ -77,7 +77,7 @@ namespace FlaxEngine { return FindActor(typeof(T), name) as T; } - + /// /// Tries to find actor of the given type and tag in a root actor or all loaded scenes. /// @@ -102,13 +102,14 @@ namespace FlaxEngine } /// - /// Finds all the scripts of the given type in all the loaded scenes. + /// Finds all the scripts of the given type in an actor or all the loaded scenes. /// /// Type of the object. + /// The root to find scripts. If null, will search in all scenes /// Found scripts list. - public static T[] GetScripts() where T : Script + public static T[] GetScripts(Actor root = null) where T : Script { - var scripts = GetScripts(typeof(T)); + var scripts = GetScripts(typeof(T), root); if (typeof(T) == typeof(Script)) return (T[])scripts; if (scripts.Length == 0) diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index b9c6b698a..44d9a3584 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -469,11 +469,12 @@ public: API_FUNCTION() static Array GetActors(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, bool activeOnly = false); /// - /// Finds all the scripts of the given type in all the loaded scenes. + /// Finds all the scripts of the given type in an actor or all the loaded scenes. /// /// Type of the script to search for. Includes any scripts derived from the type. + /// The root to find scripts. If null, will search in all scenes /// Found scripts list. - API_FUNCTION() static Array GetScripts(API_PARAM(Attributes="TypeReference(typeof(Script))") const MClass* type); + API_FUNCTION() static Array GetScripts(API_PARAM(Attributes="TypeReference(typeof(Script))") const MClass* type, Actor* root = nullptr); /// /// Tries to find scene with given ID. From 0764ff6cb7f327dfa728c5a6d71fe94b83241139 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 31 Jan 2025 23:56:30 +0200 Subject: [PATCH 02/12] Fix C# projects not compiling in VS with native code plugins present --- Source/Tools/Flax.Build/Build/Builder.Projects.cs | 4 ++-- Source/Tools/Flax.Build/Projects/Project.cs | 4 ++-- Source/Tools/Flax.Build/Projects/ProjectGenerator.cs | 2 +- .../Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs | 2 +- .../Projects/VisualStudio/CSSDKProjectGenerator.cs | 5 ++--- .../Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs | 2 +- .../Projects/VisualStudio/VisualStudioProjectGenerator.cs | 2 +- .../VisualStudioCode/VisualStudioCodeProjectGenerator.cs | 2 +- Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs | 2 +- 9 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index 87c5b4d89..c4aac04f1 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -559,7 +559,7 @@ namespace Flax.Build foreach (var project in projects) { Log.Verbose(project.Name + " -> " + project.Path); - project.Generate(solutionPath); + project.Generate(solutionPath, project == mainSolutionProject); } } @@ -638,7 +638,7 @@ namespace Flax.Build using (new ProfileEventScope("GenerateProject")) { Log.Verbose("Project " + rulesProjectName + " -> " + project.Path); - dotNetProjectGenerator.GenerateProject(project, solutionPath); + dotNetProjectGenerator.GenerateProject(project, solutionPath, project == mainSolutionProject); } projects.Add(project); diff --git a/Source/Tools/Flax.Build/Projects/Project.cs b/Source/Tools/Flax.Build/Projects/Project.cs index 7cfe097b9..0f52ae7f5 100644 --- a/Source/Tools/Flax.Build/Projects/Project.cs +++ b/Source/Tools/Flax.Build/Projects/Project.cs @@ -253,9 +253,9 @@ namespace Flax.Build.Projects /// /// Generates the project. /// - public virtual void Generate(string solutionPath) + public virtual void Generate(string solutionPath, bool isMainProject) { - Generator.GenerateProject(this, solutionPath); + Generator.GenerateProject(this, solutionPath, isMainProject); } /// diff --git a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs index 673f2e7d6..fbbeca14d 100644 --- a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs @@ -52,7 +52,7 @@ namespace Flax.Build.Projects /// Generates the project. /// /// The project. - public abstract void GenerateProject(Project project, string solutionPath); + public abstract void GenerateProject(Project project, string solutionPath, bool isMainProject); /// /// Generates the solution. diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs index c5359f66e..9aba45d75 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSProjectGenerator.cs @@ -26,7 +26,7 @@ namespace Flax.Build.Projects.VisualStudio public override TargetType? Type => TargetType.DotNet; /// - public override void GenerateProject(Project project, string solutionPath) + public override void GenerateProject(Project project, string solutionPath, bool isMainProject) { var csProjectFileContent = new StringBuilder(); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs index 9059cda94..4058f9cf1 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs @@ -26,7 +26,7 @@ namespace Flax.Build.Projects.VisualStudio public override TargetType? Type => TargetType.DotNetCore; /// - public override void GenerateProject(Project project, string solutionPath) + public override void GenerateProject(Project project, string solutionPath, bool isMainProject) { var dotnetSdk = DotNetSdk.Instance; var csProjectFileContent = new StringBuilder(); @@ -109,8 +109,7 @@ 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, 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"); + // Custom .targets file for overriding MSBuild build tasks, only invoke Flax.Build once per Flax project 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; diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index fabae2b86..6196ef6b5 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -55,7 +55,7 @@ namespace Flax.Build.Projects.VisualStudio } /// - public override void GenerateProject(Project project, string solutionPath) + public override void GenerateProject(Project project, string solutionPath, bool isMainProject) { var vcProjectFileContent = new StringBuilder(); var vcFiltersFileContent = new StringBuilder(); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index a357a8c89..e3b214974 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -22,7 +22,7 @@ namespace Flax.Build.Projects.VisualStudio public override Guid ProjectTypeGuid => ProjectTypeGuids.Android; /// - public override void Generate(string solutionPath) + public override void Generate(string solutionPath, bool isMainProject) { // Try to reuse the existing project guid from existing files ProjectGuid = GetProjectGuid(Path, Name); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index 268bb87e1..367a081e3 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -38,7 +38,7 @@ namespace Flax.Build.Projects.VisualStudioCode } /// - public override void GenerateProject(Project project, string solutionPath) + public override void GenerateProject(Project project, string solutionPath, bool isMainProject) { // Not used, solution contains all projects definitions } diff --git a/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs index b8c31b252..5dba0cba1 100644 --- a/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/XCodeProjectGenerator.cs @@ -42,7 +42,7 @@ namespace Flax.Build.Projects } /// - public override void GenerateProject(Project project, string solutionPath) + public override void GenerateProject(Project project, string solutionPath, bool isMainProject) { } From 1c3d1b623db117631f93ce139759bd520fc71b94 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 25 Feb 2025 15:31:46 +0100 Subject: [PATCH 03/12] Optimize and cleanup some actor code --- Source/Engine/Level/Actor.cpp | 44 ++++++--------------- Source/Engine/Level/SceneObjectsFactory.cpp | 4 +- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 1db13145d..a9e0ec9f2 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -99,7 +99,7 @@ void Actor::SetSceneInHierarchy(Scene* scene) for (int32 i = 0; i < Children.Count(); i++) { - Children[i]->SetSceneInHierarchy(scene); + Children.Get()[i]->SetSceneInHierarchy(scene); } } @@ -111,7 +111,7 @@ void Actor::OnEnableInHierarchy() for (int32 i = 0; i < Children.Count(); i++) { - Children[i]->OnEnableInHierarchy(); + Children.Get()[i]->OnEnableInHierarchy(); } } } @@ -122,7 +122,7 @@ void Actor::OnDisableInHierarchy() { for (int32 i = 0; i < Children.Count(); i++) { - Children[i]->OnDisableInHierarchy(); + Children.Get()[i]->OnDisableInHierarchy(); } OnDisable(); @@ -192,7 +192,7 @@ void Actor::OnDeleteObject() #endif for (int32 i = 0; i < Children.Count(); i++) { - auto e = Children[i]; + auto e = Children.Get()[i]; ASSERT(e->_parent == this); e->_parent = nullptr; e->DeleteObject(); @@ -208,7 +208,7 @@ void Actor::OnDeleteObject() #endif for (int32 i = 0; i < Scripts.Count(); i++) { - auto script = Scripts[i]; + auto script = Scripts.Get()[i]; ASSERT(script->_parent == this); if (script->_wasAwakeCalled) { @@ -370,8 +370,6 @@ void Actor::SetOrderInParent(int32 index) { if (!_parent) return; - - // Cache data auto& parentChildren = _parent->Children; const int32 currentIndex = parentChildren.Find(this); ASSERT(currentIndex != INVALID_INDEX); @@ -380,8 +378,6 @@ void Actor::SetOrderInParent(int32 index) if (currentIndex != index) { parentChildren.RemoveAtKeepOrder(currentIndex); - - // Check if index is invalid if (index < 0 || index >= parentChildren.Count()) { // Append at the end @@ -894,9 +890,7 @@ void Actor::BreakPrefabLink() void Actor::Initialize() { -#if ENABLE_ASSERTION - CHECK(!IsDuringPlay()); -#endif + CHECK_DEBUG(!IsDuringPlay()); // Cache if (_parent) @@ -910,9 +904,7 @@ void Actor::Initialize() void Actor::BeginPlay(SceneBeginData* data) { -#if ENABLE_ASSERTION - CHECK(!IsDuringPlay()); -#endif + CHECK_DEBUG(!IsDuringPlay()); // Set flag Flags |= ObjectFlags::IsDuringPlay; @@ -944,9 +936,7 @@ void Actor::BeginPlay(SceneBeginData* data) void Actor::EndPlay() { -#if ENABLE_ASSERTION - CHECK(IsDuringPlay()); -#endif + CHECK_DEBUG(IsDuringPlay()); // Fire event for scripting if (IsActiveInHierarchy() && GetScene()) @@ -1184,9 +1174,7 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) void Actor::OnEnable() { -#if ENABLE_ASSERTION - CHECK(!_isEnabled); -#endif + CHECK_DEBUG(!_isEnabled); _isEnabled = true; for (int32 i = 0; i < Scripts.Count(); i++) @@ -1206,9 +1194,7 @@ void Actor::OnEnable() void Actor::OnDisable() { -#if ENABLE_ASSERTION - CHECK(_isEnabled); -#endif + CHECK_DEBUG(_isEnabled); _isEnabled = false; for (int32 i = Scripts.Count() - 1; i >= 0; i--) @@ -1295,12 +1281,8 @@ void Actor::OnLayerChanged() BoundingBox Actor::GetBoxWithChildren() const { BoundingBox result = GetBox(); - for (int32 i = 0; i < Children.Count(); i++) - { - BoundingBox::Merge(result, Children[i]->GetBoxWithChildren(), result); - } - + BoundingBox::Merge(result, Children.Get()[i]->GetBoxWithChildren(), result); return result; } @@ -1315,9 +1297,7 @@ BoundingBox Actor::GetEditorBoxChildren() const { BoundingBox result = GetEditorBox(); for (int32 i = 0; i < Children.Count(); i++) - { - BoundingBox::Merge(result, Children[i]->GetEditorBoxChildren(), result); - } + BoundingBox::Merge(result, Children.Get()[i]->GetEditorBoxChildren(), result); return result; } diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 520154b28..a53911361 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -244,9 +244,7 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, const ISerializable::D void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISerializable::DeserializeStream& stream) { -#if ENABLE_ASSERTION - CHECK(obj); -#endif + CHECK_DEBUG(obj); ISerializeModifier* modifier = context.GetModifier(); LogContextScope logContext(obj->GetID()); From ca6544204b0bb413083f322ee4a80f077a2141ad Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 25 Feb 2025 15:32:20 +0100 Subject: [PATCH 04/12] Fix actor hierarchy initialization when it gets modified by a script on the fly #2940 #2623 #2751 --- Source/Engine/Level/Actor.cpp | 36 +++++++++++++++++++++++++++++++++++ Source/Engine/Level/Actor.h | 1 + 2 files changed, 37 insertions(+) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index a9e0ec9f2..e74db1933 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -37,6 +37,10 @@ #define ACTOR_ORIENTATION_EPSILON 0.000000001f +// Start loop over actor children/scripts from the beginning to account for any newly added or removed actors. +#define ACTOR_LOOP_START_MODIFIED_HIERARCHY() _isHierarchyDirty = false +#define ACTOR_LOOP_CHECK_MODIFIED_HIERARCHY() if (_isHierarchyDirty) { _isHierarchyDirty = false; i = -1; } + namespace { Actor* GetChildByPrefabObjectId(Actor* a, const Guid& prefabObjectId) @@ -74,6 +78,7 @@ Actor::Actor(const SpawnParams& params) , _isActiveInHierarchy(true) , _isPrefabRoot(false) , _isEnabled(false) + , _isHierarchyDirty(false) , _layer(0) , _staticFlags(StaticFlags::FullyStatic) , _localTransform(Transform::Identity) @@ -109,9 +114,11 @@ void Actor::OnEnableInHierarchy() { OnEnable(); + ACTOR_LOOP_START_MODIFIED_HIERARCHY(); for (int32 i = 0; i < Children.Count(); i++) { Children.Get()[i]->OnEnableInHierarchy(); + ACTOR_LOOP_CHECK_MODIFIED_HIERARCHY(); } } } @@ -120,9 +127,11 @@ void Actor::OnDisableInHierarchy() { if (IsActiveInHierarchy() && GetScene() && _isEnabled) { + ACTOR_LOOP_START_MODIFIED_HIERARCHY(); for (int32 i = 0; i < Children.Count(); i++) { Children.Get()[i]->OnDisableInHierarchy(); + ACTOR_LOOP_CHECK_MODIFIED_HIERARCHY(); } OnDisable(); @@ -165,6 +174,7 @@ void Actor::OnDeleteObject() { // Unlink from the parent _parent->Children.RemoveKeepOrder(this); + _parent->_isHierarchyDirty = true; _parent = nullptr; _scene = nullptr; } @@ -173,6 +183,7 @@ void Actor::OnDeleteObject() { // Unlink from the parent _parent->Children.RemoveKeepOrder(this); + _parent->_isHierarchyDirty = true; _parent = nullptr; _scene = nullptr; } @@ -289,6 +300,7 @@ void Actor::SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefa if (_parent) { _parent->Children.RemoveKeepOrder(this); + _parent->_isHierarchyDirty = true; } // Set value @@ -298,6 +310,7 @@ void Actor::SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefa if (_parent) { _parent->Children.Add(this); + _parent->_isHierarchyDirty = true; } // Sync scene change if need to @@ -388,6 +401,7 @@ void Actor::SetOrderInParent(int32 index) // Change order parentChildren.Insert(index, this); } + _parent->_isHierarchyDirty = true; // Fire event OnOrderInParentChanged(); @@ -912,11 +926,15 @@ void Actor::BeginPlay(SceneBeginData* data) OnBeginPlay(); // Update scripts + ACTOR_LOOP_START_MODIFIED_HIERARCHY(); for (int32 i = 0; i < Scripts.Count(); i++) { auto e = Scripts.Get()[i]; if (!e->IsDuringPlay()) + { e->BeginPlay(data); + ACTOR_LOOP_CHECK_MODIFIED_HIERARCHY(); + } } // Update children @@ -924,7 +942,10 @@ void Actor::BeginPlay(SceneBeginData* data) { auto e = Children.Get()[i]; if (!e->IsDuringPlay()) + { e->BeginPlay(data); + ACTOR_LOOP_CHECK_MODIFIED_HIERARCHY(); + } } // Fire events for scripting @@ -963,19 +984,27 @@ void Actor::EndPlay() Flags &= ~ObjectFlags::IsDuringPlay; // Call event deeper + ACTOR_LOOP_START_MODIFIED_HIERARCHY(); for (int32 i = 0; i < Children.Count(); i++) { auto e = Children.Get()[i]; if (e->IsDuringPlay()) + { e->EndPlay(); + ACTOR_LOOP_CHECK_MODIFIED_HIERARCHY(); + } } // Inform attached scripts + ACTOR_LOOP_START_MODIFIED_HIERARCHY(); for (int32 i = 0; i < Scripts.Count(); i++) { auto e = Scripts.Get()[i]; if (e->IsDuringPlay()) + { e->EndPlay(); + ACTOR_LOOP_CHECK_MODIFIED_HIERARCHY(); + } } // Cleanup managed object @@ -1177,18 +1206,25 @@ void Actor::OnEnable() CHECK_DEBUG(!_isEnabled); _isEnabled = true; + ACTOR_LOOP_START_MODIFIED_HIERARCHY(); for (int32 i = 0; i < Scripts.Count(); i++) { auto script = Scripts[i]; if (script->GetEnabled() && !script->_wasStartCalled) + { script->Start(); + ACTOR_LOOP_CHECK_MODIFIED_HIERARCHY(); + } } for (int32 i = 0; i < Scripts.Count(); i++) { auto script = Scripts[i]; if (script->GetEnabled() && !script->_wasEnableCalled) + { script->Enable(); + ACTOR_LOOP_CHECK_MODIFIED_HIERARCHY(); + } } } diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 4cc475d13..efd27e9fd 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -37,6 +37,7 @@ protected: uint16 _isActiveInHierarchy : 1; uint16 _isPrefabRoot : 1; uint16 _isEnabled : 1; + uint16 _isHierarchyDirty : 1; uint16 _drawNoCulling : 1; uint16 _drawCategory : 4; byte _layer; From dcba97f84a1f11c6f1c6a2740b42d204fb68d5e1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 25 Feb 2025 16:46:17 +0100 Subject: [PATCH 05/12] Fix missing masked terrain rendering #3177 --- Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 2a040b55a..53b54a946 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -191,8 +191,11 @@ bool TerrainMaterialShader::Load() psDesc.DepthFunc = ComparisonFunc::Less; psDesc.HS = nullptr; psDesc.DS = nullptr; - // TODO: masked terrain materials (depth pass should clip holes) psDesc.PS = nullptr; + if (EnumHasAnyFlags(_info.UsageFlags, MaterialUsageFlags::UseMask)) + { + psDesc.PS = _shader->GetPS("PS_Depth"); + } _cache.Depth.Init(psDesc); return false; From 83c3201ef8f0f1edf9bd8740935f919de838c4eb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 25 Feb 2025 18:09:47 +0100 Subject: [PATCH 06/12] Fix regression in `rapidjson` update --- Source/Engine/Serialization/JsonWriter.h | 2 +- Source/ThirdParty/rapidjson/writer.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Engine/Serialization/JsonWriter.h b/Source/Engine/Serialization/JsonWriter.h index b35269081..396f6a1e3 100644 --- a/Source/Engine/Serialization/JsonWriter.h +++ b/Source/Engine/Serialization/JsonWriter.h @@ -86,7 +86,7 @@ public: void String(const StringAnsi& value) { - String(value.Get(), static_cast(value.Length())); + String(value.Get(), value.Length()); } FORCE_INLINE void RawValue(const StringAnsi& str) diff --git a/Source/ThirdParty/rapidjson/writer.h b/Source/ThirdParty/rapidjson/writer.h index 632e02ce7..da3f42552 100644 --- a/Source/ThirdParty/rapidjson/writer.h +++ b/Source/ThirdParty/rapidjson/writer.h @@ -202,7 +202,6 @@ public: } bool String(const Ch* str, SizeType length, bool copy = false) { - RAPIDJSON_ASSERT(str != 0); (void)copy; Prefix(kStringType); return EndValue(WriteString(str, length)); From 8f1a1827f2c12346733af9575ecb1dd50e048856 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 25 Feb 2025 22:17:18 +0100 Subject: [PATCH 07/12] Fix crash in OpenAL audio backend on context rebuild #3225 --- .../Engine/Audio/OpenAL/AudioBackendOAL.cpp | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index f9456d7dd..c7e9dc41b 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -38,6 +38,28 @@ } #endif +const Char* GetOpenALErrorString(int error) +{ + switch (error) + { + case AL_NO_ERROR: + return TEXT("AL_NO_ERROR"); + case AL_INVALID_NAME: + return TEXT("AL_INVALID_NAME"); + case AL_INVALID_ENUM: + return TEXT("AL_INVALID_ENUM"); + case AL_INVALID_VALUE: + return TEXT("AL_INVALID_VALUE"); + case AL_INVALID_OPERATION: + return TEXT("AL_INVALID_OPERATION"); + case AL_OUT_OF_MEMORY: + return TEXT("AL_OUT_OF_MEMORY"); + default: + break; + } + return TEXT("???"); +} + namespace ALC { struct SourceData @@ -181,6 +203,12 @@ namespace ALC { states.Add({ source->GetState(), source->GetTime() }); source->Stop(); + if (source->SourceID) + { + alDeleteSources(1, &source->SourceID); + ALC_CHECK_ERROR(alDeleteSources); + source->SourceID = 0; + } } } @@ -253,28 +281,6 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth) return 0; } -const Char* GetOpenALErrorString(int error) -{ - switch (error) - { - case AL_NO_ERROR: - return TEXT("AL_NO_ERROR"); - case AL_INVALID_NAME: - return TEXT("AL_INVALID_NAME"); - case AL_INVALID_ENUM: - return TEXT("AL_INVALID_ENUM"); - case AL_INVALID_VALUE: - return TEXT("AL_INVALID_VALUE"); - case AL_INVALID_OPERATION: - return TEXT("AL_INVALID_OPERATION"); - case AL_OUT_OF_MEMORY: - return TEXT("AL_OUT_OF_MEMORY"); - default: - break; - } - return TEXT("???"); -} - void AudioBackendOAL::Listener_Reset() { alListenerf(AL_GAIN, Audio::GetVolume()); From aaad2face2123ebee3df934a4d4995f6ca6832f9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 25 Feb 2025 22:19:26 +0100 Subject: [PATCH 08/12] Fix HRTF audio to be disabled due to common audio problems #3225 --- Source/Engine/Audio/AudioSettings.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Audio/AudioSettings.h b/Source/Engine/Audio/AudioSettings.h index edff00390..dda305c33 100644 --- a/Source/Engine/Audio/AudioSettings.h +++ b/Source/Engine/Audio/AudioSettings.h @@ -16,27 +16,27 @@ public: /// /// If checked, audio playback will be disabled in build game. Can be used if game uses custom audio playback engine. /// - API_FIELD(Attributes="EditorOrder(0), DefaultValue(false), EditorDisplay(\"General\")") + API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"General\")") bool DisableAudio = false; /// /// The doppler effect factor. Scale for source and listener velocities. Default is 1. /// - API_FIELD(Attributes="EditorOrder(100), DefaultValue(1.0f), EditorDisplay(\"General\")") + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"General\")") float DopplerFactor = 1.0f; /// /// True if mute all audio playback when game has no use focus. /// - API_FIELD(Attributes="EditorOrder(200), DefaultValue(true), EditorDisplay(\"General\", \"Mute On Focus Loss\")") + API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"General\", \"Mute On Focus Loss\")") bool MuteOnFocusLoss = true; /// /// Enables or disables HRTF audio for in-engine processing of 3D audio (if supported by platform). /// If enabled, the user should be using two-channel/headphones audio output and have all other surround virtualization disabled (Atmos, DTS:X, vendor specific, etc.) /// - API_FIELD(Attributes="EditorOrder(300), DefaultValue(true), EditorDisplay(\"Spatial Audio\")") - bool EnableHRTF = true; + API_FIELD(Attributes="EditorOrder(300), EditorDisplay(\"Spatial Audio\")") + bool EnableHRTF = false; public: /// From 2d956ebb36cd0b2274ba1b31f64e2488c4961fa2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 26 Feb 2025 17:46:22 +0100 Subject: [PATCH 09/12] Fix error when applying prefab changes with missing (deleted) nested prefabs #3244 --- .../CustomEditors/Dedicated/ActorEditor.cs | 3 +- Source/Editor/Managed/ManagedEditor.cpp | 15 ---- Source/Editor/Managed/ManagedEditor.h | 1 - .../Undo/Actions/BreakPrefabLinkAction.cs | 9 ++- Source/Engine/Level/Prefabs/Prefab.Apply.cpp | 29 ++++++- Source/Engine/Level/Prefabs/Prefab.cpp | 18 +++++ Source/Engine/Level/Prefabs/Prefab.h | 9 +++ Source/Engine/Tests/TestPrefabs.cpp | 76 ++++++++++++++++++- 8 files changed, 136 insertions(+), 24 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index d1cdd8780..a93969e36 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -69,8 +69,7 @@ namespace FlaxEditor.CustomEditors.Dedicated Values.SetReferenceValue(prefabInstance); // Display prefab UI (when displaying object inside Prefab Window then display only nested prefabs) - var prefabId = prefab.ID; - Editor.GetPrefabNestedObject(ref prefabId, ref prefabObjectId, out var nestedPrefabId, out var nestedPrefabObjectId); + prefab.GetNestedObject(ref prefabObjectId, out var nestedPrefabId, out var nestedPrefabObjectId); var nestedPrefab = FlaxEngine.Content.Load(nestedPrefabId); var panel = layout.CustomContainer(); panel.CustomControl.Height = 20.0f; diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index c4a052f9e..652878d4d 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -617,21 +617,6 @@ void ManagedEditor::WipeOutLeftoverSceneObjects() ObjectsRemovalService::Flush(); } -void ManagedEditor::GetPrefabNestedObject(const Guid& prefabId, const Guid& prefabObjectId, Guid& outPrefabId, Guid& outPrefabObjectId) -{ - outPrefabId = Guid::Empty; - outPrefabObjectId = Guid::Empty; - const auto prefab = Content::Load(prefabId); - if (!prefab) - return; - const ISerializable::DeserializeStream** prefabObjectDataPtr = prefab->ObjectsDataCache.TryGet(prefabObjectId); - if (!prefabObjectDataPtr) - return; - const ISerializable::DeserializeStream& prefabObjectData = **prefabObjectDataPtr; - JsonTools::GetGuidIfValid(outPrefabId, prefabObjectData, "PrefabID"); - JsonTools::GetGuidIfValid(outPrefabObjectId, prefabObjectData, "PrefabObjectID"); -} - void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly) { ASSERT(!HasManagedInstance()); diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 7bd0cdefa..4722db997 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -259,7 +259,6 @@ public: API_FUNCTION(Internal) static Array GetVisualScriptLocals(); API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local); API_FUNCTION(Internal) static void WipeOutLeftoverSceneObjects(); - API_FUNCTION(Internal) static void GetPrefabNestedObject(API_PARAM(Ref) const Guid& prefabId, API_PARAM(Ref) const Guid& prefabObjectId, API_PARAM(Out) Guid& outPrefabId, API_PARAM(Out) Guid& outPrefabObjectId); private: void OnEditorAssemblyLoaded(MAssembly* assembly); diff --git a/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs b/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs index a6f338633..a4fb8e1f5 100644 --- a/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs +++ b/Source/Editor/Undo/Actions/BreakPrefabLinkAction.cs @@ -33,9 +33,14 @@ namespace FlaxEditor.Actions // Check if this object comes from another nested prefab (to break link only from the top-level prefab) Item nested; nested.ID = ID; - Editor.GetPrefabNestedObject(ref PrefabID, ref PrefabObjectID, out nested.PrefabID, out nested.PrefabObjectID); - if (nested.PrefabID != Guid.Empty && nested.PrefabObjectID != Guid.Empty) + var prefab = FlaxEngine.Content.Load(PrefabID); + if (prefab != null && + prefab.GetNestedObject(ref PrefabObjectID, out nested.PrefabID, out nested.PrefabObjectID) && + nested.PrefabID != Guid.Empty && + nested.PrefabObjectID != Guid.Empty) + { nestedPrefabLinks.Add(nested); + } } } } diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index deb3ed9e9..418ecd477 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -707,7 +707,7 @@ bool Prefab::ApplyAll(Actor* targetActor) for (int32 i = 0; i < nestedPrefabIds.Count(); i++) { const auto nestedPrefab = Content::LoadAsync(nestedPrefabIds[i]); - if (nestedPrefab && nestedPrefab != this && (nestedPrefab->Flags & ObjectFlags::WasMarkedToDelete) == ObjectFlags::None) + if (nestedPrefab && nestedPrefab != this && EnumHasNoneFlags(nestedPrefab->Flags, ObjectFlags::WasMarkedToDelete)) { allPrefabs.Add(nestedPrefab); } @@ -778,6 +778,29 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr for (int32 i = 0; i < targetObjects->Count(); i++) { SceneObject* obj = targetObjects.Value->At(i); + + // Check the whole chain of prefab references to be valid for this object + bool brokenPrefab = false; + Guid nestedPrefabId = obj->GetPrefabID(), nestedPrefabObjectId = obj->GetPrefabObjectID(); + while (!brokenPrefab && nestedPrefabId.IsValid() && nestedPrefabObjectId.IsValid()) + { + auto prefab = Content::Load(nestedPrefabId); + if (prefab) + { + prefab->GetNestedObject(nestedPrefabObjectId, nestedPrefabId, nestedPrefabObjectId); + } + else + { + LOG(Warning, "Missing prefab {0}.", nestedPrefabId); + brokenPrefab = true; + } + } + if (brokenPrefab) + { + LOG(Warning, "Broken prefab reference on object {0}. Breaking linkage to inline object inside prefab.", GetObjectName(obj)); + obj->BreakPrefabLink(); + } + writer.SceneObject(obj); } writer.EndArray(); @@ -809,7 +832,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr SceneObject* obj = targetObjects.Value->At(i); auto data = it->GetObject(); - // Check if object is from that prefab + // Check if object is from this prefab if (obj->GetPrefabID() == prefabId) { if (!obj->GetPrefabObjectID().IsValid()) @@ -883,7 +906,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr { const SceneObject* obj = targetObjects->At(i); - // Check if object is from that prefab + // Check if object is from this prefab if (obj->GetPrefabID() == prefabId) { // Map prefab instance to existing prefab object diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index b85be7287..a50ec3900 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -94,6 +94,24 @@ SceneObject* Prefab::GetDefaultInstance(const Guid& objectId) return result; } +bool Prefab::GetNestedObject(const Guid& objectId, Guid& outPrefabId, Guid& outObjectId) const +{ + if (WaitForLoaded()) + return false; + bool result = false; + Guid result1 = Guid::Empty, result2 = Guid::Empty; + const ISerializable::DeserializeStream** prefabObjectDataPtr = ObjectsDataCache.TryGet(objectId); + if (prefabObjectDataPtr) + { + const ISerializable::DeserializeStream& prefabObjectData = **prefabObjectDataPtr; + result = JsonTools::GetGuidIfValid(result1, prefabObjectData, "PrefabID") && + JsonTools::GetGuidIfValid(result2, prefabObjectData, "PrefabObjectID"); + } + outPrefabId = result1; + outObjectId = result2; + return result; +} + void Prefab::DeleteDefaultInstance() { ScopeLock lock(Locker); diff --git a/Source/Engine/Level/Prefabs/Prefab.h b/Source/Engine/Level/Prefabs/Prefab.h index 5b7de8637..2e8931d32 100644 --- a/Source/Engine/Level/Prefabs/Prefab.h +++ b/Source/Engine/Level/Prefabs/Prefab.h @@ -70,6 +70,15 @@ public: /// The object of the prefab loaded from the prefab. Contains the default values. It's not added to gameplay but deserialized with postLoad and init event fired. API_FUNCTION() SceneObject* GetDefaultInstance(API_PARAM(Ref) const Guid& objectId); + /// + /// Gets the reference to the other nested prefab for a specific prefab object. + /// + /// The ID of the object in this prefab. + /// Result ID of the prefab asset referenced by the given object. + /// Result ID of the prefab object referenced by the given object. + /// True if got valid reference, otherwise false. + API_FUNCTION() bool GetNestedObject(API_PARAM(Ref) const Guid& objectId, API_PARAM(Out) Guid& outPrefabId, API_PARAM(Out) Guid& outObjectId) const; + #if USE_EDITOR /// /// Applies the difference from the prefab object instance, saves the changes and synchronizes them with the active instances of the prefab asset. diff --git a/Source/Engine/Tests/TestPrefabs.cpp b/Source/Engine/Tests/TestPrefabs.cpp index fd8ffa1e2..b90a84436 100644 --- a/Source/Engine/Tests/TestPrefabs.cpp +++ b/Source/Engine/Tests/TestPrefabs.cpp @@ -559,7 +559,7 @@ TEST_CASE("Prefabs") Content::DeleteAsset(prefabNested1); Content::DeleteAsset(prefabBase); } - SECTION("Test Applying Prefab ChangeTo Object References") + SECTION("Test Applying Prefab Change To Object References") { // https://github.com/FlaxEngine/FlaxEngine/issues/3136 @@ -614,4 +614,78 @@ TEST_CASE("Prefabs") instanceB->DeleteObject(); Content::DeleteAsset(prefab); } + SECTION("Test Applying Prefab With Missing Nested Prefab") + { + // https://github.com/FlaxEngine/FlaxEngine/issues/3244 + + // Create Prefab B with just root object + AssetReference prefabB = Content::CreateVirtualAsset(); + REQUIRE(prefabB); + Guid id; + Guid::Parse("25dbe4b0416be0777a6ce59e8788b10f", id); + prefabB->ChangeID(id); + auto prefabBInit = prefabB->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"aac6b9644492fbca1a6ab0a7904a557e\"," + "\"TypeName\": \"FlaxEngine.ExponentialHeightFog\"," + "\"Name\": \"Prefab B.Root\"" + "}" + "]"); + REQUIRE(!prefabBInit); + + // Create Prefab A with nested Prefab B attached to the root + AssetReference prefabA = Content::CreateVirtualAsset(); + REQUIRE(prefabA); + Guid::Parse("4cb744714f746e31855f41815612d14b", id); + prefabA->ChangeID(id); + auto prefabAInit = prefabA->Init(Prefab::TypeName, + "[" + "{" + "\"ID\": \"244274a04cc60d56a2f024bfeef5772d\"," + "\"TypeName\": \"FlaxEngine.SpotLight\"," + "\"Name\": \"Prefab A.Root\"" + "}," + "{" + "\"ID\": \"1e51f1094f430733333f8280e78dfcc3\"," + "\"PrefabID\": \"25dbe4b0416be0777a6ce59e8788b10f\"," + "\"PrefabObjectID\": \"aac6b9644492fbca1a6ab0a7904a557e\"," + "\"ParentID\": \"244274a04cc60d56a2f024bfeef5772d\"" + "}" + "]"); + REQUIRE(!prefabAInit); + + // Spawn test instances of both prefabs + ScriptingObjectReference instanceA = PrefabManager::SpawnPrefab(prefabA); + ScriptingObjectReference instanceB = PrefabManager::SpawnPrefab(prefabB); + + // Delete nested prefab + Content::DeleteAsset(prefabB); + + // Apply instance A and verify it's fine even tho prefab B doesn't exist anymore + bool applyResult = PrefabManager::ApplyAll(instanceA); + REQUIRE(!applyResult); + + // Check state of objects + REQUIRE(instanceA); + REQUIRE(instanceA->Children.Count() == 1); + REQUIRE(instanceA->Children[0] != nullptr); + REQUIRE(instanceA->Children[0]->Is()); + REQUIRE(instanceB); + REQUIRE(instanceB->Is()); + + // Verify if prefab has new data to properly spawn another prefab + ScriptingObjectReference instanceC = PrefabManager::SpawnPrefab(prefabA); + REQUIRE(instanceC); + REQUIRE(instanceC->Children.Count() == 1); + REQUIRE(instanceC->Children[0] != nullptr); + REQUIRE(instanceC->Children[0]->Is()); + + // Cleanup + instanceA->DeleteObject(); + instanceB->DeleteObject(); + instanceC->DeleteObject(); + Content::DeleteAsset(prefabA); + + } } From 809fd2653ad0ef667f286b0dc7dc150fe138a63f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 26 Feb 2025 20:55:57 +0100 Subject: [PATCH 10/12] Fix game viewport size to not include DPI scale (screen-space uses it) #2976 --- Source/Editor/Editor.cs | 4 ++-- Source/Engine/Engine/Screen.h | 4 ++-- Source/Engine/Level/Actors/Camera.cpp | 9 +++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 3a11ed449..7f2351426 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -1550,9 +1550,9 @@ namespace FlaxEditor // Handle case when Game window is not selected in tab view var dockedTo = gameWin.ParentDockPanel; if (dockedTo != null && dockedTo.SelectedTab != gameWin && dockedTo.SelectedTab != null) - result = dockedTo.SelectedTab.Size * root.DpiScale; + result = dockedTo.SelectedTab.Size; else - result = gameWin.Viewport.Size * root.DpiScale; + result = gameWin.Viewport.Size; result = Float2.Round(result); } diff --git a/Source/Engine/Engine/Screen.h b/Source/Engine/Engine/Screen.h index 7322fa368..469d30847 100644 --- a/Source/Engine/Engine/Screen.h +++ b/Source/Engine/Engine/Screen.h @@ -30,7 +30,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen); API_PROPERTY() static void SetIsFullscreen(bool value); /// - /// Gets the window size. + /// Gets the window size (in screen-space, includes DPI scale). /// /// The value API_PROPERTY() static Float2 GetSize(); @@ -50,7 +50,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Screen); API_FUNCTION() static Float2 GameViewportToScreen(const Float2& viewportPos); /// - /// Sets the window size. + /// Sets the window size (in screen-space, includes DPI scale). /// /// /// Resizing may not happen immediately. It will be performed before next frame rendering. diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index f6aa00c78..f664537d2 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -246,11 +246,16 @@ Ray Camera::ConvertMouseToRay(const Float2& mousePosition, const Viewport& viewp Viewport Camera::GetViewport() const { Viewport result = Viewport(Float2::Zero); + float dpiScale = Platform::GetDpiScale(); #if USE_EDITOR // Editor if (Editor::Managed) + { result.Size = Editor::Managed->GetGameWindowSize(); + if (auto* window = Editor::Managed->GetGameWindow()) + dpiScale = window->GetDpiScale(); + } #else // Game auto mainWin = Engine::MainWindow; @@ -258,9 +263,13 @@ Viewport Camera::GetViewport() const { const auto size = mainWin->GetClientSize(); result.Size = size; + dpiScale = mainWin->GetDpiScale(); } #endif + // Remove DPI scale (game viewport coords are unscaled) + result.Size /= dpiScale; + // Fallback to the default value if (result.Size.MinValue() <= ZeroTolerance) result.Size = Float2(1280, 720); From b2c8c4018c0928889e891ea1d66733aa0640f6fb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 26 Feb 2025 22:43:30 +0100 Subject: [PATCH 11/12] Fix scripting bindings codegen for `SoftTypeReference` and `SceneReference` #2761 --- Source/Engine/AI/BehaviorKnowledgeSelector.cs | 2 +- Source/Engine/Core/Types/Variant.cpp | 7 ++ Source/Engine/Core/Types/Variant.h | 2 + Source/Engine/Scripting/SoftTypeReference.cs | 10 +++ Source/Engine/Scripting/SoftTypeReference.h | 14 +++- Source/Engine/Tests/TestScripting.h | 11 +++- .../Bindings/BindingsGenerator.CSharp.cs | 64 ++++++++++--------- .../Bindings/BindingsGenerator.Cpp.cs | 22 ++++++- 8 files changed, 95 insertions(+), 37 deletions(-) diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.cs b/Source/Engine/AI/BehaviorKnowledgeSelector.cs index 075d9decf..9ca6f83e2 100644 --- a/Source/Engine/AI/BehaviorKnowledgeSelector.cs +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.cs @@ -74,7 +74,7 @@ namespace FlaxEngine /// /// The knowledge container to access. /// The value to set. - /// True if set value value, otherwise false. + /// True if set value, otherwise false. public bool Set(BehaviorKnowledge knowledge, object value) { return knowledge != null && knowledge.Set(Path, value); diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 3734a5b95..7868b4719 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -3151,6 +3151,13 @@ Variant Variant::Parse(const StringView& text, const VariantType& type) return result; } +Variant Variant::Typename(const StringAnsiView& value) +{ + Variant result; + result.SetTypename(value); + return result; +} + bool Variant::CanCast(const Variant& v, const VariantType& to) { if (v.Type == to) diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index 5db8a6a83..0a7aff2aa 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -410,6 +410,8 @@ public: return MoveTemp(v); } + static Variant Typename(const StringAnsiView& value); + static bool CanCast(const Variant& v, const VariantType& to); static Variant Cast(const Variant& v, const VariantType& to); static bool NearEqual(const Variant& a, const Variant& b, float epsilon = 1e-6f); diff --git a/Source/Engine/Scripting/SoftTypeReference.cs b/Source/Engine/Scripting/SoftTypeReference.cs index 11067c141..67b5a2513 100644 --- a/Source/Engine/Scripting/SoftTypeReference.cs +++ b/Source/Engine/Scripting/SoftTypeReference.cs @@ -38,6 +38,16 @@ namespace FlaxEngine _typeName = typeName; } + /// + /// Implicit cast operator from type name to string. + /// + /// The soft type reference. + /// The type name. + public static implicit operator string(SoftTypeReference s) + { + return s._typeName; + } + /// /// Gets the soft type reference from full name. /// diff --git a/Source/Engine/Scripting/SoftTypeReference.h b/Source/Engine/Scripting/SoftTypeReference.h index 3da71d7e2..c37ad1c1f 100644 --- a/Source/Engine/Scripting/SoftTypeReference.h +++ b/Source/Engine/Scripting/SoftTypeReference.h @@ -12,7 +12,7 @@ /// The soft reference to the scripting type contained in the scripting assembly. /// template -API_STRUCT(InBuild) struct SoftTypeReference +API_STRUCT(InBuild, MarshalAs=StringAnsi) struct SoftTypeReference { protected: StringAnsi _typeName; @@ -64,7 +64,7 @@ public: return *this; } - FORCE_INLINE SoftTypeReference& operator=(const StringAnsiView& s) + FORCE_INLINE SoftTypeReference& operator=(const StringAnsiView& s) noexcept { _typeName = s; return *this; @@ -95,6 +95,16 @@ public: return _typeName.HasChars(); } + operator StringAnsi() const + { + return _typeName; + } + + String ToString() const + { + return _typeName.ToString(); + } + public: // Gets the type full name (eg. FlaxEngine.Actor). StringAnsiView GetTypeName() const diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index 95cd403e4..8107b94b9 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -7,6 +7,8 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/SerializableScriptingObject.h" +#include "Engine/Scripting/SoftTypeReference.h" +#include "Engine/Content/SceneReference.h" // Test default values init on fields. API_STRUCT(NoDefault) struct TestDefaultValues @@ -124,10 +126,17 @@ API_STRUCT(NoDefault) struct TestStruct : public ISerializable API_FIELD() Float3 Vector = Float3::One; // Ref API_FIELD() ScriptingObject* Object = nullptr; + // Soft Type Ref + API_FIELD() SoftTypeReference SoftTypeRef; + // Scene Ref + API_FIELD() SceneReference SceneRef; friend bool operator==(const TestStruct& lhs, const TestStruct& rhs) { - return lhs.Vector == rhs.Vector && lhs.Object == rhs.Object; + return lhs.Vector == rhs.Vector && + lhs.Object == rhs.Object && + lhs.SoftTypeRef == rhs.SoftTypeRef && + lhs.SceneRef == rhs.SceneRef; } }; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 5d03d0489..b524e1648 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1605,41 +1605,43 @@ namespace Flax.Build.Bindings if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) continue; + var marshalType = fieldInfo.Type; + var apiType = FindApiTypeInfo(buildData, marshalType, structureInfo); + if (apiType != null && apiType.MarshalAs != null) + marshalType = apiType.MarshalAs; + bool internalType = apiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, structureInfo); string type, originalType; - if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) + if (marshalType.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { // Fixed-size array that needs to be inlined into structure instead of passing it as managed array - fieldInfo.Type.IsArray = false; - originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); - fieldInfo.Type.IsArray = true; + marshalType.IsArray = false; + originalType = type = GenerateCSharpNativeToManaged(buildData, marshalType, structureInfo); + marshalType.IsArray = true; } else - originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); - + originalType = type = GenerateCSharpNativeToManaged(buildData, marshalType, structureInfo); + if (apiType != null && apiType.MarshalAs != null) + Log.Error("marshal as into type: " + type); structContents.Append(structIndent).Append("public "); - - var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo); - bool internalType = apiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, structureInfo); string internalTypeMarshaller = ""; - - if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) + if (marshalType.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { #if USE_NETCORE if (GenerateCSharpUseFixedBuffer(originalType)) { // Use fixed statement with primitive types of buffers - structContents.Append($"fixed {originalType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine(); + structContents.Append($"fixed {originalType} {fieldInfo.Name}0[{marshalType.ArraySize}];").AppendLine(); // Copy fixed-size array - toManagedContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(managed.{fieldInfo.Name}0), new IntPtr(unmanaged.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.ArraySize}ul);"); - toNativeContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(unmanaged.{fieldInfo.Name}0), new IntPtr(managed.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.ArraySize}ul);"); + toManagedContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(managed.{fieldInfo.Name}0), new IntPtr(unmanaged.{fieldInfo.Name}0), sizeof({originalType}) * {marshalType.ArraySize}ul);"); + toNativeContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(unmanaged.{fieldInfo.Name}0), new IntPtr(managed.{fieldInfo.Name}0), sizeof({originalType}) * {marshalType.ArraySize}ul);"); } else #endif { // Padding in structs for fixed-size array structContents.Append(type).Append(' ').Append(fieldInfo.Name).Append("0;").AppendLine(); - for (int i = 1; i < fieldInfo.Type.ArraySize; i++) + for (int i = 1; i < marshalType.ArraySize; i++) { GenerateCSharpAttributes(buildData, structContents, structIndent, structureInfo, fieldInfo, fieldInfo.IsStatic); structContents.Append(structIndent).Append("public "); @@ -1649,7 +1651,7 @@ namespace Flax.Build.Bindings // Copy fixed-size array item one-by-one if (fieldInfo.Access == AccessLevel.Public || fieldInfo.Access == AccessLevel.Internal) { - for (int i = 0; i < fieldInfo.Type.ArraySize; i++) + for (int i = 0; i < marshalType.ArraySize; i++) { toManagedContent.AppendLine($"managed.{fieldInfo.Name}{i} = unmanaged.{fieldInfo.Name}{i};"); toNativeContent.AppendLine($"unmanaged.{fieldInfo.Name}{i} = managed.{fieldInfo.Name}{i};"); @@ -1664,23 +1666,23 @@ namespace Flax.Build.Bindings } else { - if (fieldInfo.Type.IsObjectRef || fieldInfo.Type.Type == "Dictionary") + if (marshalType.IsObjectRef || marshalType.Type == "Dictionary") type = "IntPtr"; - else if (fieldInfo.Type.IsPtr && !originalType.EndsWith("*")) + else if (marshalType.IsPtr && !originalType.EndsWith("*")) type = "IntPtr"; - else if (fieldInfo.Type.Type == "Array" || fieldInfo.Type.Type == "Span" || fieldInfo.Type.Type == "DataContainer" || fieldInfo.Type.Type == "BytesContainer") + else if (marshalType.Type == "Array" || marshalType.Type == "Span" || marshalType.Type == "DataContainer" || marshalType.Type == "BytesContainer") { type = "IntPtr"; - apiType = FindApiTypeInfo(buildData, fieldInfo.Type.GenericArgs[0], structureInfo); + apiType = FindApiTypeInfo(buildData, marshalType.GenericArgs[0], structureInfo); internalType = apiType is StructureInfo elementStructureInfo && UseCustomMarshalling(buildData, elementStructureInfo, structureInfo); } - else if (fieldInfo.Type.Type == "Version") + else if (marshalType.Type == "Version") type = "IntPtr"; else if (type == "string") type = "IntPtr"; else if (type == "bool") type = "byte"; - else if (fieldInfo.Type.Type == "Variant") + else if (marshalType.Type == "Variant") type = "IntPtr"; else if (internalType) { @@ -1695,9 +1697,9 @@ namespace Flax.Build.Bindings // Generate struct constructor/getter and deconstructor/setter function toManagedContent.Append("managed.").Append(fieldInfo.Name).Append(" = "); toNativeContent.Append("unmanaged.").Append(fieldInfo.Name).Append(" = "); - if (fieldInfo.Type.IsObjectRef) + if (marshalType.IsObjectRef) { - var managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type.GenericArgs[0], structureInfo); + var managedType = GenerateCSharpNativeToManaged(buildData, marshalType.GenericArgs[0], structureInfo); toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); @@ -1705,7 +1707,7 @@ namespace Flax.Build.Bindings // Permanent ScriptingObject handle is passed from native side, do not release it //freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } - else if (fieldInfo.Type.Type == "ScriptingObject") + else if (marshalType.Type == "ScriptingObject") { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); @@ -1714,7 +1716,7 @@ namespace Flax.Build.Bindings // Permanent ScriptingObject handle is passed from native side, do not release it //freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } - else if (fieldInfo.Type.IsPtr && originalType != "IntPtr" && !originalType.EndsWith("*")) + else if (marshalType.IsPtr && originalType != "IntPtr" && !originalType.EndsWith("*")) { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); @@ -1723,14 +1725,14 @@ namespace Flax.Build.Bindings // Permanent ScriptingObject handle is passed from native side, do not release it //freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } - else if (fieldInfo.Type.Type == "Dictionary") + else if (marshalType.Type == "Dictionary") { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } - else if (fieldInfo.Type.Type == "Array" || fieldInfo.Type.Type == "Span" || fieldInfo.Type.Type == "DataContainer" || fieldInfo.Type.Type == "BytesContainer") + else if (marshalType.Type == "Array" || marshalType.Type == "Span" || marshalType.Type == "DataContainer" || marshalType.Type == "BytesContainer") { string originalElementType = originalType.Substring(0, originalType.Length - 2); if (internalType) @@ -1744,7 +1746,7 @@ namespace Flax.Build.Bindings freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.NativeToManaged.Free(value); }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } - else if (fieldInfo.Type.GenericArgs[0].IsObjectRef) + else if (marshalType.GenericArgs[0].IsObjectRef) { // Array elements passed as GCHandles toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)) : null;"); @@ -1763,7 +1765,7 @@ namespace Flax.Build.Bindings freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } } - else if (fieldInfo.Type.Type == "Version") + else if (marshalType.Type == "Version") { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);"); @@ -1782,7 +1784,7 @@ namespace Flax.Build.Bindings toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != 0;"); toNativeContent.AppendLine($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0;"); } - else if (fieldInfo.Type.Type == "Variant") + else if (marshalType.Type == "Variant") { // Variant passed as boxed object handle toManagedContent.AppendLine($"ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged.{fieldInfo.Name});"); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index a2c5f6934..49c0fa97f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -169,6 +169,8 @@ namespace Flax.Build.Bindings return $"Variant(StringAnsiView({value}))"; if (typeInfo.IsObjectRef) return $"Variant({value}.Get())"; + if (typeInfo.Type == "SoftTypeReference") + return $"Variant::Typename(StringAnsiView({value}))"; if (typeInfo.IsArray) { var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo); @@ -227,6 +229,8 @@ namespace Flax.Build.Bindings return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.IsObjectRef) return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; + if (typeInfo.Type == "SoftTypeReference") + return $"(StringAnsiView){value}"; if (typeInfo.IsArray) throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) @@ -258,10 +262,24 @@ namespace Flax.Build.Bindings if (apiType.IsScriptingObject) return $"ScriptingObject::Cast<{typeInfo.Type}>((ScriptingObject*){value})"; if (apiType.IsStruct && !CppInBuildVariantStructures.Contains(apiType.Name)) + { + var name = apiType.FullNameNative; + if (typeInfo.GenericArgs != null) + { + name += '<'; + for (var i = 0; i < typeInfo.GenericArgs.Count; i++) + { + if (i != 0) + name += ", "; + name += typeInfo.GenericArgs[i]; + } + name += '>'; + } if (typeInfo.IsPtr) - return $"({apiType.FullNameNative}*){value}.AsBlob.Data"; + return $"({name}*){value}.AsBlob.Data"; else - return $"*({apiType.FullNameNative}*){value}.AsBlob.Data"; + return $"*({name}*){value}.AsBlob.Data"; + } } if (typeInfo.IsPtr) From bb35d9f81140bdb4c2890e5914233acb451ccc49 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 27 Feb 2025 15:34:29 +0100 Subject: [PATCH 12/12] Add improved AndroidNdk detection to handle versions folder in env var location and fallback to automatic #2893 --- .../Platforms/Android/AndroidNdk.cs | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs index 7cc8afce3..c85ac6cbe 100644 --- a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs +++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs @@ -34,23 +34,50 @@ namespace Flax.Build.Platforms // Find Android NDK folder path var sdkPath = Environment.GetEnvironmentVariable("ANDROID_NDK"); - if (string.IsNullOrEmpty(sdkPath)) + FindNDKVersion(sdkPath); + if (!IsValid) { - // Look for ndk installed side-by-side with an sdk - if (AndroidSdk.Instance.IsValid && Directory.Exists(Path.Combine(AndroidSdk.Instance.RootPath, "ndk"))) + if (!string.IsNullOrEmpty(sdkPath)) { - var subdirs = Directory.GetDirectories(Path.Combine(AndroidSdk.Instance.RootPath, "ndk")); - if (subdirs.Length != 0) - { - Utilities.SortVersionDirectories(subdirs); - sdkPath = subdirs.Last(); - } + Log.Warning(string.Format("Specified Android NDK folder in ANDROID_NDK env variable doesn't contain valid NDK ({0})", sdkPath)); + } + + // Look for ndk installed side-by-side with a sdk + if (AndroidSdk.Instance.IsValid) + { + sdkPath = Path.Combine(AndroidSdk.Instance.RootPath, "ndk"); + FindNDKVersion(sdkPath); } } - else if (!Directory.Exists(sdkPath)) + } + + private void FindNDKVersion(string folder) + { + // Skip if folder is invalid or missing + if (string.IsNullOrEmpty(folder) || !Directory.Exists(folder)) + return; + + // Check that explicit folder + FindNDK(folder); + if (IsValid) + return; + + // Check folders with versions + var subDirs = Directory.GetDirectories(folder); + if (subDirs.Length != 0) { - Log.Warning(string.Format("Specified Android NDK folder in ANDROID_NDK env variable doesn't exist ({0})", sdkPath)); + Utilities.SortVersionDirectories(subDirs); + FindNDK(subDirs.Last()); } + + if (!IsValid) + { + Log.Warning(string.Format("Failed to detect Android NDK version ({0})", folder)); + } + } + + private void FindNDK(string sdkPath) + { if (!string.IsNullOrEmpty(sdkPath) && Directory.Exists(sdkPath)) { var sourceProperties = Path.Combine(sdkPath, "source.properties"); @@ -72,15 +99,10 @@ namespace Flax.Build.Platforms Version = v; IsValid = true; } - else - { - Log.Warning(string.Format("Failed to detect Android NDK version ({0})", sdkPath)); - } } if (IsValid) { RootPath = sdkPath; - IsValid = true; Log.Info(string.Format("Found Android NDK {1} at {0}", RootPath, Version)); } }