diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index c6a841d06..1d5c1adbc 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -6,7 +6,7 @@ jobs: # Editor editor-windows: name: Editor (Windows, Development x64) - runs-on: "windows-latest" + runs-on: "windows-2019" steps: - name: Checkout repo uses: actions/checkout@v2 @@ -23,7 +23,7 @@ jobs: # Game game-windows: name: Game (Windows, Release x64) - runs-on: "windows-latest" + runs-on: "windows-2019" steps: - name: Checkout repo uses: actions/checkout@v2 diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 4b9530788..f5c8726b6 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -9,7 +9,7 @@ jobs: # Windows package-windows-editor: name: Editor (Windows) - runs-on: "windows-latest" + runs-on: "windows-2019" steps: - name: Checkout repo uses: actions/checkout@v2 @@ -34,7 +34,7 @@ jobs: path: Output/EditorDebugSymbols.zip package-windows-game: name: Game (Windows) - runs-on: "windows-latest" + runs-on: "windows-2019" steps: - name: Checkout repo uses: actions/checkout@v2 diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index 58e438c46..23a15157a 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -163,6 +163,12 @@ namespace FlaxEditor.Content.Import [EditorOrder(90), DefaultValue(ModelLightmapUVsSource.Disable), EditorDisplay("Geometry", "Lightmap UVs Source"), Tooltip("Model lightmap UVs source")] public ModelLightmapUVsSource LightmapUVsSource { get; set; } = ModelLightmapUVsSource.Disable; + /// + /// If specified, all meshes which name starts with this prefix will be imported as a separate collision data (excluded used for rendering). + /// + [EditorOrder(100), DefaultValue(""), EditorDisplay("Geometry")] + public string CollisionMeshesPrefix { get; set; } + /// /// Custom uniform import scale. /// @@ -284,7 +290,7 @@ namespace FlaxEditor.Content.Import /// [EditorOrder(420), DefaultValue(true), EditorDisplay("Materials", "Restore Materials On Reimport"), Tooltip("If checked, the importer will try to restore the assigned materials to the model slots.")] public bool RestoreMaterialsOnReimport { get; set; } = true; - + /// /// If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1. /// @@ -314,6 +320,7 @@ namespace FlaxEditor.Content.Import public byte ImportVertexColors; public byte ImportBlendShapes; public ModelLightmapUVsSource LightmapUVsSource; + public string CollisionMeshesPrefix; // Transform public float Scale; @@ -364,6 +371,7 @@ namespace FlaxEditor.Content.Import ImportVertexColors = (byte)(ImportVertexColors ? 1 : 0), ImportBlendShapes = (byte)(ImportBlendShapes ? 1 : 0), LightmapUVsSource = LightmapUVsSource, + CollisionMeshesPrefix = CollisionMeshesPrefix, Scale = Scale, Rotation = Rotation, Translation = Translation, @@ -403,6 +411,7 @@ namespace FlaxEditor.Content.Import ImportVertexColors = options.ImportVertexColors != 0; ImportBlendShapes = options.ImportBlendShapes != 0; LightmapUVsSource = options.LightmapUVsSource; + CollisionMeshesPrefix = options.CollisionMeshesPrefix; Scale = options.Scale; Rotation = options.Rotation; Translation = options.Translation; diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs index da82f73c5..fbcfaad40 100644 --- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs +++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs @@ -86,6 +86,7 @@ namespace FlaxEditor.Content { foreach (var child in modelItem.ParentFolder.Children) { + // Check if there is collision that was made with this model if (child is BinaryAssetItem b && b.IsOfType()) { var collisionData = FlaxEngine.Content.Load(b.ID); @@ -97,6 +98,25 @@ namespace FlaxEditor.Content return; } } + + // Check if there is a auto-imported collision + if (child is ContentFolder childFolder && childFolder.ShortName == modelItem.ShortName) + { + foreach (var childFolderChild in childFolder.Children) + { + if (childFolderChild is BinaryAssetItem c && c.IsOfType()) + { + var collisionData = FlaxEngine.Content.Load(c.ID); + if (collisionData && (collisionData.Options.Model == model.ID || collisionData.Options.Model == Guid.Empty)) + { + Editor.Instance.Windows.ContentWin.Select(c); + if (created != null) + FlaxEngine.Scripting.InvokeOnUpdate(() => created(collisionData)); + return; + } + } + } + } } } diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index 89387fe5f..00303d310 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -229,7 +229,13 @@ namespace FlaxEditor.CustomEditors for (int i = 0; i < Count; i++) { if (!Equals(this[i], _defaultValue)) + { + // Special case for String (null string is kind of equal to empty string from the user perspective) + if (this[i] == null && _defaultValue is string defaultValueStr && defaultValueStr.Length == 0) + continue; + return true; + } } } return false; diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index db7b4f5d5..598aae016 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -227,8 +227,8 @@ namespace FlaxEditor.GUI.Tabs /// public Tab SelectedTab { - get => _selectedIndex == -1 ? null : Children[_selectedIndex + 1] as Tab; - set => SelectedTabIndex = Children.IndexOf(value) - 1; + get => _selectedIndex == -1 && Children.Count > _selectedIndex + 1 ? null : Children[_selectedIndex + 1] as Tab; + set => SelectedTabIndex = value != null ? Children.IndexOf(value) - 1 : -1; } /// diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index da04854ec..248aa757f 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -166,6 +166,7 @@ struct InternalModelOptions byte ImportVertexColors; byte ImportBlendShapes; ModelLightmapUVsSource LightmapUVsSource; + MonoString* CollisionMeshesPrefix; // Transform float Scale; @@ -213,6 +214,7 @@ struct InternalModelOptions to->ImportVertexColors = from->ImportVertexColors; to->ImportBlendShapes = from->ImportBlendShapes; to->LightmapUVsSource = from->LightmapUVsSource; + to->CollisionMeshesPrefix = MUtils::ToString(from->CollisionMeshesPrefix); to->Scale = from->Scale; to->Rotation = from->Rotation; to->Translation = from->Translation; @@ -251,6 +253,7 @@ struct InternalModelOptions to->ImportVertexColors = from->ImportVertexColors; to->ImportBlendShapes = from->ImportBlendShapes; to->LightmapUVsSource = from->LightmapUVsSource; + to->CollisionMeshesPrefix = MUtils::ToString(from->CollisionMeshesPrefix); to->Scale = from->Scale; to->Rotation = from->Rotation; to->Translation = from->Translation; diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 0c22759aa..410a369b4 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -93,6 +93,11 @@ namespace FlaxEditor.Options public void Load() { Editor.Log("Loading editor options"); + if (!File.Exists(_optionsFilePath)) + { + Editor.LogWarning("Missing editor settings"); + return; + } try { @@ -100,12 +105,12 @@ namespace FlaxEditor.Options var asset = FlaxEngine.Content.LoadAsync(_optionsFilePath); if (asset == null) { - Editor.LogWarning("Missing or invalid editor settings"); + Editor.LogWarning("Invalid editor settings"); return; } if (asset.WaitForLoaded()) { - Editor.LogWarning("Failed to load editor settings"); + Editor.LogError("Failed to load editor settings"); return; } diff --git a/Source/Editor/Scripting/CodeEditor.cpp b/Source/Editor/Scripting/CodeEditor.cpp index 198d28396..1cfc4e737 100644 --- a/Source/Editor/Scripting/CodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditor.cpp @@ -180,7 +180,22 @@ void CodeEditingManager::OpenSolution(CodeEditorTypes editorType) const auto editor = GetCodeEditor(editorType); if (editor) { - OpenSolution(editor); + // Ensure that no async task is running + if (IsAsyncOpenRunning()) + { + // TODO: enqueue action and handle many actions in the queue + LOG(Warning, "Cannot use code editor during async open action."); + return; + } + + if (editor->UseAsyncForOpen()) + { + AsyncOpenTask::OpenSolution(editor); + } + else + { + editor->OpenSolution(); + } } else { @@ -201,26 +216,6 @@ void CodeEditingManager::OnFileAdded(CodeEditorTypes editorType, const String& p } } -void CodeEditingManager::OpenSolution(CodeEditor* editor) -{ - // Ensure that no async task is running - if (IsAsyncOpenRunning()) - { - // TODO: enqueue action and handle many actions in the queue - LOG(Warning, "Cannot use code editor during async open action."); - return; - } - - if (editor->UseAsyncForOpen()) - { - AsyncOpenTask::OpenSolution(editor); - } - else - { - editor->OpenSolution(); - } -} - void OnAsyncBegin(Thread* thread) { ASSERT(AsyncOpenThread == nullptr); diff --git a/Source/Editor/Scripting/CodeEditor.h b/Source/Editor/Scripting/CodeEditor.h index 638f1d997..a837f0d6e 100644 --- a/Source/Editor/Scripting/CodeEditor.h +++ b/Source/Editor/Scripting/CodeEditor.h @@ -193,12 +193,6 @@ public: /// The path. API_FUNCTION() static void OnFileAdded(CodeEditorTypes editorType, const String& path); - /// - /// Opens the solution project. Handles async opening. - /// - /// The code editor. - static void OpenSolution(CodeEditor* editor); - /// /// The asynchronous open begins. /// diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index 8005524e7..25e22eaca 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -156,7 +156,7 @@ bool ScriptsBuilder::IsSourceWorkspaceDirty() return _wasProjectStructureChanged; } -bool ScriptsBuilder::IsSourceDirty(const TimeSpan& timeout) +bool ScriptsBuilder::IsSourceDirtyFor(const TimeSpan& timeout) { ScopeLock scopeLock(_locker); return _lastSourceCodeEdited > (_lastCompileAction + timeout); @@ -626,7 +626,7 @@ void ScriptsBuilderService::Update() // Check if compile code (if has been edited) const TimeSpan timeToCallCompileIfDirty = TimeSpan::FromMilliseconds(50); auto mainWindow = Engine::MainWindow; - if (ScriptsBuilder::IsSourceDirty(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused()) + if (ScriptsBuilder::IsSourceDirtyFor(timeToCallCompileIfDirty) && mainWindow && mainWindow->IsFocused()) { // Check if auto reload is enabled if (Editor::Managed->CanAutoReloadScripts()) diff --git a/Source/Editor/Scripting/ScriptsBuilder.h b/Source/Editor/Scripting/ScriptsBuilder.h index df6ff604a..52fdd8457 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.h +++ b/Source/Editor/Scripting/ScriptsBuilder.h @@ -63,7 +63,7 @@ public: /// /// Time to use for checking. /// True if source code is dirty, otherwise false. - static bool IsSourceDirty(const TimeSpan& timeout); + static bool IsSourceDirtyFor(const TimeSpan& timeout); /// /// Returns true if scripts are being now compiled/reloaded. diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index d85a06296..9ec08aacc 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -829,7 +829,13 @@ namespace FlaxEditor.Utilities var attr = field.GetAttribute(); if (attr != null) { - field.SetValue(obj, attr.Value); + // Prevent value type conflicts + var value = attr.Value; + var fieldType = field.ValueType.Type; + if (value != null && value.GetType() != fieldType) + value = Convert.ChangeType(value, fieldType); + + field.SetValue(obj, value); } else if (isStructure) { @@ -845,7 +851,13 @@ namespace FlaxEditor.Utilities var attr = property.GetAttribute(); if (attr != null) { - property.SetValue(obj, attr.Value); + // Prevent value type conflicts + var value = attr.Value; + var propertyType = property.ValueType.Type; + if (value != null && value.GetType() != propertyType) + value = Convert.ChangeType(value, propertyType); + + property.SetValue(obj, value); } } } diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 407a7d016..8d520a62d 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -2,6 +2,7 @@ #include "Asset.h" #include "Content.h" +#include "SoftAssetReference.h" #include "Cache/AssetsCache.h" #include "Loading/ContentLoadingManager.h" #include "Loading/Tasks/LoadAssetTask.h" @@ -102,6 +103,65 @@ void WeakAssetReferenceBase::OnUnloaded(Asset* asset) _asset = nullptr; } +String SoftAssetReferenceBase::ToString() const +{ + return _asset ? _asset->ToString() : (_id.IsValid() ? _id.ToString() : TEXT("")); +} + +void SoftAssetReferenceBase::OnSet(Asset* asset) +{ + if (_asset == asset) + return; + if (_asset) + { + _asset->OnUnloaded.Unbind(this); + _asset->RemoveReference(); + } + _asset = asset; + _id = asset ? asset->GetID() : Guid::Empty; + if (asset) + { + asset->AddReference(); + asset->OnUnloaded.Bind(this); + } + Changed(); +} + +void SoftAssetReferenceBase::OnSet(const Guid& id) +{ + if (_id == id) + return; + if (_asset) + { + _asset->OnUnloaded.Unbind(this); + _asset->RemoveReference(); + } + _asset = nullptr; + _id = id; + Changed(); +} + +void SoftAssetReferenceBase::OnResolve(const ScriptingTypeHandle& type) +{ + ASSERT(!_asset); + _asset = ::LoadAsset(_id, type); + if (_asset) + { + _asset->OnUnloaded.Bind(this); + _asset->AddReference(); + } +} + +void SoftAssetReferenceBase::OnUnloaded(Asset* asset) +{ + ASSERT(_asset == asset); + _asset->RemoveReference(); + _asset->OnUnloaded.Unbind(this); + _asset = nullptr; + _id = Guid::Empty; + Changed(); +} + Asset::Asset(const SpawnParams& params, const AssetInfo* info) : ManagedScriptingObject(params) , _refCount(0) diff --git a/Source/Engine/Content/SoftAssetReference.h b/Source/Engine/Content/SoftAssetReference.h new file mode 100644 index 000000000..eb182220d --- /dev/null +++ b/Source/Engine/Content/SoftAssetReference.h @@ -0,0 +1,234 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Content/Asset.h" + +/// +/// The asset soft reference. Asset gets referenced (loaded) on actual use (ID reference is resolving it). +/// +class FLAXENGINE_API SoftAssetReferenceBase +{ +protected: + Asset* _asset = nullptr; + Guid _id = Guid::Empty; + +public: + /// + /// Action fired when field gets changed (link a new asset or change to the another value). + /// + Delegate<> Changed; + +public: + NON_COPYABLE(SoftAssetReferenceBase); + + /// + /// Initializes a new instance of the class. + /// + SoftAssetReferenceBase() = default; + + /// + /// Finalizes an instance of the class. + /// + ~SoftAssetReferenceBase() + { + } + +public: + /// + /// Gets the asset ID or Guid::Empty if not set. + /// + FORCE_INLINE Guid GetID() const + { + return _id; + } + + /// + /// Gets the asset property value as string. + /// + String ToString() const; + +protected: + void OnSet(Asset* asset); + void OnSet(const Guid& id); + void OnResolve(const ScriptingTypeHandle& type); + void OnUnloaded(Asset* asset); +}; + +/// +/// The asset soft reference. Asset gets referenced (loaded) on actual use (ID reference is resolving it). +/// +template +API_CLASS(InBuild) class SoftAssetReference : public SoftAssetReferenceBase +{ +public: + typedef T AssetType; + typedef SoftAssetReference Type; + +public: + /// + /// Initializes a new instance of the class. + /// + SoftAssetReference() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The asset to set. + SoftAssetReference(T* asset) + { + OnSet(asset); + } + + /// + /// Initializes a new instance of the class. + /// + /// The other. + SoftAssetReference(const SoftAssetReference& other) + { + OnSet(other.Get()); + } + + SoftAssetReference(SoftAssetReference&& other) + { + OnSet(other.Get()); + other.OnSet(nullptr); + } + + /// + /// Finalizes an instance of the class. + /// + ~SoftAssetReference() + { + } + +public: + FORCE_INLINE bool operator==(T* other) + { + return Get() == other; + } + FORCE_INLINE bool operator==(const SoftAssetReference& other) + { + return GetID() == other.GetID(); + } + FORCE_INLINE bool operator!=(T* other) + { + return Get() != other; + } + FORCE_INLINE bool operator!=(const SoftAssetReference& other) + { + return GetID() != other.GetID(); + } + SoftAssetReference& operator=(const SoftAssetReference& other) + { + if (this != &other) + OnSet(other.GetID()); + return *this; + } + SoftAssetReference& operator=(SoftAssetReference&& other) + { + if (this != &other) + { + OnSet(other.GetID()); + other.OnSet(nullptr); + } + return *this; + } + FORCE_INLINE SoftAssetReference& operator=(const T& other) + { + OnSet(&other); + return *this; + } + FORCE_INLINE SoftAssetReference& operator=(T* other) + { + OnSet(other); + return *this; + } + FORCE_INLINE SoftAssetReference& operator=(const Guid& id) + { + OnSet(id); + return *this; + } + FORCE_INLINE operator T*() const + { + return (T*)Get(); + } + FORCE_INLINE operator bool() const + { + return Get() != nullptr; + } + FORCE_INLINE T* operator->() const + { + return (T*)Get(); + } + template + FORCE_INLINE U* As() const + { + return static_cast(Get()); + } + +public: + + /// + /// Gets the asset (or null if unassigned). + /// + T* Get() const + { + if (!_asset) + const_cast(this)->OnResolve(T::TypeInitializer); + return (T*)_asset; + } + + /// + /// Gets managed instance object (or null if no asset linked). + /// + MObject* GetManagedInstance() const + { + auto asset = Get(); + return asset ? asset->GetOrCreateManagedInstance() : nullptr; + } + + /// + /// Determines whether asset is assigned and managed instance of the asset is alive. + /// + bool HasManagedInstance() const + { + auto asset = Get(); + return asset && asset->HasManagedInstance(); + } + + /// + /// Gets the managed instance object or creates it if missing or null if not assigned. + /// + MObject* GetOrCreateManagedInstance() const + { + auto asset = Get(); + return asset ? asset->GetOrCreateManagedInstance() : nullptr; + } + + /// + /// Sets the asset. + /// + /// The object ID. Uses Scripting to find the registered asset of the given ID. + FORCE_INLINE void Set(const Guid& id) + { + OnSet(id); + } + + /// + /// Sets the asset. + /// + /// The asset. + FORCE_INLINE void Set(T* asset) + { + OnSet(asset); + } +}; + +template +uint32 GetHash(const SoftAssetReference& key) +{ + return GetHash(key.GetID()); +} diff --git a/Source/Engine/ContentExporters/ExportModel.cpp b/Source/Engine/ContentExporters/ExportModel.cpp index dab2862c0..079ce8245 100644 --- a/Source/Engine/ContentExporters/ExportModel.cpp +++ b/Source/Engine/ContentExporters/ExportModel.cpp @@ -75,8 +75,8 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) for (uint32 i = 0; i < vertices; i++) { - auto v = vb1[i].TexCoord; - output->WriteText(StringAnsi::Format("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y))); + auto v = vb1[i].TexCoord.ToVector2(); + output->WriteText(StringAnsi::Format("vt {0} {1}\n", v.X, v.Y)); } output->WriteChar('\n'); @@ -180,8 +180,8 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context for (uint32 i = 0; i < vertices; i++) { - auto v = vb0[i].TexCoord; - output->WriteText(StringAnsi::Format("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y))); + auto v = vb0[i].TexCoord.ToVector2(); + output->WriteText(StringAnsi::Format("vt {0} {1}\n", v.X, v.Y)); } output->WriteChar('\n'); diff --git a/Source/Engine/ContentImporters/CreateCollisionData.h b/Source/Engine/ContentImporters/CreateCollisionData.h index 3126cfd56..547a10485 100644 --- a/Source/Engine/ContentImporters/CreateCollisionData.h +++ b/Source/Engine/ContentImporters/CreateCollisionData.h @@ -6,7 +6,9 @@ #if COMPILE_WITH_ASSETS_IMPORTER +#if COMPILE_WITH_PHYSICS_COOKING #include "Engine/Physics/CollisionCooking.h" +#endif /// /// Creating collision data asset utility @@ -23,7 +25,6 @@ public: static CreateAssetResult Create(CreateAssetContext& context); #if COMPILE_WITH_PHYSICS_COOKING - /// /// Cooks the mesh collision data and saves it to the asset using format. /// @@ -31,7 +32,6 @@ public: /// The input argument data. /// True if failed, otherwise false. See log file to track errors better. static bool CookMeshCollision(const String& outputPath, CollisionCooking::Argument& arg); - #endif }; diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h index 99ca7702d..b6c1ce7fb 100644 --- a/Source/Engine/Core/Math/Vector3.h +++ b/Source/Engine/Core/Math/Vector3.h @@ -639,7 +639,7 @@ public: { Vector3 output = input; const float length = input.Length(); - if (!Math::IsZero(length)) + if (Math::Abs(length) >= ZeroTolerance) { const float inv = 1.0f / length; output.X *= inv; diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 91e6b14f1..51827397a 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -11,6 +11,7 @@ #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Assets/Material.h" #include "Engine/Content/Content.h" +#include "Engine/Content/SoftAssetReference.h" #include "Engine/Platform/Windows/WindowsWindow.h" #include "Engine/Render2D/Render2D.h" #include "Engine/Engine/CommandLine.h" @@ -19,7 +20,6 @@ #include "Engine/Profiler/Profiler.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Core/Utilities.h" -#include "Engine/Scripting/SoftObjectReference.h" GPUPipelineState* GPUPipelineState::Spawn(const SpawnParams& params) { @@ -253,7 +253,7 @@ struct GPUDevice::PrivateData GPUPipelineState* PS_Clear = nullptr; GPUBuffer* FullscreenTriangleVB = nullptr; AssetReference DefaultMaterial; - SoftObjectReference DefaultDeformableMaterial; + SoftAssetReference DefaultDeformableMaterial; AssetReference DefaultNormalMap; AssetReference DefaultWhiteTexture; AssetReference DefaultBlackTexture; diff --git a/Source/Engine/Graphics/Models/BlendShape.cpp b/Source/Engine/Graphics/Models/BlendShape.cpp index fb1c27b48..5f1f75c1a 100644 --- a/Source/Engine/Graphics/Models/BlendShape.cpp +++ b/Source/Engine/Graphics/Models/BlendShape.cpp @@ -9,7 +9,7 @@ BlendShapesInstance::MeshInstance::MeshInstance() : IsUsed(false) , IsDirty(false) , DirtyMinVertexIndex(0) - , DirtyMaxVertexIndex(MAX_uint32) + , DirtyMaxVertexIndex(MAX_uint32 - 1) , VertexBuffer(0, sizeof(VB0SkinnedElementType), TEXT("Skinned Mesh Blend Shape")) { } @@ -112,7 +112,7 @@ void BlendShapesInstance::Update(SkinnedModel* skinnedModel) // Initialize the dynamic vertex buffer data (use the dirty range from the previous update to be cleared with initial data) instance.VertexBuffer.Data.Resize(vertexBuffer.Length()); const uint32 dirtyVertexDataStart = instance.DirtyMinVertexIndex * sizeof(VB0SkinnedElementType); - const uint32 dirtyVertexDataLength = Math::Min(instance.DirtyMaxVertexIndex - instance.DirtyMinVertexIndex, vertexCount) * sizeof(VB0SkinnedElementType); + const uint32 dirtyVertexDataLength = Math::Min(instance.DirtyMaxVertexIndex - instance.DirtyMinVertexIndex + 1, vertexCount) * sizeof(VB0SkinnedElementType); Platform::MemoryCopy(instance.VertexBuffer.Data.Get() + dirtyVertexDataStart, vertexBuffer.Get() + dirtyVertexDataStart, dirtyVertexDataLength); // Blend all blend shapes diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index 4b39cef65..d1e867c6c 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -589,6 +589,17 @@ void MeshData::Merge(MeshData& other) } } +bool MaterialSlotEntry::UsesProperties() const +{ + return Diffuse.Color != Color::White || + Diffuse.TextureIndex != -1 || + Emissive.Color != Color::Transparent || + Emissive.TextureIndex != -1 || + !Math::IsOne(Opacity.Value) || + Opacity.TextureIndex != -1 || + Normals.TextureIndex != -1; +} + void ModelData::CalculateLODsScreenSizes() { const float autoComputeLodPowerBase = 0.5f; diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 2b99d0c83..4d4733fb7 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -373,16 +373,7 @@ struct FLAXENGINE_API MaterialSlotEntry bool TwoSided = false; - bool UsesProperties() const - { - return Diffuse.Color != Color::White || - Diffuse.TextureIndex != -1 || - Emissive.Color != Color::Transparent || - Emissive.TextureIndex != -1 || - !Math::IsOne(Opacity.Value) || - Opacity.TextureIndex != -1 || - Normals.TextureIndex != -1; - } + bool UsesProperties() const; }; /// diff --git a/Source/Engine/Graphics/PixelFormatExtensions.cpp b/Source/Engine/Graphics/PixelFormatExtensions.cpp index e79699830..c22c9aba6 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.cpp +++ b/Source/Engine/Graphics/PixelFormatExtensions.cpp @@ -1,7 +1,6 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "PixelFormatExtensions.h" -#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Math/Math.h" // ReSharper disable CppClangTidyClangDiagnosticSwitchEnum diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index 8e28da63a..e9d64ce71 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -1047,7 +1047,7 @@ void GPUContextVulkan::BindIB(GPUBuffer* indexBuffer) void GPUContextVulkan::BindSampler(int32 slot, GPUSampler* sampler) { ASSERT(slot >= GPU_STATIC_SAMPLERS_COUNT && slot < GPU_MAX_SAMPLER_BINDED); - const auto handle = sampler ? ((GPUSamplerVulkan*)sampler)->Sampler : nullptr; + const auto handle = sampler ? ((GPUSamplerVulkan*)sampler)->Sampler : VK_NULL_HANDLE; _samplerHandles[slot] = handle; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp index 8d5a9c4b5..522fc1a89 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp @@ -366,7 +366,7 @@ void GPUDeviceVulkan::GetInstanceLayersAndExtensions(Array& outInst } #if VK_EXT_debug_utils - if (!vkTrace && outDebugUtils && FindLayerExtension(globalLayerExtensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) + if (!vkTrace && FindLayerExtension(globalLayerExtensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { outInstanceExtensions.Add(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp index 1e321149a..6fab41edd 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp @@ -185,6 +185,8 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) // Flush removed resources _device->DeferredDeletionQueue.ReleaseResources(true); } + ASSERT(_surface == VK_NULL_HANDLE); + ASSERT_LOW_LAYER(_backBuffers.Count() == 0); // Create platform-dependent surface VulkanPlatform::CreateSurface(windowHandle, GPUDeviceVulkan::Instance, &_surface); @@ -205,7 +207,7 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) VALIDATE_VULKAN_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, _surface, &surfaceFormatsCount, nullptr)); ASSERT(surfaceFormatsCount > 0); - Array surfaceFormats; + Array> surfaceFormats; surfaceFormats.AddZeroed(surfaceFormatsCount); VALIDATE_VULKAN_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, _surface, &surfaceFormatsCount, surfaceFormats.Get())); @@ -414,8 +416,8 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) } } - // Calculate memory usage - _memoryUsage = RenderTools::CalculateTextureMemoryUsage(_format, _width, _height, 1) * _backBuffers.Count(); + // Estimate memory usage + _memoryUsage = 1024 + RenderTools::CalculateTextureMemoryUsage(_format, _width, _height, 1) * _backBuffers.Count(); return false; } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp index 0ee9e0c82..be8f26255 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp @@ -270,6 +270,11 @@ bool GPUTextureVulkan::OnInit() imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; if (useUAV) imageInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT; +#if PLATFORM_MAC + // MoltenVK: VK_ERROR_FEATURE_NOT_PRESENT: vkCreateImageView(): 2D views on 3D images can only be used as color attachments. + if (IsVolume() && _desc.HasPerSliceViews()) + imageInfo.usage &= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +#endif imageInfo.tiling = optimalTiling ? VK_IMAGE_TILING_OPTIMAL : VK_IMAGE_TILING_LINEAR; imageInfo.samples = (VkSampleCountFlagBits)MultiSampleLevel(); // TODO: set initialLayout to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL for IsRegularTexture() ??? diff --git a/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp index 38cc28d22..9b2998296 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/Mac/MacVulkanPlatform.cpp @@ -8,6 +8,7 @@ void MacVulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers) { + extensions.Add(VK_KHR_SURFACE_EXTENSION_NAME); extensions.Add(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); } diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 87faece63..672644fad 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -1024,10 +1024,12 @@ void UpdateGPU(RenderTask* task, GPUContext* context) const auto& track = particleSystem->Tracks[j]; if (track.Type != ParticleSystem::Track::Types::Emitter || track.Disabled) continue; - const uint32 emitterIndex = track.AsEmitter.Index; - auto emitter = particleSystem->Emitters[emitterIndex].Get(); - auto& data = instance.Emitters[emitterIndex]; - if (!emitter || !emitter->IsLoaded() || !data.Buffer || emitter->SimulationMode != ParticlesSimulationMode::GPU) + const int32 emitterIndex = track.AsEmitter.Index; + ParticleEmitter* emitter = particleSystem->Emitters[emitterIndex].Get(); + if (!emitter || !emitter->IsLoaded() || emitter->SimulationMode != ParticlesSimulationMode::GPU || instance.Emitters.Count() <= emitterIndex) + continue; + ParticleEmitterInstance& data = instance.Emitters[emitterIndex]; + if (!data.Buffer) continue; ASSERT(emitter->Capacity != 0 && emitter->Graph.Layout.Size != 0); diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 84c282fa1..fdc2fc86c 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -242,7 +242,7 @@ void CharacterController::CreateShape() void CharacterController::UpdateBounds() { - void* actor = PhysicsBackend::GetShapeActor(_shape); + void* actor = _shape ? PhysicsBackend::GetShapeActor(_shape) : nullptr; if (actor) PhysicsBackend::GetActorBounds(actor, _box); else diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index 123d390ae..e76304163 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -837,8 +837,8 @@ DragDropEffect MacWindow::DoDragDrop(const StringView& data) void MacWindow::SetCursor(CursorType type) { WindowBase::SetCursor(type); - if (!_isMouseOver) - return; + //if (!_isMouseOver) + // return; NSCursor* cursor = nullptr; switch (type) { @@ -875,6 +875,7 @@ void MacWindow::SetCursor(CursorType type) if (cursor) { [cursor set]; + [NSCursor unhide]; } } diff --git a/Source/Engine/Platform/Win32/Win32Platform.h b/Source/Engine/Platform/Win32/Win32Platform.h index e31e3e985..b2009e803 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.h +++ b/Source/Engine/Platform/Win32/Win32Platform.h @@ -25,7 +25,11 @@ public: static void MemoryBarrier(); static int64 InterlockedExchange(int64 volatile* dst, int64 exchange) { +#if WIN64 return _InterlockedExchange64(dst, exchange); +#else + return _interlockedexchange64(dst, exchange); +#endif } static int32 InterlockedCompareExchange(int32 volatile* dst, int32 exchange, int32 comperand) { @@ -37,15 +41,27 @@ public: } static int64 InterlockedIncrement(int64 volatile* dst) { +#if WIN64 return _InterlockedExchangeAdd64(dst, 1) + 1; +#else + return _interlockedexchange64(dst, 1) + 1; +#endif } static int64 InterlockedDecrement(int64 volatile* dst) { +#if WIN64 return _InterlockedExchangeAdd64(dst, -1) - 1; +#else + return _interlockedexchangeadd64(dst, -1) - 1; +#endif } static int64 InterlockedAdd(int64 volatile* dst, int64 value) { +#if WIN64 return _InterlockedExchangeAdd64(dst, value); +#else + return _interlockedexchangeadd64(dst, value); +#endif } static int32 AtomicRead(int32 volatile* dst) { @@ -61,7 +77,11 @@ public: } static void AtomicStore(int64 volatile* dst, int64 value) { +#if WIN64 _InterlockedExchange64(dst, value); +#else + _interlockedexchange64(dst, value); +#endif } static void Prefetch(void const* ptr); static void* Allocate(uint64 size, uint64 alignment); diff --git a/Source/Engine/Scripting/Events.h b/Source/Engine/Scripting/Events.h index 6599f2cda..c5b798e12 100644 --- a/Source/Engine/Scripting/Events.h +++ b/Source/Engine/Scripting/Events.h @@ -18,7 +18,7 @@ class FLAXENGINE_API ScriptingEvents public: /// - /// Global table for registered even binder methods (key is pair of type and event name, value is method that takes instance with event, object to bind and flag to bind or unbind). + /// Global table for registered event binder methods (key is pair of type and event name, value is method that takes instance with event, object to bind and flag to bind or unbind). /// /// /// Key: pair of event type, event name. diff --git a/Source/Engine/Scripting/SoftObjectReference.h b/Source/Engine/Scripting/SoftObjectReference.h index 8ee877324..3af64f595 100644 --- a/Source/Engine/Scripting/SoftObjectReference.h +++ b/Source/Engine/Scripting/SoftObjectReference.h @@ -24,6 +24,7 @@ public: Delegate<> Changed; public: + NON_COPYABLE(SoftObjectReferenceBase); /// /// Initializes a new instance of the class. diff --git a/Source/Engine/Serialization/Serialization.cpp b/Source/Engine/Serialization/Serialization.cpp index daee80978..447871f1d 100644 --- a/Source/Engine/Serialization/Serialization.cpp +++ b/Source/Engine/Serialization/Serialization.cpp @@ -23,6 +23,8 @@ #include "Engine/Core/Math/Matrix.h" #include "Engine/Scripting/ManagedSerialization.h" #include "Engine/Scripting/ManagedCLR/MUtils.h" +#include "Engine/Scripting/ScriptingObjectReference.h" +#include "Engine/Content/Asset.h" #include "Engine/Utilities/Encryption.h" #if USE_MONO #include diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index b6a9a6da3..6db037ac8 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -5,14 +5,21 @@ #include "SerializationFwd.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" -#include "Engine/Scripting/ScriptingObjectReference.h" -#include "Engine/Scripting/SoftObjectReference.h" -#include "Engine/Content/AssetReference.h" -#include "Engine/Content/WeakAssetReference.h" +#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Utilities/Encryption.h" struct Version; struct VariantType; +template +class ScriptingObjectReference; +template +class SoftObjectReference; +template +class AssetReference; +template +class WeakAssetReference; +template +class SoftAssetReference; // @formatter:off @@ -513,6 +520,26 @@ namespace Serialization v = id; } + // Soft Asset Reference + + template + inline bool ShouldSerialize(const SoftAssetReference& v, const void* otherObj) + { + return !otherObj || v.Get() != ((SoftAssetReference*)otherObj)->Get(); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const SoftAssetReference& v, const void* otherObj) + { + stream.Guid(v.GetID()); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, SoftAssetReference& v, ISerializeModifier* modifier) + { + Guid id; + Deserialize(stream, id, modifier); + v = id; + } + // Array template diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index c7e7f9f91..d86a941bc 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -519,14 +519,12 @@ void ReadStream::ReadJson(ISerializable* obj) void WriteStream::WriteText(const StringView& text) { - for (int32 i = 0; i < text.Length(); i++) - WriteChar(text[i]); + WriteBytes(text.Get(), sizeof(Char) * text.Length()); } void WriteStream::WriteText(const StringAnsiView& text) { - for (int32 i = 0; i < text.Length(); i++) - WriteChar(text[i]); + WriteBytes(text.Get(), sizeof(char) * text.Length()); } void WriteStream::WriteString(const StringView& data) diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index 5628a2471..169036aa9 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -423,7 +423,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) #endif // Report progress - float hemispheresProgress = static_cast(_workerStagePosition1) / lightmapEntry.Hemispheres.Count(); + float hemispheresProgress = static_cast(_workerStagePosition1) / Math::Max(lightmapEntry.Hemispheres.Count(), 1); float lightmapsProgress = static_cast(_workerStagePosition0 + hemispheresProgress) / scene->Lightmaps.Count(); float bouncesProgress = static_cast(_giBounceRunningIndex) / _bounceCount; reportProgress(BuildProgressStep::RenderHemispheres, lightmapsProgress / _bounceCount + bouncesProgress); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp index d59563160..2b3620616 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Options.cpp @@ -42,6 +42,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(ImportVertexColors); SERIALIZE(ImportBlendShapes); SERIALIZE(LightmapUVsSource); + SERIALIZE(CollisionMeshesPrefix); SERIALIZE(Scale); SERIALIZE(Rotation); SERIALIZE(Translation); @@ -79,6 +80,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(ImportVertexColors); DESERIALIZE(ImportBlendShapes); DESERIALIZE(LightmapUVsSource); + DESERIALIZE(CollisionMeshesPrefix); DESERIALIZE(Scale); DESERIALIZE(Rotation); DESERIALIZE(Translation); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index b18392d7d..f014bd571 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -16,6 +16,7 @@ #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/ContentImporters/CreateMaterial.h" +#include "Engine/ContentImporters/CreateCollisionData.h" #include "Editor/Utilities/EditorUtilities.h" #include @@ -562,7 +563,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op materialOptions.Opacity.Texture = data.Textures[material.Opacity.TextureIndex].AssetID; if (material.Normals.TextureIndex != -1) materialOptions.Normals.Texture = data.Textures[material.Normals.TextureIndex].AssetID; - if (material.TwoSided | material.Diffuse.HasAlphaMask) + if (material.TwoSided || material.Diffuse.HasAlphaMask) materialOptions.Info.CullMode = CullMode::TwoSided; if (!Math::IsOne(material.Opacity.Value) || material.Opacity.TextureIndex != -1) materialOptions.Info.BlendMode = MaterialBlendMode::Transparent; @@ -624,6 +625,41 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op } } + // Collision mesh output + if (options.CollisionMeshesPrefix.HasChars()) + { + // Extract collision meshes + ModelData collisionModel; + for (auto& lod : data.LODs) + { + for (int32 i = lod.Meshes.Count() - 1; i >= 0; i--) + { + auto mesh = lod.Meshes[i]; + if (mesh->Name.StartsWith(options.CollisionMeshesPrefix, StringSearchCase::IgnoreCase)) + { + if (collisionModel.LODs.Count() == 0) + collisionModel.LODs.AddOne(); + collisionModel.LODs[0].Meshes.Add(mesh); + lod.Meshes.RemoveAtKeepOrder(i); + if (lod.Meshes.IsEmpty()) + break; + } + } + } + if (collisionModel.LODs.HasItems()) + { + // Create collision + CollisionCooking::Argument arg; + arg.Type = CollisionDataType::TriangleMesh; + arg.OverrideModelData = &collisionModel; + auto assetPath = autoImportOutput / StringUtils::GetFileNameWithoutExtension(path) + TEXT("Collision") ASSET_FILES_EXTENSION_WITH_DOT; + if (CreateCollisionData::CookMeshCollision(assetPath, arg)) + { + LOG(Error, "Failed to create collision mesh."); + } + } + } + // For generated lightmap UVs coordinates needs to be moved so all meshes are in unique locations in [0-1]x[0-1] coordinates space if (options.LightmapUVsSource == ModelLightmapUVsSource::Generate && data.LODs.HasItems() && data.LODs[0].Meshes.Count() > 1) { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 8e60d4e5d..13351db29 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -177,6 +177,7 @@ public: bool ImportVertexColors = true; bool ImportBlendShapes = false; ModelLightmapUVsSource LightmapUVsSource = ModelLightmapUVsSource::Disable; + String CollisionMeshesPrefix; // Transform float Scale = 1.0f; diff --git a/Source/ThirdParty/fmt/core.h b/Source/ThirdParty/fmt/core.h index 7de04519f..7d2686a50 100644 --- a/Source/ThirdParty/fmt/core.h +++ b/Source/ThirdParty/fmt/core.h @@ -18,6 +18,7 @@ #define FMT_USE_WINDOWS_H 0 //#define FMT_USE_STRING_VIEW 1 #define FMT_USE_STRING 0 +#define FMT_USE_LONG_DOUBLE 0 #define FMT_USE_ITERATOR 0 #define FMT_USE_LOCALE_GROUPING 0 #define FMT_EXCEPTIONS 0 diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 5c419ccb5..b28744a50 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -19,6 +19,7 @@ namespace Flax.Build.Bindings internal static readonly Dictionary CSharpNativeToManagedBasicTypes = new Dictionary() { // Language types + { "bool", "bool" }, { "int8", "sbyte" }, { "int16", "short" }, { "int32", "int" }, @@ -233,8 +234,12 @@ namespace Flax.Build.Bindings return result; } - // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + // Object reference property + if ((typeInfo.Type == "ScriptingObjectReference" || + typeInfo.Type == "AssetReference" || + typeInfo.Type == "WeakAssetReference" || + typeInfo.Type == "SoftAssetReference" || + typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller); // Array or Span @@ -310,8 +315,12 @@ namespace Flax.Build.Bindings return "IntPtr"; } - // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + // Object reference property + if ((typeInfo.Type == "ScriptingObjectReference" || + typeInfo.Type == "AssetReference" || + typeInfo.Type == "WeakAssetReference" || + typeInfo.Type == "SoftAssetReference" || + typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return "IntPtr"; // Function @@ -364,8 +373,12 @@ namespace Flax.Build.Bindings return string.Format("FlaxEngine.Object.GetUnmanagedInterface({{0}}, typeof({0}))", apiType.FullNameManaged); } - // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + // Object reference property + if ((typeInfo.Type == "ScriptingObjectReference" || + typeInfo.Type == "AssetReference" || + typeInfo.Type == "WeakAssetReference" || + typeInfo.Type == "SoftAssetReference" || + typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return "FlaxEngine.Object.GetUnmanagedPtr({0})"; // Default diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs index 0072c1240..e9d2076ed 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -19,7 +19,7 @@ namespace Flax.Build.Bindings partial class BindingsGenerator { private static readonly Dictionary TypeCache = new Dictionary(); - private const int CacheVersion = 11; + private const int CacheVersion = 12; internal static void Write(BinaryWriter writer, string e) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 77821e29f..cc101638e 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -16,6 +16,7 @@ namespace Flax.Build.Bindings private static readonly string[] CppParamsThatNeedConversionWrappers = new string[64]; private static readonly string[] CppParamsThatNeedConversionTypes = new string[64]; private static readonly string[] CppParamsWrappersCache = new string[64]; + public static readonly List> CppInternalCalls = new List>(); public static readonly List CppUsedNonPodTypes = new List(); private static readonly List CppUsedNonPodTypesList = new List(); public static readonly HashSet CppReferencesFiles = new HashSet(); @@ -130,7 +131,11 @@ namespace Flax.Build.Bindings return value; if (typeInfo.Type == "String") return $"Variant(StringView({value}))"; - if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") + if (typeInfo.Type == "AssetReference" || + typeInfo.Type == "WeakAssetReference" || + typeInfo.Type == "SoftAssetReference" || + typeInfo.Type == "ScriptingObjectReference" || + typeInfo.Type == "SoftObjectReference") return $"Variant({value}.Get())"; if (typeInfo.IsArray) { @@ -177,7 +182,7 @@ namespace Flax.Build.Bindings return $"(StringView){value}"; if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char") return $"((StringView){value}).GetNonTerminatedText()"; // (StringView)Variant, if not empty, is guaranteed to point to a null-terminated buffer. - if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") + if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftAssetReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; @@ -400,8 +405,12 @@ namespace Flax.Build.Bindings type = "void*"; return "MUtils::ToManaged({0})"; default: - // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + // Object reference property + if ((typeInfo.Type == "ScriptingObjectReference" || + typeInfo.Type == "AssetReference" || + typeInfo.Type == "WeakAssetReference" || + typeInfo.Type == "SoftAssetReference" || + typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) { type = "MonoObject*"; return "{0}.GetManagedInstance()"; @@ -568,8 +577,12 @@ namespace Flax.Build.Bindings type = "void*"; return "MUtils::ToNative({0})"; default: - // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + // Object reference property + if ((typeInfo.Type == "ScriptingObjectReference" || + typeInfo.Type == "AssetReference" || + typeInfo.Type == "WeakAssetReference" || + typeInfo.Type == "SoftAssetReference" || + typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) { // For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration) @@ -771,8 +784,53 @@ namespace Flax.Build.Bindings return $"MUtils::Box<{nativeType}>({value}, {GenerateCppGetNativeClass(buildData, typeInfo, caller, null)})"; } + private static bool GenerateCppWrapperFunctionImplicitBinding(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) + { + if (typeInfo.IsVoid) + return true; + if (typeInfo.IsPtr || typeInfo.IsRef || typeInfo.IsArray || typeInfo.IsBitField || (typeInfo.GenericArgs != null && typeInfo.GenericArgs.Count != 0)) + return false; + if (CSharpNativeToManagedBasicTypes.ContainsKey(typeInfo.Type) || CSharpNativeToManagedBasicTypes.ContainsValue(typeInfo.Type)) + return true; + var apiType = FindApiTypeInfo(buildData, typeInfo, caller); + if (apiType != null) + { + if (apiType.IsEnum) + return true; + } + return false; + } + + private static bool GenerateCppWrapperFunctionImplicitBinding(BuildData buildData, FunctionInfo functionInfo, ApiTypeInfo caller) + { + if (!functionInfo.IsStatic || functionInfo.Access != AccessLevel.Public || (functionInfo.Glue.CustomParameters != null && functionInfo.Glue.CustomParameters.Count != 0)) + return false; + if (!GenerateCppWrapperFunctionImplicitBinding(buildData, functionInfo.ReturnType, caller)) + return false; + for (int i = 0; i < functionInfo.Parameters.Count; i++) + { + var parameterInfo = functionInfo.Parameters[i]; + if (parameterInfo.IsOut || parameterInfo.IsRef || !GenerateCppWrapperFunctionImplicitBinding(buildData, parameterInfo.Type, caller)) + return false; + } + return true; + } + private static void GenerateCppWrapperFunction(BuildData buildData, StringBuilder contents, ApiTypeInfo caller, FunctionInfo functionInfo, string callFormat = "{0}({1})") { + // Optimize static function wrappers that match C# internal call ABI exactly + // Use it for Engine-internally only because in games this makes it problematic to use the same function name but with different signature that is not visible to scripting + if (CurrentModule.Module is EngineModule && callFormat == "{0}({1})" && GenerateCppWrapperFunctionImplicitBinding(buildData, functionInfo, caller)) + { + // Ensure the function name is unique within a class/structure + if (caller is ClassStructInfo classStructInfo && classStructInfo.Functions.All(f => f.Name != functionInfo.Name || f == functionInfo)) + { + // Use native method binding directly (no generated wrapper) + CppInternalCalls.Add(new KeyValuePair(functionInfo.UniqueName, classStructInfo.Name + "::" + functionInfo.Name)); + return; + } + } + // Setup function binding glue to ensure that wrapper method signature matches for C++ and C# functionInfo.Glue = new FunctionInfo.GlueInfo { @@ -797,6 +855,7 @@ namespace Flax.Build.Bindings }); } + CppInternalCalls.Add(new KeyValuePair(functionInfo.UniqueName, functionInfo.UniqueName)); contents.AppendFormat(" static {0} {1}(", returnValueType, functionInfo.UniqueName); var separator = false; @@ -1453,6 +1512,7 @@ namespace Flax.Build.Bindings var useScripting = classInfo.IsStatic || classInfo.IsScriptingObject; var useCSharp = EngineConfiguration.WithCSharp(buildData.TargetOptions); var hasInterface = classInfo.Interfaces != null && classInfo.Interfaces.Any(x => x.Access == AccessLevel.Public); + CppInternalCalls.Clear(); if (classInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, classInfo, classTypeNameNative); @@ -1520,7 +1580,7 @@ namespace Flax.Build.Bindings { // Convert value back from managed to native (could be modified there) paramType.IsRef = false; - var managedToNative = GenerateCppWrapperManagedToNative(buildData, paramType, classInfo, out var managedType, null, out var _); + var managedToNative = GenerateCppWrapperManagedToNative(buildData, paramType, classInfo, out var managedType, null, out _); var passAsParamPtr = managedType.EndsWith("*"); var paramValue = $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]"; if (!string.IsNullOrEmpty(managedToNative)) @@ -1538,6 +1598,7 @@ namespace Flax.Build.Bindings contents.Append(" }").AppendLine().AppendLine(); // C# event wrapper binding method (binds/unbinds C# wrapper to C++ delegate) + CppInternalCalls.Add(new KeyValuePair(eventInfo.Name + "_Bind", eventInfo.Name + "_ManagedBind")); contents.AppendFormat(" static void {0}_ManagedBind(", eventInfo.Name); if (!eventInfo.IsStatic) contents.AppendFormat("{0}* obj, ", classTypeNameNative); @@ -1685,47 +1746,9 @@ namespace Flax.Build.Bindings } if (useScripting && useCSharp) { - foreach (var eventInfo in classInfo.Events) + foreach (var e in CppInternalCalls) { - if (eventInfo.IsHidden) - continue; - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{eventInfo.Name}_Bind\", &{eventInfo.Name}_ManagedBind);"); - } - foreach (var fieldInfo in classInfo.Fields) - { - if (fieldInfo.IsHidden) - continue; - if (fieldInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Getter.UniqueName}\", &{fieldInfo.Getter.UniqueName});"); - if (fieldInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Setter.UniqueName}\", &{fieldInfo.Setter.UniqueName});"); - } - foreach (var propertyInfo in classInfo.Properties) - { - if (propertyInfo.IsHidden) - continue; - if (propertyInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Getter.UniqueName}\", &{propertyInfo.Getter.UniqueName});"); - if (propertyInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Setter.UniqueName}\", &{propertyInfo.Setter.UniqueName});"); - } - foreach (var functionInfo in classInfo.Functions) - { - if (functionInfo.IsHidden) - continue; - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});"); - } - if (hasInterface) - { - foreach (var interfaceInfo in classInfo.Interfaces) - { - if (interfaceInfo.Access != AccessLevel.Public) - continue; - foreach (var functionInfo in interfaceInfo.Functions) - { - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});"); - } - } + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{e.Key}\", &{e.Value});"); } } GenerateCppClassInitRuntime?.Invoke(buildData, classInfo, contents); @@ -1801,6 +1824,7 @@ namespace Flax.Build.Bindings if (structureInfo.Parent != null && !(structureInfo.Parent is FileInfo)) structureTypeNameInternal = structureInfo.Parent.FullNameNative + '_' + structureTypeNameInternal; var useCSharp = EngineConfiguration.WithCSharp(buildData.TargetOptions); + CppInternalCalls.Clear(); if (structureInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, structureInfo, structureTypeNameNative); @@ -1869,12 +1893,9 @@ namespace Flax.Build.Bindings if (useCSharp) { - foreach (var fieldInfo in structureInfo.Fields) + foreach (var e in CppInternalCalls) { - if (fieldInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{structureTypeNameManagedInternalCall}::Internal_{fieldInfo.Getter.UniqueName}\", &{fieldInfo.Getter.UniqueName});"); - if (fieldInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{structureTypeNameManagedInternalCall}::Internal_{fieldInfo.Setter.UniqueName}\", &{fieldInfo.Setter.UniqueName});"); + contents.AppendLine($" ADD_INTERNAL_CALL(\"{structureTypeNameManagedInternalCall}::Internal_{e.Key}\", &{e.Value});"); } } @@ -2189,6 +2210,7 @@ namespace Flax.Build.Bindings CppIncludeFilesList.Clear(); CppVariantToTypes.Clear(); CppVariantFromTypes.Clear(); + CurrentModule = moduleInfo; // Disable C# scripting based on configuration ScriptingLangInfos[0].Enabled = EngineConfiguration.WithCSharp(buildData.TargetOptions); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 7a84947b0..ca683066a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -48,6 +48,7 @@ namespace Flax.Build.Bindings public static event GenerateModuleBindingsDelegate GenerateModuleBindings; public static event GenerateBinaryModuleBindingsDelegate GenerateBinaryModuleBindings; + public static ModuleInfo CurrentModule; public static ModuleInfo ParseModule(BuildData buildData, Module module, BuildOptions moduleOptions = null) { diff --git a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs index 36d97cfde..3f212a3a5 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs @@ -16,6 +16,7 @@ namespace Flax.Build.Bindings public ClassStructInfo BaseType; public List Interfaces; public List Inheritance; // Data from parsing, used to interfaces and base type construct in Init + public List Functions = new List(); public override void Init(Builder.BuildData buildData) { @@ -52,6 +53,7 @@ namespace Flax.Build.Bindings writer.Write((byte)BaseTypeInheritance); BindingsGenerator.Write(writer, BaseType); BindingsGenerator.Write(writer, Inheritance); + BindingsGenerator.Write(writer, Functions); base.Write(writer); } @@ -62,6 +64,7 @@ namespace Flax.Build.Bindings BaseTypeInheritance = (AccessLevel)reader.ReadByte(); BaseType = BindingsGenerator.Read(reader, BaseType); Inheritance = BindingsGenerator.Read(reader, Inheritance); + Functions = BindingsGenerator.Read(reader, Functions); base.Read(reader); } @@ -72,8 +75,6 @@ namespace Flax.Build.Bindings /// public abstract class VirtualClassInfo : ClassStructInfo { - public List Functions = new List(); - internal HashSet UniqueFunctionNames; public override void Init(Builder.BuildData buildData) @@ -100,20 +101,6 @@ namespace Flax.Build.Bindings public abstract int GetScriptVTableOffset(VirtualClassInfo classInfo); - public override void Write(BinaryWriter writer) - { - BindingsGenerator.Write(writer, Functions); - - base.Write(writer); - } - - public override void Read(BinaryReader reader) - { - Functions = BindingsGenerator.Read(reader, Functions); - - base.Read(reader); - } - public override void AddChild(ApiTypeInfo apiTypeInfo) { apiTypeInfo.Namespace = null; diff --git a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs index adc527c18..24c700c22 100644 --- a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs @@ -12,7 +12,6 @@ namespace Flax.Build.Bindings public class StructureInfo : ClassStructInfo { public List Fields = new List(); - public List Functions = new List(); public bool IsAutoSerialization; public bool ForceNoPod; diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs index 682b655a2..77f24782c 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs @@ -271,6 +271,7 @@ namespace Flax.Build.Platforms // Input libraries var libraryPaths = new HashSet(); + var dylibs = new HashSet(); foreach (var library in linkEnvironment.InputLibraries) { var dir = Path.GetDirectoryName(library); @@ -290,6 +291,7 @@ namespace Flax.Build.Platforms else if (ext == ".dylib") { // Link against dynamic library + dylibs.Add(library); task.PrerequisiteFiles.Add(library); libraryPaths.Add(dir); args.Add(string.Format("\"{0}\"", library)); @@ -319,6 +321,7 @@ namespace Flax.Build.Platforms else if (ext == ".dylib") { // Link against dynamic library + dylibs.Add(library); task.PrerequisiteFiles.Add(library); libraryPaths.Add(dir); args.Add(string.Format("\"{0}\"", library)); @@ -370,6 +373,27 @@ namespace Flax.Build.Platforms task.Cost = task.PrerequisiteFiles.Count; task.ProducedFiles.Add(outputFilePath); + Task lastTask = task; + if (options.LinkEnv.Output == LinkerOutput.Executable) + { + // Fix rpath for dynamic libraries + foreach (var library in dylibs) + { + var rpathTask = graph.Add(); + rpathTask.ProducedFiles.Add(outputFilePath); + rpathTask.WorkingDirectory = options.WorkingDirectory; + rpathTask.CommandPath = "install_name_tool"; + var filename = Path.GetFileName(library); + var outputFolder = Path.GetDirectoryName(outputFilePath); + rpathTask.CommandArguments = string.Format("-change \"{0}/{1}\" \"@loader_path/{1}\" {2}", outputFolder, filename, outputFilePath); + rpathTask.InfoMessage = "Fixing rpath to " + filename; + rpathTask.Cost = 1; + rpathTask.DisableCache = true; + rpathTask.DependentTasks = new HashSet(); + rpathTask.DependentTasks.Add(lastTask); + lastTask = rpathTask; + } + } if (!options.LinkEnv.DebugInformation) { // Strip debug symbols @@ -382,7 +406,7 @@ namespace Flax.Build.Platforms stripTask.Cost = 1; stripTask.DisableCache = true; stripTask.DependentTasks = new HashSet(); - stripTask.DependentTasks.Add(task); + stripTask.DependentTasks.Add(lastTask); } }