From aecc81f5e5adde21980c5cbed4a243d14f7646ce Mon Sep 17 00:00:00 2001 From: Zbigniew Skowron Date: Sun, 8 Aug 2021 22:04:54 +0200 Subject: [PATCH 01/94] Fixed many invalid uses of StringView::GetText(), where a null-terminated string was required. Renamed GetText() to GetNonTerminatedText() to reduce chance of same bugs appearing in the future. --- Source/Editor/Utilities/EditorUtilities.cpp | 2 +- Source/Engine/Content/Content.cpp | 9 +- Source/Engine/Content/Content.h | 8 ++ Source/Engine/Core/Types/String.h | 132 +++++++++++------- Source/Engine/Core/Types/StringView.cpp | 16 +-- Source/Engine/Core/Types/StringView.h | 41 +++--- Source/Engine/Localization/Localization.cpp | 10 +- Source/Engine/Platform/Base/PlatformBase.h | 12 +- .../Engine/Platform/Linux/LinuxPlatform.cpp | 2 +- Source/Engine/Platform/StringUtils.h | 20 +-- Source/Engine/Platform/UWP/UWPPlatform.cpp | 2 +- .../Platform/Windows/WindowsClipboard.cpp | 2 +- .../Platform/Windows/WindowsPlatform.cpp | 8 +- .../Bindings/BindingsGenerator.Cpp.cs | 2 +- 14 files changed, 158 insertions(+), 108 deletions(-) diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index 85d409b92..1b271d3d1 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -816,6 +816,6 @@ bool EditorUtilities::ReplaceInFile(const StringView& file, const StringView& fi String text; if (File::ReadAllText(file, text)) return true; - text.Replace(findWhat.GetText(), replaceWith.GetText()); + text.Replace(findWhat.Get(), findWhat.Length(), replaceWith.Get(), replaceWith.Length()); return File::WriteAllText(file, text, Encoding::ANSI); } diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 98e2c241c..abe40e285 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -384,12 +384,12 @@ Asset* Content::LoadAsyncInternal(const StringView& internalPath, MClass* type) CHECK_RETURN(type, nullptr); const auto scriptingType = Scripting::FindScriptingType(type->GetFullName()); if (scriptingType) - return LoadAsyncInternal(internalPath.GetText(), scriptingType); + return LoadAsyncInternal(internalPath, scriptingType); LOG(Error, "Failed to find asset type '{0}'.", String(type->GetFullName())); return nullptr; } -Asset* Content::LoadAsyncInternal(const Char* internalPath, const ScriptingTypeHandle& type) +Asset* Content::LoadAsyncInternal(const StringView& internalPath, const ScriptingTypeHandle& type) { #if USE_EDITOR const String path = Globals::EngineContentFolder / internalPath + ASSET_FILES_EXTENSION_WITH_DOT; @@ -411,6 +411,11 @@ Asset* Content::LoadAsyncInternal(const Char* internalPath, const ScriptingTypeH return asset; } +Asset* Content::LoadAsyncInternal(const Char* internalPath, const ScriptingTypeHandle& type) +{ + return LoadAsyncInternal(StringView(internalPath), type); +} + FLAXENGINE_API Asset* LoadAsset(const Guid& id, const ScriptingTypeHandle& type) { return Content::LoadAsync(id, type); diff --git a/Source/Engine/Content/Content.h b/Source/Engine/Content/Content.h index da558dc5f..c8e3cad90 100644 --- a/Source/Engine/Content/Content.h +++ b/Source/Engine/Content/Content.h @@ -187,6 +187,14 @@ public: /// The loaded asset or null if failed. API_FUNCTION(Attributes="HideInEditor") static Asset* LoadAsyncInternal(const StringView& internalPath, MClass* type); + /// + /// Loads internal engine asset and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async. + /// + /// The path of the asset relative to the engine internal content (excluding the extension). + /// The asset type. If loaded object has different type (excluding types derived from the given) the loading fails. + /// The loaded asset or null if failed. + static Asset* LoadAsyncInternal(const StringView& internalPath, const ScriptingTypeHandle& type); + /// /// Loads internal engine asset and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async. /// diff --git a/Source/Engine/Core/Types/String.h b/Source/Engine/Core/Types/String.h index 6871cb287..0a62c516f 100644 --- a/Source/Engine/Core/Types/String.h +++ b/Source/Engine/Core/Types/String.h @@ -413,67 +413,97 @@ public: return replacedChars; } + /// + /// Replaces all occurences of searchText within current string with replacementText. + /// + /// String to search for. If empty or null no replacements are done. + /// String to replace with. Null is treated as empty string. + /// Number of replacements made. (In case-sensitive mode if search text and replacement text are equal no replacements are done, and zero is returned.) int32 Replace(const T* searchText, const T* replacementText, StringSearchCase searchCase = StringSearchCase::CaseSensitive) { - int32 replacedCount = 0; - if (HasChars() && searchText && *searchText && replacementText && (searchCase == StringSearchCase::IgnoreCase || StringUtils::Compare(searchText, replacementText) != 0)) + const int32 searchTextLength = StringUtils::Length(searchText); + const int32 replacementTextLength = StringUtils::Length(replacementText); + return Replace(searchText, searchTextLength, replacementText, replacementTextLength, searchCase); + } + + /// + /// Replaces all occurences of searchText within current string with replacementText. + /// + /// String to search for. If empty or null no replacements are done. + /// Length of searchText. + /// String to replace with. Null is treated as empty string. + /// Length of replacementText. + /// Number of replacements made. (In case-sensitive mode if search text and replacement text are equal no replacements are done, and zero is returned.) + int32 Replace(const T* searchText, int32 searchTextLength, const T* replacementText, int32 replacementTextLength, StringSearchCase searchCase = StringSearchCase::CaseSensitive) + { + if (!HasChars()) + return 0; + + if (searchTextLength == 0) + return 0; + + // If we are doing case sensitive search and replacement text is equal to search text, we do nothing. + if ((searchCase == StringSearchCase::CaseSensitive) && (searchTextLength == replacementTextLength) && (StringUtils::Compare(searchText, replacementText) == 0)) { - const int32 searchTextLength = StringUtils::Length(searchText); - const int32 replacementTextLength = StringUtils::Length(replacementText); - if (searchTextLength == replacementTextLength) + return 0; + } + + int32 replacedCount = 0; + + if (searchTextLength == replacementTextLength) + { + T* pos = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(_data, searchText) : StringUtils::Find(_data, searchText)); + while (pos != nullptr) { - T* pos = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(_data, searchText) : StringUtils::Find(_data, searchText)); - while (pos != nullptr) - { - replacedCount++; + replacedCount++; - for (int32 i = 0; i < replacementTextLength; i++) - pos[i] = replacementText[i]; + for (int32 i = 0; i < replacementTextLength; i++) + pos[i] = replacementText[i]; - if (pos + searchTextLength - **this < Length()) - pos = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(pos + searchTextLength, searchText) : StringUtils::Find(pos + searchTextLength, searchText)); - else - break; - } + if (pos + searchTextLength - **this < Length()) + pos = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(pos + searchTextLength, searchText) : StringUtils::Find(pos + searchTextLength, searchText)); + else + break; } - else if (Contains(searchText, searchCase)) + } + else if (Contains(searchText, searchCase)) + { + T* readPosition = _data; + T* searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText)); + while (searchPosition != nullptr) { - T* readPosition = _data; - T* searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText)); - while (searchPosition != nullptr) - { - replacedCount++; - readPosition = searchPosition + searchTextLength; - searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText)); - } - - const auto oldLength = _length; - const auto oldData = _data; - _length += replacedCount * (replacementTextLength - searchTextLength); - _data = (T*)Platform::Allocate((_length + 1) * sizeof(T), 16); - - T* writePosition = _data; - readPosition = oldData; + replacedCount++; + readPosition = searchPosition + searchTextLength; searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText)); - while (searchPosition != nullptr) - { - const int32 writeOffset = (int32)(searchPosition - readPosition); - Platform::MemoryCopy(writePosition, readPosition, writeOffset * sizeof(T)); - writePosition += writeOffset; - - Platform::MemoryCopy(writePosition, replacementText, replacementTextLength * sizeof(T)); - writePosition += replacementTextLength; - - readPosition = searchPosition + searchTextLength; - searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText)); - } - - const int32 writeOffset = (int32)(oldData - readPosition) + oldLength; - Platform::MemoryCopy(writePosition, readPosition, writeOffset * sizeof(T)); - - _data[_length] = 0; - Platform::Free(oldData); } + + const auto oldLength = _length; + const auto oldData = _data; + _length += replacedCount * (replacementTextLength - searchTextLength); + _data = (T*)Platform::Allocate((_length + 1) * sizeof(T), 16); + + T* writePosition = _data; + readPosition = oldData; + searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText)); + while (searchPosition != nullptr) + { + const int32 writeOffset = (int32)(searchPosition - readPosition); + Platform::MemoryCopy(writePosition, readPosition, writeOffset * sizeof(T)); + writePosition += writeOffset; + + if (replacementTextLength > 0) + Platform::MemoryCopy(writePosition, replacementText, replacementTextLength * sizeof(T)); + writePosition += replacementTextLength; + + readPosition = searchPosition + searchTextLength; + searchPosition = (T*)(searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(readPosition, searchText) : StringUtils::Find(readPosition, searchText)); + } + + const int32 writeOffset = (int32)(oldData - readPosition) + oldLength; + Platform::MemoryCopy(writePosition, readPosition, writeOffset * sizeof(T)); + + _data[_length] = 0; + Platform::Free(oldData); } return replacedCount; diff --git a/Source/Engine/Core/Types/StringView.cpp b/Source/Engine/Core/Types/StringView.cpp index 606e37ad1..4df8bc8e9 100644 --- a/Source/Engine/Core/Types/StringView.cpp +++ b/Source/Engine/Core/Types/StringView.cpp @@ -18,12 +18,12 @@ StringView::StringView(const String& str) bool StringView::operator==(const String& other) const { - return StringUtils::Compare(this->GetText(), *other) == 0; + return this->Compare(StringView(other)) == 0; } bool StringView::operator!=(const String& other) const { - return StringUtils::Compare(this->GetText(), *other) != 0; + return this->Compare(StringView(other)) != 0; } StringView StringView::Left(int32 count) const @@ -62,12 +62,12 @@ StringAnsi StringView::ToStringAnsi() const bool operator==(const String& a, const StringView& b) { - return a.Length() == b.Length() && StringUtils::Compare(a.GetText(), b.GetText(), b.Length()) == 0; + return a.Length() == b.Length() && StringUtils::Compare(a.GetText(), b.GetNonTerminatedText(), b.Length()) == 0; } bool operator!=(const String& a, const StringView& b) { - return a.Length() != b.Length() || StringUtils::Compare(a.GetText(), b.GetText(), b.Length()) != 0; + return a.Length() != b.Length() || StringUtils::Compare(a.GetText(), b.GetNonTerminatedText(), b.Length()) != 0; } StringAnsiView StringAnsiView::Empty; @@ -79,12 +79,12 @@ StringAnsiView::StringAnsiView(const StringAnsi& str) bool StringAnsiView::operator==(const StringAnsi& other) const { - return StringUtils::Compare(this->GetText(), *other) == 0; + return this->Compare(StringAnsiView(other)) == 0; } bool StringAnsiView::operator!=(const StringAnsi& other) const { - return StringUtils::Compare(this->GetText(), *other) != 0; + return this->Compare(StringAnsiView(other)) != 0; } StringAnsi StringAnsiView::Substring(int32 startIndex) const @@ -111,10 +111,10 @@ StringAnsi StringAnsiView::ToStringAnsi() const bool operator==(const StringAnsi& a, const StringAnsiView& b) { - return a.Length() == b.Length() && StringUtils::Compare(a.GetText(), b.GetText(), b.Length()) == 0; + return a.Length() == b.Length() && StringUtils::Compare(a.GetText(), b.GetNonTerminatedText(), b.Length()) == 0; } bool operator!=(const StringAnsi& a, const StringAnsiView& b) { - return a.Length() != b.Length() || StringUtils::Compare(a.GetText(), b.GetText(), b.Length()) != 0; + return a.Length() != b.Length() || StringUtils::Compare(a.GetText(), b.GetNonTerminatedText(), b.Length()) != 0; } diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h index 0ed2580ce..f0d608493 100644 --- a/Source/Engine/Core/Types/StringView.h +++ b/Source/Engine/Core/Types/StringView.h @@ -63,9 +63,12 @@ public: const int32 lengthDiff = Length() - str.Length(); if (lengthDiff != 0) return lengthDiff; + if (Length() == 0) + return 0; + // We know here that both this StringView and str are not empty, and therefore Get() below are valid. if (searchCase == StringSearchCase::CaseSensitive) - return StringUtils::Compare(this->GetText(), str.GetText(), Length()); - return StringUtils::CompareIgnoreCase(this->GetText(), str.GetText(), Length()); + return StringUtils::Compare(this->Get(), str.Get(), Length()); + return StringUtils::CompareIgnoreCase(this->Get(), str.Get(), Length()); } public: @@ -95,7 +98,7 @@ public: } /// - /// Gets the pointer to the string. + /// Gets the pointer to the string. Pointer can be null, and won't be null-terminated. /// FORCE_INLINE constexpr const T* operator*() const { @@ -103,7 +106,7 @@ public: } /// - /// Gets the pointer to the string. + /// Gets the pointer to the string. Pointer can be null, and won't be null-terminated. /// FORCE_INLINE constexpr const T* Get() const { @@ -111,9 +114,9 @@ public: } /// - /// Gets the pointer to the string or to the static empty text if string is null. Returned pointer is always valid (read-only). + /// Gets the pointer to the string or to the static empty text if string is null. Returned pointer is always non-null, but is not null-terminated. /// - FORCE_INLINE const T* GetText() const + FORCE_INLINE const T* GetNonTerminatedText() const { return _data ? _data : (const T*)TEXT(""); } @@ -177,15 +180,17 @@ public: { if (prefix.IsEmpty() || Length() < prefix.Length()) return false; + // We know that this StringView is not empty, and therefore Get() below is valid. if (searchCase == StringSearchCase::IgnoreCase) - return StringUtils::CompareIgnoreCase(this->GetText(), *prefix, prefix.Length()) == 0; - return StringUtils::Compare(this->GetText(), *prefix, prefix.Length()) == 0; + return StringUtils::CompareIgnoreCase(this->Get(), *prefix, prefix.Length()) == 0; + return StringUtils::Compare(this->Get(), *prefix, prefix.Length()) == 0; } bool EndsWith(const StringViewBase& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const { if (suffix.IsEmpty() || Length() < suffix.Length()) return false; + // We know that this StringView is not empty, and therefore accessing data below is valid. if (searchCase == StringSearchCase::IgnoreCase) return StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix) == 0; return StringUtils::Compare(&(*this)[Length() - suffix.Length()], *suffix) == 0; @@ -232,7 +237,7 @@ public: /// /// Initializes a new instance of the class. /// - /// The characters sequence. + /// The characters sequence. If null, constructed StringView will be empty. StringView(const Char* str) { _data = str; @@ -242,7 +247,7 @@ public: /// /// Initializes a new instance of the class. /// - /// The characters sequence. + /// The characters sequence. Can be null if length is zero. /// The characters sequence length (excluding null-terminator character). constexpr StringView(const Char* str, int32 length) : StringViewBase(str, length) @@ -270,7 +275,7 @@ public: /// True if this string is lexicographically equivalent to the other, otherwise false. FORCE_INLINE bool operator==(const StringView& other) const { - return StringUtils::Compare(this->GetText(), other.GetText()) == 0; + return this->Compare(other) == 0; } /// @@ -280,7 +285,7 @@ public: /// True if this string is lexicographically is not equivalent to the other, otherwise false. FORCE_INLINE bool operator!=(const StringView& other) const { - return StringUtils::Compare(this->GetText(), other.GetText()) != 0; + return this->Compare(other) != 0; } /// @@ -290,7 +295,7 @@ public: /// True if this string is lexicographically equivalent to the other, otherwise false. FORCE_INLINE bool operator==(const Char* other) const { - return StringUtils::Compare(this->GetText(), other ? other : TEXT("")) == 0; + return this->Compare(StringView(other)) == 0; } /// @@ -300,7 +305,7 @@ public: /// True if this string is lexicographically is not equivalent to the other, otherwise false. FORCE_INLINE bool operator!=(const Char* other) const { - return StringUtils::Compare(this->GetText(), other ? other : TEXT("")) != 0; + return this->Compare(StringView(other)) != 0; } /// @@ -459,7 +464,7 @@ public: /// True if this string is lexicographically equivalent to the other, otherwise false. FORCE_INLINE bool operator==(const StringAnsiView& other) const { - return StringUtils::Compare(this->GetText(), other.GetText()) == 0; + return this->Compare(other) == 0; } /// @@ -469,7 +474,7 @@ public: /// True if this string is lexicographically is not equivalent to the other, otherwise false. FORCE_INLINE bool operator!=(const StringAnsiView& other) const { - return StringUtils::Compare(this->GetText(), other.GetText()) != 0; + return this->Compare(other) != 0; } /// @@ -479,7 +484,7 @@ public: /// True if this string is lexicographically equivalent to the other, otherwise false. FORCE_INLINE bool operator==(const char* other) const { - return StringUtils::Compare(this->GetText(), other ? other : "") == 0; + return this->Compare(StringAnsiView(other)) == 0; } /// @@ -489,7 +494,7 @@ public: /// True if this string is lexicographically is not equivalent to the other, otherwise false. FORCE_INLINE bool operator!=(const char* other) const { - return StringUtils::Compare(this->GetText(), other ? other : "") != 0; + return this->Compare(StringAnsiView(other)) != 0; } /// diff --git a/Source/Engine/Localization/Localization.cpp b/Source/Engine/Localization/Localization.cpp index defab1c4f..7ac0a09ed 100644 --- a/Source/Engine/Localization/Localization.cpp +++ b/Source/Engine/Localization/Localization.cpp @@ -278,18 +278,18 @@ String Localization::GetPluralString(const String& id, int32 n, const String& fa { CHECK_RETURN(n >= 1, fallback); n--; - StringView result; + const String* result = nullptr; for (auto& e : Instance.LocalizedStringTables) { const auto table = e.Get(); const auto messages = table ? table->Entries.TryGet(id) : nullptr; if (messages && messages->Count() > n) { - result = messages->At(n); + result = &messages->At(n); break; } } - if (result.IsEmpty()) - result = fallback; - return String::Format(result.GetText(), n); + if (!result) + result = &fallback; + return String::Format(result->GetText(), n); } diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index b6d5d67bd..ac2150c58 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -169,8 +169,8 @@ public: /// /// Copy memory region /// - /// Destination memory address - /// Source memory address + /// Destination memory address. Must not be null, even if size is zero. + /// Source memory address. Must not be null, even if size is zero. /// Size of the memory to copy in bytes FORCE_INLINE static void MemoryCopy(void* dst, const void* src, uint64 size) { @@ -180,7 +180,7 @@ public: /// /// Set memory region with given value /// - /// Destination memory address + /// Destination memory address. Must not be null, even if size is zero. /// Size of the memory to set in bytes /// Value to set FORCE_INLINE static void MemorySet(void* dst, uint64 size, int32 value) @@ -191,7 +191,7 @@ public: /// /// Clear memory region with zeros /// - /// Destination memory address + /// Destination memory address. Must not be null, even if size is zero. /// Size of the memory to clear in bytes FORCE_INLINE static void MemoryClear(void* dst, uint64 size) { @@ -201,8 +201,8 @@ public: /// /// Compare two blocks of the memory. /// - /// The first buffer address. - /// The second buffer address. + /// The first buffer address. Must not be null, even if size is zero. + /// The second buffer address. Must not be null, even if size is zero. /// Size of the memory to compare in bytes. FORCE_INLINE static int32 MemoryCompare(const void* buf1, const void* buf2, uint64 size) { diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index f0339225a..49c4e3aaa 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -1659,7 +1659,7 @@ void LinuxClipboard::SetText(const StringView& text) return; X11::Window window = (X11::Window)mainWindow->GetNativePtr(); - Impl::ClipboardText.Set(text.GetText(), text.Length()); + Impl::ClipboardText.Set(text.Get(), text.Length()); X11::XSetSelectionOwner(xDisplay, xAtomClipboard, window, CurrentTime); // CLIPBOARD X11::XSetSelectionOwner(xDisplay, (X11::Atom)1, window, CurrentTime); // XA_PRIMARY } diff --git a/Source/Engine/Platform/StringUtils.h b/Source/Engine/Platform/StringUtils.h index 101049b8e..4905ec646 100644 --- a/Source/Engine/Platform/StringUtils.h +++ b/Source/Engine/Platform/StringUtils.h @@ -120,36 +120,36 @@ public: public: - // Compare two strings with case sensitive + // Compare two strings with case sensitive. Strings must not be null. static int32 Compare(const Char* str1, const Char* str2); - // Compare two strings without case sensitive + // Compare two strings without case sensitive. Strings must not be null. static int32 Compare(const Char* str1, const Char* str2, int32 maxCount); - // Compare two strings without case sensitive + // Compare two strings without case sensitive. Strings must not be null. static int32 CompareIgnoreCase(const Char* str1, const Char* str2); - // Compare two strings without case sensitive + // Compare two strings without case sensitive. Strings must not be null. static int32 CompareIgnoreCase(const Char* str1, const Char* str2, int32 maxCount); - // Compare two strings with case sensitive + // Compare two strings with case sensitive. Strings must not be null. static int32 Compare(const char* str1, const char* str2); - // Compare two strings without case sensitive + // Compare two strings without case sensitive. Strings must not be null. static int32 Compare(const char* str1, const char* str2, int32 maxCount); - // Compare two strings without case sensitive + // Compare two strings without case sensitive. Strings must not be null. static int32 CompareIgnoreCase(const char* str1, const char* str2); - // Compare two strings without case sensitive + // Compare two strings without case sensitive. Strings must not be null. static int32 CompareIgnoreCase(const char* str1, const char* str2, int32 maxCount); public: - // Get string length + // Get string length. Returns 0 if str is null. static int32 Length(const Char* str); - // Get string length + // Get string length. Returns 0 if str is null. static int32 Length(const char* str); // Copy string diff --git a/Source/Engine/Platform/UWP/UWPPlatform.cpp b/Source/Engine/Platform/UWP/UWPPlatform.cpp index b0fdf8f21..aeb799a2c 100644 --- a/Source/Engine/Platform/UWP/UWPPlatform.cpp +++ b/Source/Engine/Platform/UWP/UWPPlatform.cpp @@ -35,7 +35,7 @@ void RunUWP() DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon) { - return (DialogResult)CUWPPlatform->ShowMessageDialog(parent ? parent->GetImpl() : nullptr, text.GetText(), caption.GetText(), (UWPPlatformImpl::MessageBoxButtons)buttons, (UWPPlatformImpl::MessageBoxIcon)icon); + return (DialogResult)CUWPPlatform->ShowMessageDialog(parent ? parent->GetImpl() : nullptr, String(text).GetText(), String(caption).GetText(), (UWPPlatformImpl::MessageBoxButtons)buttons, (UWPPlatformImpl::MessageBoxIcon)icon); } bool UWPPlatform::Init() diff --git a/Source/Engine/Platform/Windows/WindowsClipboard.cpp b/Source/Engine/Platform/Windows/WindowsClipboard.cpp index 196c1d7b3..baba81e8f 100644 --- a/Source/Engine/Platform/Windows/WindowsClipboard.cpp +++ b/Source/Engine/Platform/Windows/WindowsClipboard.cpp @@ -25,7 +25,7 @@ void WindowsClipboard::SetText(const StringView& text) { const int32 size = (text.Length() + 1) * sizeof(Char); const HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, size); - Platform::MemoryCopy(GlobalLock(hMem), text.GetText(), size); + Platform::MemoryCopy(GlobalLock(hMem), String(text).GetText(), size); GlobalUnlock(hMem); OpenClipboard(nullptr); diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 759713f7c..6849c0ac8 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -435,7 +435,7 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri } // Show dialog - int result = MessageBoxW(parent ? static_cast(parent->GetNativePtr()) : nullptr, text.GetText(), caption.GetText(), flags); + int result = MessageBoxW(parent ? static_cast(parent->GetNativePtr()) : nullptr, String(text).GetText(), String(caption).GetText(), flags); // Translate result to dialog result DialogResult dialogResult; @@ -948,10 +948,12 @@ int32 WindowsPlatform::StartProcess(const StringView& filename, const StringView LOG(Info, "Working directory: {0}", workingDir); } + String filenameString(filename); + SHELLEXECUTEINFOW shExecInfo = { 0 }; shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW); shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; - shExecInfo.lpFile = filename.GetText(); + shExecInfo.lpFile = filenameString.GetText(); shExecInfo.lpParameters = args.HasChars() ? args.Get() : nullptr; shExecInfo.lpDirectory = workingDir.HasChars() ? workingDir.Get() : nullptr; shExecInfo.nShow = hiddenWindow ? SW_HIDE : SW_SHOW; @@ -1109,7 +1111,7 @@ int32 WindowsPlatform::RunProcess(const StringView& cmdLine, const StringView& w // Create the process PROCESS_INFORMATION procInfo; - if (!CreateProcessW(nullptr, const_cast(cmdLine.GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, workingDir.HasChars() ? workingDir.Get() : nullptr, &startupInfoEx.StartupInfo, &procInfo)) + if (!CreateProcessW(nullptr, const_cast(String(cmdLine).GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, String(workingDir).GetText(), &startupInfoEx.StartupInfo, &procInfo)) { LOG(Warning, "Cannot start process '{0}'. Error code: 0x{1:x}", cmdLine, static_cast(GetLastError())); goto ERROR_EXIT; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index e61a2cf0a..a071e161c 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -166,7 +166,7 @@ namespace Flax.Build.Bindings if (typeInfo.Type == "String") return $"(StringView){value}"; if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char") - return $"((StringView){value}).GetText()"; + return $"((StringView){value}).GetNonTerminatedText()"; // This is a bug, as we need a null-terminated strig here. Any idea how to fix it? if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") From 9a590ae48427e02b18adac7c7c48acc198c0a905 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 11:59:08 +0200 Subject: [PATCH 02/94] Fix actor renaming on paste to keep previous name if can --- Source/Editor/Undo/Actions/PasteActorsAction.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs index c77feb71d..94e8990b2 100644 --- a/Source/Editor/Undo/Actions/PasteActorsAction.cs +++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs @@ -141,15 +141,20 @@ namespace FlaxEditor.Actions for (int i = 0; i < nodeParents.Count; i++) { var node = nodeParents[i]; - var parent = node.Actor?.Parent; + var actor = node.Actor; + var parent = actor?.Parent; if (parent != null) { // Fix name collisions - string name = node.Name; - Actor[] children = parent.Children; - if (children.Any(x => x.Name == name)) + var name = actor.Name; + for (int j = 0; j < parent.ChildrenCount; j++) { - node.Actor.Name = StringUtils.IncrementNameNumber(name, x => children.All(y => y.Name != x)); + var child = parent.Children[j]; + if (child != actor && child.Name == actor.Name) + { + var children = parent.Children; + actor.Name = StringUtils.IncrementNameNumber(name, x => children.All(y => y.Name != x)); + } } } From f68321c9f5391e2665b51f5136ddd64bf3aaa784 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 12:12:32 +0200 Subject: [PATCH 03/94] Fix pasting actors if cannot spawn a object (eg. type missing) to still paste valid objects --- Source/Engine/Level/Actor.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 63fc12b20..076ac4608 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1611,21 +1611,22 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM // Create object auto obj = SceneObjectsFactory::Spawn(document, modifier); + sceneObjects->At(i) = obj; if (obj == nullptr) { LOG(Warning, "Cannot create object."); - return true; + continue; } obj->RegisterObject(); // Add to results - sceneObjects->At(i) = obj; Actor* actor = dynamic_cast(obj); if (actor) { output.Add(actor); } } + // TODO: optimize this to call json parsing only once per-object instead of twice (spawn + load) stream.SetPosition(startPos); for (int32 i = 0; i < objectsCount; i++) { @@ -1639,7 +1640,7 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM int32 orderInParent; stream.ReadInt32(&orderInParent); - // Load JSON + // Load JSON rapidjson_flax::Document document; { PROFILE_CPU_NAMED("Json.Parse"); @@ -1653,7 +1654,10 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM // Deserialize object auto obj = sceneObjects->At(i); - SceneObjectsFactory::Deserialize(obj, document, modifier); + if (obj) + SceneObjectsFactory::Deserialize(obj, document, modifier); + else + SceneObjectsFactory::HandleObjectDeserializationError(document); } Scripting::ObjectsLookupIdMapping.Set(nullptr); From fd01d37002d4c00e01a4a73c4f7faa6c88950ddc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 12:12:56 +0200 Subject: [PATCH 04/94] Add helper parent actor of missing object logging to `HandleObjectDeserializationError` --- Source/Engine/Level/Level.cpp | 16 ---------------- Source/Engine/Level/SceneObjectsFactory.cpp | 14 +++++++++++++- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index e97cf6b20..2659379a5 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -998,25 +998,9 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, bool autoI auto& objData = data[i]; auto obj = sceneObjects->At(i); if (obj) - { SceneObjectsFactory::Deserialize(obj, objData, modifier.Value); - } else - { SceneObjectsFactory::HandleObjectDeserializationError(objData); - - // Try to log some useful info about missing object (eg. it's parent name for faster fixing) - const auto parentIdMember = objData.FindMember("ParentID"); - if (parentIdMember != objData.MemberEnd() && parentIdMember->value.IsString()) - { - Guid parentId = JsonTools::GetGuid(parentIdMember->value); - Actor* parent = Scripting::FindObject(parentId); - if (parent) - { - LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName()); - } - } - } } Scripting::ObjectsLookupIdMapping.Set(nullptr); } diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 321b0f6f1..897066d5e 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -344,11 +344,23 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::DeserializeStream& value) { + // Print invalid object data contents rapidjson_flax::StringBuffer buffer; PrettyJsonWriter writer(buffer); value.Accept(writer.GetWriter()); - LOG(Warning, "Failed to deserialize scene object from data: {0}", String(buffer.GetString())); + + // Try to log some useful info about missing object (eg. it's parent name for faster fixing) + const auto parentIdMember = value.FindMember("ParentID"); + if (parentIdMember != value.MemberEnd() && parentIdMember->value.IsString()) + { + const Guid parentId = JsonTools::GetGuid(parentIdMember->value); + Actor* parent = Scripting::FindObject(parentId); + if (parent) + { + LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName()); + } + } } Actor* SceneObjectsFactory::CreateActor(int32 typeId, const Guid& id) From 09c11250b4d92a69a76db76175717946d93b5299 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 14:35:22 +0200 Subject: [PATCH 05/94] Add options for vehicle wheel suspension configuration and state --- .../Engine/Physics/Actors/WheeledVehicle.cpp | 7 ++--- Source/Engine/Physics/Actors/WheeledVehicle.h | 30 +++++++++++++++++++ Source/Engine/Physics/Physics.cpp | 8 +++-- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 0fe53f570..388aa1f21 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -303,12 +303,11 @@ void WheeledVehicle::Setup() PxVehicleSuspensionData suspensionData; const float suspensionFrequency = 7.0f; - const float suspensionDampingRatio = 1.0f; - suspensionData.mMaxCompression = 10.0f; - suspensionData.mMaxDroop = 10.0f; + suspensionData.mMaxCompression = wheel.SuspensionMaxRaise; + suspensionData.mMaxDroop = wheel.SuspensionMaxDrop; suspensionData.mSprungMass = sprungMasses[i]; suspensionData.mSpringStrength = Math::Square(suspensionFrequency) * suspensionData.mSprungMass; - suspensionData.mSpringDamperRate = suspensionDampingRatio * 2.0f * Math::Sqrt(suspensionData.mSpringStrength * suspensionData.mSprungMass); + suspensionData.mSpringDamperRate = wheel.SuspensionDampingRate * 2.0f * Math::Sqrt(suspensionData.mSpringStrength * suspensionData.mSprungMass); PxVehicleTireData tire; tire.mType = 0; diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index 1941038bf..a6a1708ad 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -213,6 +213,21 @@ public: /// Collider that represents the wheel shape and it's placement. Has to be attached as a child to the vehicle. Triangle mesh collider is not supported (use convex mesh or basic shapes). /// API_FIELD() ScriptingObjectReference Collider; + + /// + /// Spring damper rate of suspension unit. + /// + API_FIELD(Attributes="Limit(0)") float SuspensionDampingRate = 1.0f; + + /// + /// The maximum offset for the suspension that wheel can go above resting location. + /// + API_FIELD(Attributes="Limit(0)") float SuspensionMaxRaise = 10.0f; + + /// + /// The maximum offset for the suspension that wheel can go below resting location. + /// + API_FIELD(Attributes="Limit(0)") float SuspensionMaxDrop = 10.0f; }; /// @@ -246,6 +261,21 @@ public: /// The friction experienced by the tire for the combination of tire type and surface type after accounting. /// API_FIELD() float TireFriction = 0.0f; + + /// + /// The steer angle (in degrees) of the wheel about the "up" vector accounting for input steer and toe and, if applicable, Ackermann steer correction. + /// + API_FIELD() float SteerAngle = 0.0f; + + /// + /// The rotation angle (in degrees) about the rolling axis for the specified wheel. + /// + API_FIELD() float RotationAngle = 0.0f; + + /// + /// The compression of the suspension spring. Offsets the wheel location. + /// + API_FIELD() float SuspensionOffset = 0.0f; }; private: diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index bdee2fdc2..8066f404d 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -845,10 +845,12 @@ void Physics::CollectResults() state.TireContactPoint = P2C(perWheel.tireContactPoint); state.TireContactNormal = P2C(perWheel.tireContactNormal); state.TireFriction = perWheel.tireFriction; + state.SteerAngle = RadiansToDegrees * perWheel.steerAngle; + state.RotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j); + const float suspensionOffsetDelta = perWheel.suspJounce - state.SuspensionOffset; + state.SuspensionOffset = perWheel.suspJounce; - const float wheelRotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j); - const float wheelSteerAngle = RadiansToDegrees * perWheel.steerAngle; - wheelData.Collider->SetLocalOrientation(Quaternion::Euler(0, wheelSteerAngle, wheelRotationAngle) * wheelData.LocalOrientation); + wheelData.Collider->SetLocalOrientation(Quaternion::Euler(0, state.SteerAngle, state.RotationAngle) * wheelData.LocalOrientation); } } } From 623f981bc19621770ddce14be2abb1dd90b8eac1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 15:22:10 +0200 Subject: [PATCH 06/94] Fix car wheel location by applying compression of the suspension spring --- .../Engine/Physics/Actors/WheeledVehicle.cpp | 37 +++++++++++++++++-- Source/Engine/Physics/Actors/WheeledVehicle.h | 6 +++ Source/Engine/Physics/Physics.cpp | 16 +++++++- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 388aa1f21..2c21e8880 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -300,6 +300,7 @@ void WheeledVehicle::Setup() auto& data = _wheelsData[i]; data.Collider = wheel.Collider; data.LocalOrientation = wheel.Collider->GetLocalOrientation(); + data.ChildrenPoses.Resize(0); PxVehicleSuspensionData suspensionData; const float suspensionFrequency = 7.0f; @@ -507,11 +508,25 @@ void WheeledVehicle::Setup() void WheeledVehicle::DrawPhysicsDebug(RenderView& view) { // Wheels shapes - for (auto& wheel : _wheels) + for (auto& data : _wheelsData) { + int32 wheelIndex = 0; + for (; wheelIndex < _wheels.Count(); wheelIndex++) + { + if (_wheels[wheelIndex].Collider == data.Collider) + break; + } + if (wheelIndex == _wheels.Count()) + break; + auto& wheel = _wheels[wheelIndex]; if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) { - DEBUG_DRAW_WIRE_CYLINDER(wheel.Collider->GetPosition(), wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true); + const Vector3 basePos = wheel.Collider->GetPosition(); + const Vector3 currentPos = basePos + Vector3(0, data.State.SuspensionOffset, 0); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, true); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, true); + DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, true); + DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true); } } } @@ -519,11 +534,25 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view) void WheeledVehicle::OnDebugDrawSelected() { // Wheels shapes - for (auto& wheel : _wheels) + for (auto& data : _wheelsData) { + int32 wheelIndex = 0; + for (; wheelIndex < _wheels.Count(); wheelIndex++) + { + if (_wheels[wheelIndex].Collider == data.Collider) + break; + } + if (wheelIndex == _wheels.Count()) + break; + auto& wheel = _wheels[wheelIndex]; if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger()) { - DEBUG_DRAW_WIRE_CYLINDER(wheel.Collider->GetPosition(), wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false); + const Vector3 basePos = wheel.Collider->GetPosition(); + const Vector3 currentPos = basePos + Vector3(0, data.State.SuspensionOffset, 0); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, false); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false); + DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, false); + DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false); } } diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index a6a1708ad..12b5c864c 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -285,6 +285,12 @@ private: Collider* Collider; Quaternion LocalOrientation; WheelState State; + struct ChildPose + { + Actor* Child; + Vector3 Pose; + }; + Array> ChildrenPoses; }; void* _drive = nullptr; diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 8066f404d..07b9de909 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -847,10 +847,24 @@ void Physics::CollectResults() state.TireFriction = perWheel.tireFriction; state.SteerAngle = RadiansToDegrees * perWheel.steerAngle; state.RotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j); - const float suspensionOffsetDelta = perWheel.suspJounce - state.SuspensionOffset; state.SuspensionOffset = perWheel.suspJounce; + // Rotate wheel wheelData.Collider->SetLocalOrientation(Quaternion::Euler(0, state.SteerAngle, state.RotationAngle) * wheelData.LocalOrientation); + + // Apply suspension offset (cannot move collider because it breaks driving so move it's children but preserve the initial pose) + for (auto child : wheelData.Collider->Children) + { + int32 poseIndex = 0; + for (; poseIndex < wheelData.ChildrenPoses.Count(); poseIndex++) + { + if (wheelData.ChildrenPoses[poseIndex].Child == child) + break; + } + if (poseIndex == wheelData.ChildrenPoses.Count()) + wheelData.ChildrenPoses.Add({ child, child->GetLocalPosition() }); + child->SetPosition(wheelData.Collider->GetTransform().LocalToWorld(wheelData.ChildrenPoses[poseIndex].Pose) + Vector3(0, perWheel.suspJounce, 0)); + } } } } From 64813c1c709ed386f584da65f31273dbbf1b754c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 15:22:29 +0200 Subject: [PATCH 07/94] Add caching ShowGUI and ShowDebugDraw in Game window --- Source/Editor/Windows/GameWindow.cs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 26cef2621..101e845a4 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Xml; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.Options; @@ -484,5 +485,31 @@ namespace FlaxEditor.Windows return result; } + + /// + public override bool UseLayoutData => true; + + /// + public override void OnLayoutSerialize(XmlWriter writer) + { + writer.WriteAttributeString("ShowGUI", ShowGUI.ToString()); + writer.WriteAttributeString("ShowDebugDraw", ShowDebugDraw.ToString()); + } + + /// + public override void OnLayoutDeserialize(XmlElement node) + { + if (bool.TryParse(node.GetAttribute("ShowGUI"), out bool value1)) + ShowGUI = value1; + if (bool.TryParse(node.GetAttribute("ShowDebugDraw"), out value1)) + ShowDebugDraw = value1; + } + + /// + public override void OnLayoutDeserialize() + { + ShowGUI = true; + ShowDebugDraw = false; + } } } From e4bd5c725191b39e04d1c65cb1b4c715594bd824 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 15:24:39 +0200 Subject: [PATCH 08/94] Add `Engine.HasGameViewportFocus` to C# API --- Source/Engine/Engine/Engine.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h index 51860e5a2..403c7d575 100644 --- a/Source/Engine/Engine/Engine.h +++ b/Source/Engine/Engine/Engine.h @@ -156,8 +156,7 @@ public: /// /// Checks whenever the game viewport is focused by the user (eg. can receive input). /// - /// True if game viewport is focused, otherwise false. - static bool HasGameViewportFocus(); + API_PROPERTY() static bool HasGameViewportFocus(); private: From bd70e53806248c62e17e89e0940ad43d6069c643 Mon Sep 17 00:00:00 2001 From: Zbigniew Skowron Date: Mon, 9 Aug 2021 22:09:06 +0200 Subject: [PATCH 09/94] Fixed StringView::GetText() related issued pointed out in review. --- Source/Engine/Core/Types/Variant.h | 4 ++-- Source/Engine/Platform/Windows/WindowsClipboard.cpp | 9 ++++++--- Source/Engine/Platform/Windows/WindowsPlatform.cpp | 2 +- .../Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index 0c8f06f15..df8c4aeea 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -270,8 +270,8 @@ public: explicit operator float() const; explicit operator double() const; explicit operator void*() const; - explicit operator StringView() const; - explicit operator StringAnsiView() const; + explicit operator StringView() const; // Returned StringView, if not empty, is guaranteed to point to a null terminated buffer. + explicit operator StringAnsiView() const; // Returned StringView, if not empty, is guaranteed to point to a null terminated buffer. explicit operator ScriptingObject*() const; explicit operator struct _MonoObject*() const; explicit operator Asset*() const; diff --git a/Source/Engine/Platform/Windows/WindowsClipboard.cpp b/Source/Engine/Platform/Windows/WindowsClipboard.cpp index baba81e8f..056d187c6 100644 --- a/Source/Engine/Platform/Windows/WindowsClipboard.cpp +++ b/Source/Engine/Platform/Windows/WindowsClipboard.cpp @@ -23,9 +23,12 @@ void WindowsClipboard::Clear() void WindowsClipboard::SetText(const StringView& text) { - const int32 size = (text.Length() + 1) * sizeof(Char); - const HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, size); - Platform::MemoryCopy(GlobalLock(hMem), String(text).GetText(), size); + const int32 sizeWithoutNull = text.Length() * sizeof(Char); + const HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, sizeWithoutNull + sizeof(Char)); + + Char* pMem = static_cast(GlobalLock(hMem)); + Platform::MemoryCopy(pMem, text.GetNonTerminatedText(), sizeWithoutNull); + Platform::MemorySet(pMem + text.Length(), sizeof(Char), 0); GlobalUnlock(hMem); OpenClipboard(nullptr); diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 6849c0ac8..ccd514c11 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -1111,7 +1111,7 @@ int32 WindowsPlatform::RunProcess(const StringView& cmdLine, const StringView& w // Create the process PROCESS_INFORMATION procInfo; - if (!CreateProcessW(nullptr, const_cast(String(cmdLine).GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, String(workingDir).GetText(), &startupInfoEx.StartupInfo, &procInfo)) + if (!CreateProcessW(nullptr, const_cast(String(cmdLine).GetText()), nullptr, nullptr, TRUE, dwCreationFlags, (LPVOID)environmentStr, workingDir.HasChars() ? workingDir.Get() : nullptr, &startupInfoEx.StartupInfo, &procInfo)) { LOG(Warning, "Cannot start process '{0}'. Error code: 0x{1:x}", cmdLine, static_cast(GetLastError())); goto ERROR_EXIT; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index a071e161c..553cab3aa 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -166,7 +166,7 @@ namespace Flax.Build.Bindings if (typeInfo.Type == "String") return $"(StringView){value}"; if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char") - return $"((StringView){value}).GetNonTerminatedText()"; // This is a bug, as we need a null-terminated strig here. Any idea how to fix it? + 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") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") From 94b4c20b356e2df4f2a626072bdecb5914e1ef45 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 13:29:12 +0200 Subject: [PATCH 10/94] Add timeline view panning with right mouse button --- .../Editor/GUI/Timeline/GUI/BackgroundArea.cs | 95 +++++++++++++++++++ Source/Editor/GUI/Timeline/Timeline.cs | 71 +++++++------- 2 files changed, 132 insertions(+), 34 deletions(-) create mode 100644 Source/Editor/GUI/Timeline/GUI/BackgroundArea.cs diff --git a/Source/Editor/GUI/Timeline/GUI/BackgroundArea.cs b/Source/Editor/GUI/Timeline/GUI/BackgroundArea.cs new file mode 100644 index 000000000..a62fb5023 --- /dev/null +++ b/Source/Editor/GUI/Timeline/GUI/BackgroundArea.cs @@ -0,0 +1,95 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.GUI.Timeline.GUI +{ + /// + /// The timeline background area control. + /// + class BackgroundArea : Panel + { + private Timeline _timeline; + internal bool _rightMouseButtonDown; + private Vector2 _rightMouseButtonLastPos; + private float _rightMouseButtonMovement; + + public BackgroundArea(Timeline timeline) + : base(ScrollBars.Both) + { + _timeline = timeline; + } + + /// + public override bool OnMouseDown(Vector2 location, MouseButton button) + { + if (base.OnMouseDown(location, button)) + return true; + + if (button == MouseButton.Right) + { + _rightMouseButtonDown = true; + _rightMouseButtonLastPos = location; + _rightMouseButtonMovement = 0; + Focus(); + StartMouseCapture(); + return true; + } + + return false; + } + + /// + public override void OnMouseMove(Vector2 location) + { + // Panning timeline view with a right-mouse button + if (_rightMouseButtonDown) + { + var movePos = location + ViewOffset; + var delta = _rightMouseButtonLastPos - movePos; + _rightMouseButtonLastPos = movePos; + _rightMouseButtonMovement += delta.Length; + + var hScroll = HScrollBar.Visible && HScrollBar.Enabled; + var vScroll = VScrollBar.Visible && VScrollBar.Enabled; + if (vScroll && hScroll) + Cursor = CursorType.SizeAll; + else if (vScroll) + Cursor = CursorType.SizeNS; + else if (hScroll) + Cursor = CursorType.SizeWE; + + bool wasLocked = IsLayoutLocked; + IsLayoutLocked = true; + if (hScroll) + HScrollBar.TargetValue += delta.X; + if (vScroll) + VScrollBar.TargetValue += delta.Y; + IsLayoutLocked = wasLocked; + PerformLayout(); + return; + } + + base.OnMouseMove(location); + } + + /// + public override bool OnMouseUp(Vector2 location, MouseButton button) + { + if (button == MouseButton.Right && _rightMouseButtonDown) + { + EndMouseCapture(); + _rightMouseButtonDown = false; + Cursor = CursorType.Default; + if (_rightMouseButtonMovement < 1.0f) + { + _timeline.ShowContextMenu(PointToParent(_timeline, location)); + } + return true; + } + + return base.OnMouseUp(location, button); + } + } +} diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 3e3b303f4..5f7dbb912 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -288,7 +288,7 @@ namespace FlaxEditor.GUI.Timeline private TimeIntervalsHeader _timeIntervalsHeader; private ContainerControl _backgroundScroll; private Background _background; - private Panel _backgroundArea; + private BackgroundArea _backgroundArea; private TimelineEdge _leftEdge; private TimelineEdge _rightEdge; private Button _addTrackButton; @@ -305,6 +305,7 @@ namespace FlaxEditor.GUI.Timeline private PositionHandle _positionHandle; private bool _isRightMouseButtonDown; private Vector2 _rightMouseButtonDownPos; + private Vector2 _rightMouseButtonMovePos; private float _zoom = 1.0f; private bool _isMovingPositionHandle; private bool _canPlayPauseStop = true; @@ -912,7 +913,7 @@ namespace FlaxEditor.GUI.Timeline Offsets = new Margin(0, 0, 0, HeaderTopAreaHeight), Parent = _splitter.Panel2 }; - _backgroundArea = new Panel(ScrollBars.Both) + _backgroundArea = new BackgroundArea(this) { AutoFocus = false, ClipChildren = false, @@ -1954,6 +1955,38 @@ namespace FlaxEditor.GUI.Timeline } } + internal void ShowContextMenu(Vector2 location) + { + if (!ContainsFocus) + Focus(); + + var controlUnderMouse = GetChildAtRecursive(location); + var mediaUnderMouse = controlUnderMouse; + while (mediaUnderMouse != null && !(mediaUnderMouse is Media)) + { + mediaUnderMouse = mediaUnderMouse.Parent; + } + + var menu = new ContextMenu.ContextMenu(); + if (mediaUnderMouse is Media media) + { + media.OnTimelineShowContextMenu(menu, controlUnderMouse); + if (media.PropertiesEditObject != null) + { + menu.AddButton("Edit media", () => ShowEditPopup(media.PropertiesEditObject, ref location, media.Track)); + } + } + if (PropertiesEditObject != null) + { + menu.AddButton("Edit timeline", () => ShowEditPopup(PropertiesEditObject, ref location, this)); + } + menu.AddSeparator(); + menu.AddButton("Reset zoom", () => Zoom = 1.0f); + menu.AddButton("Show whole timeline", ShowWholeTimeline); + OnShowContextMenu(menu); + menu.Show(this, location); + } + /// protected override void PerformLayoutBeforeChildren() { @@ -1986,8 +2019,8 @@ namespace FlaxEditor.GUI.Timeline { _isRightMouseButtonDown = true; _rightMouseButtonDownPos = location; + _rightMouseButtonMovePos = location; Focus(); - return true; } @@ -2006,38 +2039,8 @@ namespace FlaxEditor.GUI.Timeline if (button == MouseButton.Right && _isRightMouseButtonDown) { _isRightMouseButtonDown = false; - if (Vector2.Distance(ref location, ref _rightMouseButtonDownPos) < 4.0f) - { - if (!ContainsFocus) - Focus(); - - var controlUnderMouse = GetChildAtRecursive(location); - var mediaUnderMouse = controlUnderMouse; - while (mediaUnderMouse != null && !(mediaUnderMouse is Media)) - { - mediaUnderMouse = mediaUnderMouse.Parent; - } - - var menu = new ContextMenu.ContextMenu(); - if (mediaUnderMouse is Media media) - { - media.OnTimelineShowContextMenu(menu, controlUnderMouse); - if (media.PropertiesEditObject != null) - { - menu.AddButton("Edit media", () => ShowEditPopup(media.PropertiesEditObject, ref location, media.Track)); - } - } - if (PropertiesEditObject != null) - { - menu.AddButton("Edit timeline", () => ShowEditPopup(PropertiesEditObject, ref location, this)); - } - menu.AddSeparator(); - menu.AddButton("Reset zoom", () => Zoom = 1.0f); - menu.AddButton("Show whole timeline", ShowWholeTimeline); - OnShowContextMenu(menu); - menu.Show(this, location); - } + ShowContextMenu(location); } return base.OnMouseUp(location, button); From c1b2fc19c162a06ba81b437860fe44a05d96bcc6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 13:29:42 +0200 Subject: [PATCH 11/94] Add better timeline UI positioning for improved viewport navigation --- Source/Editor/GUI/Timeline/GUI/PositionHandle.cs | 5 +++-- Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs | 3 ++- Source/Editor/GUI/Timeline/Timeline.cs | 16 ++++++++-------- Source/Engine/UI/GUI/Panels/ScrollBar.cs | 1 + 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs index 0e9cddc46..6cc2f12c4 100644 --- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs +++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs @@ -27,7 +27,8 @@ namespace FlaxEditor.GUI.Timeline.GUI { var style = Style.Current; var icon = Editor.Instance.Icons.VisjectArrowClosed32; - var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y; + var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f; + var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap; Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1); var m2 = Matrix3x3.Translation2D(0, timeAxisHeaderOffset); @@ -36,7 +37,7 @@ namespace FlaxEditor.GUI.Timeline.GUI Render2D.DrawSprite(icon, new Rectangle(new Vector2(4, -Width), Size), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground); Render2D.PopTransform(); - Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f)); + Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f)); base.Draw(); } diff --git a/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs b/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs index 0a7699cc5..83cb9760e 100644 --- a/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs +++ b/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs @@ -37,7 +37,8 @@ namespace FlaxEditor.GUI.Timeline.GUI public override void Draw() { var style = Style.Current; - var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y; + var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f; + var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap; var moveColor = style.ProgressNormal; var thickness = 2.0f; diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 5f7dbb912..6f7564f7a 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -959,12 +959,12 @@ namespace FlaxEditor.GUI.Timeline private void UpdatePositionHandle() { var handleWidth = 12.0f; - _positionHandle.Bounds = new Rectangle( - StartOffset * 2.0f - handleWidth * 0.5f + _currentFrame / _framesPerSecond * UnitsPerSecond * Zoom, - HeaderTopAreaHeight * -0.5f, - handleWidth, - HeaderTopAreaHeight * 0.5f - ); + var bounds = new Rectangle(); + bounds.Location.X = StartOffset * 2.0f - handleWidth * 0.5f + _currentFrame / _framesPerSecond * UnitsPerSecond * Zoom; + bounds.Location.Y = 0; + bounds.Size.X = handleWidth; + bounds.Size.Y = HeaderTopAreaHeight * 0.5f; + _positionHandle.Bounds = bounds; } private void OnFpsPopupShowing(ComboBox comboBox) @@ -1936,8 +1936,8 @@ namespace FlaxEditor.GUI.Timeline _background.Bounds = new Rectangle(StartOffset, 0, Duration * UnitsPerSecond * Zoom, height); var edgeWidth = 6.0f; - _leftEdge.Bounds = new Rectangle(_background.Left - edgeWidth * 0.5f + StartOffset, HeaderTopAreaHeight * -0.5f, edgeWidth, height + HeaderTopAreaHeight * 0.5f); - _rightEdge.Bounds = new Rectangle(_background.Right - edgeWidth * 0.5f + StartOffset, HeaderTopAreaHeight * -0.5f, edgeWidth, height + HeaderTopAreaHeight * 0.5f); + _leftEdge.Bounds = new Rectangle(_background.Left - edgeWidth * 0.5f + StartOffset, 0, edgeWidth, height); + _rightEdge.Bounds = new Rectangle(_background.Right - edgeWidth * 0.5f + StartOffset, 0, edgeWidth, height); _backgroundScroll.Bounds = new Rectangle(0, 0, _background.Width + 5 * StartOffset, height); } diff --git a/Source/Engine/UI/GUI/Panels/ScrollBar.cs b/Source/Engine/UI/GUI/Panels/ScrollBar.cs index bea692e19..0bae50591 100644 --- a/Source/Engine/UI/GUI/Panels/ScrollBar.cs +++ b/Source/Engine/UI/GUI/Panels/ScrollBar.cs @@ -140,6 +140,7 @@ namespace FlaxEngine.GUI { _targetValue = value; _value = value; + SetUpdate(ref _update, null); OnValueChanged(); } } From 62c43f9f95c012dadf9e6521c4254326cc8d760c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 13:54:22 +0200 Subject: [PATCH 12/94] Add helper tooltips for Scene Animation actions UI --- .../CustomEditors/Editors/FlaxObjectRefEditor.cs | 9 +-------- .../Editor/CustomEditors/Editors/TypeEditor.cs | 9 +-------- Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs | 2 +- Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs | 8 +++++--- Source/Editor/Surface/Archetypes/Packing.cs | 5 +---- Source/Editor/Surface/SurfaceUtils.cs | 16 ++++++++++++++++ Source/Editor/Utilities/Utils.cs | 12 ++++++++++++ Source/Editor/Windows/ToolboxWindow.cs | 9 +-------- 8 files changed, 38 insertions(+), 32 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs index 7412ca4dc..bad441d20 100644 --- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs @@ -96,14 +96,7 @@ namespace FlaxEditor.CustomEditors.Editors // Update tooltip if (_value is SceneObject sceneObject) { - var str = sceneObject is Actor actor ? actor.Name : type.Name; - var o = sceneObject.Parent; - while (o) - { - str = o.Name + " -> " + str; - o = o.Parent; - } - TooltipText = str; + TooltipText = Utilities.Utils.GetTooltip(sceneObject); } else { diff --git a/Source/Editor/CustomEditors/Editors/TypeEditor.cs b/Source/Editor/CustomEditors/Editors/TypeEditor.cs index 95b9d0f9d..8d32d6c3e 100644 --- a/Source/Editor/CustomEditors/Editors/TypeEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TypeEditor.cs @@ -76,14 +76,7 @@ namespace FlaxEditor.CustomEditors.Editors if (_value) { _valueName = _value.Name; - TooltipText = _value.TypeName; - - var attributes = _value.GetAttributes(false); - var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); - if (tooltipAttribute != null) - { - TooltipText += "\n" + tooltipAttribute.Text; - } + TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(_value); } else { diff --git a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs index 22dae35e0..8a4e4b4d2 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs @@ -103,7 +103,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks { if (selection.Count == 1 && selection[0] is ActorNode actorNode && actorNode.Actor && IsActorValid(actorNode.Actor)) { - menu.AddButton("Select " + actorNode.Actor, OnClickedSelectActor); + menu.AddButton("Select " + actorNode.Actor, OnClickedSelectActor).TooltipText = Utilities.Utils.GetTooltip(actorNode.Actor); } else { diff --git a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs index 2ed5070c5..e8ef8794b 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Text; using FlaxEditor.CustomEditors; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; @@ -197,8 +198,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks AddMemberTag tag; tag.Member = m; tag.Archetype = EventTrack.GetArchetype(); - - menu.AddButton(sb.ToString(), OnAddMemberTrack).Tag = tag; + var tooltip = Surface.SurfaceUtils.GetVisualScriptMemberInfoDescription(new ScriptMemberInfo(m)); + menu.AddButton(sb.ToString(), OnAddMemberTrack).LinkTooltip(tooltip).Tag = tag; count++; } @@ -295,7 +296,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks AddMemberTag tag; tag.Member = m; tag.Archetype = archetype; - menu.AddButton(name + " " + m.Name, OnAddMemberTrack).Tag = tag; + var tooltip = Surface.SurfaceUtils.GetVisualScriptMemberInfoDescription(new ScriptMemberInfo(m)); + menu.AddButton(name + " " + m.Name, OnAddMemberTrack).LinkTooltip(tooltip).Tag = tag; count++; } diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs index 83d321577..c3866b101 100644 --- a/Source/Editor/Surface/Archetypes/Packing.cs +++ b/Source/Editor/Surface/Archetypes/Packing.cs @@ -122,10 +122,7 @@ namespace FlaxEditor.Surface.Archetypes { GetBox(0).CurrentType = type; Title = (_isUnpacking ? "Unpack " : "Pack ") + type.Name; - var attributes = type.GetAttributes(false); - var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); - if (tooltipAttribute != null) - TooltipText += "\n" + tooltipAttribute.Text; + TooltipText = SurfaceUtils.GetVisualScriptTypeDescription(type); } else { diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index 74f1d6130..c86a5ae14 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -427,6 +427,22 @@ namespace FlaxEditor.Surface return !TypeUtils.IsDelegate(managedType); } + internal static string GetVisualScriptTypeDescription(ScriptType type) + { + var sb = new StringBuilder(); + if (type.IsStatic) + sb.Append("static "); + else if (type.IsAbstract) + sb.Append("abstract "); + sb.Append(type.TypeName); + + var attributes = type.GetAttributes(false); + var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); + if (tooltipAttribute != null) + sb.Append("\n").Append(tooltipAttribute.Text); + return sb.ToString(); + } + internal static string GetVisualScriptMemberInfoDescription(ScriptMemberInfo member) { var name = member.Name; diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 721b5a0bc..5364cf3b8 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -76,6 +76,18 @@ namespace FlaxEditor.Utilities return string.Format("{0:0.##} {1}", bytes, MemorySizePostfixes[order]); } + internal static string GetTooltip(SceneObject obj) + { + var str = obj is Actor actor ? actor.Name : TypeUtils.GetObjectType(obj).Name; + var o = obj.Parent; + while (o) + { + str = o.Name + " -> " + str; + o = o.Parent; + } + return str; + } + /// /// The colors for the keyframes used by the curve editor. /// diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 33827ea93..15d4a0948 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -204,14 +204,7 @@ namespace FlaxEditor.Windows continue; var item = _groupSearch.AddChild(CreateActorItem(CustomEditors.CustomEditorsUtil.GetPropertyNameUI(text), actorType)); - item.TooltipText = actorType.TypeName; - var attributes = actorType.GetAttributes(false); - var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute); - if (tooltipAttribute != null) - { - item.TooltipText += '\n'; - item.TooltipText += tooltipAttribute.Text; - } + item.TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(actorType); var highlights = new List(ranges.Length); var style = Style.Current; From 27f2856e6da07071d9e07e1243b075ccca0fafe1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 15:57:22 +0200 Subject: [PATCH 13/94] Optimize FindObject and TryFindObject in Object --- Source/Engine/Scripting/Scripting.cpp | 11 +++++++++-- Source/Engine/Scripting/Scripting.h | 8 ++++---- Source/Engine/Scripting/ScriptingObject.cpp | 22 +++++++++++++++++++-- Source/Engine/Scripting/ScriptingObject.h | 7 ++++++- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 99492c268..a986ded17 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -754,13 +754,20 @@ ScriptingObject* Scripting::FindObject(Guid id, MClass* type) if (result) { // Check type - if (result->Is(type)) + if (!type || result->Is(type)) return result; LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}.", id, String(result->GetType().Fullname), String(type->GetFullName())); return nullptr; } // Check if object can be an asset and try to load it + if (!type) + { + result = Content::LoadAsync(id, Asset::GetStaticClass()); + if (!result) + LOG(Warning, "Unable to find scripting object with ID={0}", id); + return result; + } if (type == ScriptingObject::GetStaticClass() || type->IsSubClassOf(Asset::GetStaticClass())) { Asset* asset = Content::LoadAsync(id, type); @@ -796,7 +803,7 @@ ScriptingObject* Scripting::TryFindObject(Guid id, MClass* type) #endif // Check type - if (result && !result->Is(type)) + if (result && type && !result->Is(type)) { result = nullptr; } diff --git a/Source/Engine/Scripting/Scripting.h b/Source/Engine/Scripting/Scripting.h index 19de2684e..52b532795 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -133,9 +133,9 @@ public: /// Finds the object by the given identifier. Searches registered scene objects and optionally assets. Logs warning if fails. /// /// The object unique identifier. - /// The type of the object to find. + /// The type of the object to find (optional). /// The found object or null if missing. - static ScriptingObject* FindObject(Guid id, MClass* type); + static ScriptingObject* FindObject(Guid id, MClass* type = nullptr); /// /// Tries to find the object by the given identifier. @@ -152,9 +152,9 @@ public: /// Tries to find the object by the given identifier. /// /// The object unique identifier. - /// The type of the object to find. + /// The type of the object to find (optional). /// The found object or null if missing. - static ScriptingObject* TryFindObject(Guid id, MClass* type); + static ScriptingObject* TryFindObject(Guid id, MClass* type = nullptr); /// /// Finds the object by the given managed instance handle. Searches only registered scene objects. diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index a66b8a103..34e5735cb 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -244,6 +244,20 @@ bool ScriptingObject::CanCast(MClass* from, MClass* to) return from->IsSubClassOf(to); } +bool ScriptingObject::CanCast(MClass* from, MonoClass* to) +{ + if (!from && !to) + return true; + CHECK_RETURN(from && to, false); + +#if PLATFORM_LINUX + // Cannot enter GC unsafe region if the thread is not attached + MCore::AttachThread(); +#endif + + return from->IsSubClassOf(to); +} + void ScriptingObject::OnDeleteObject() { // Cleanup managed object @@ -542,13 +556,17 @@ public: static MonoObject* FindObject(Guid* id, MonoReflectionType* type) { - ScriptingObject* obj = Scripting::FindObject(*id, Scripting::FindClass(MUtils::GetClass(type))); + ScriptingObject* obj = Scripting::FindObject(*id); + if (obj && !obj->Is(MUtils::GetClass(type))) + obj = nullptr; return obj ? obj->GetOrCreateManagedInstance() : nullptr; } static MonoObject* TryFindObject(Guid* id, MonoReflectionType* type) { - ScriptingObject* obj = Scripting::TryFindObject(*id, Scripting::FindClass(MUtils::GetClass(type))); + ScriptingObject* obj = Scripting::TryFindObject(*id); + if (obj && !obj->Is(MUtils::GetClass(type))) + obj = nullptr; return obj ? obj->GetOrCreateManagedInstance() : nullptr; } diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index 1d2365d64..01dd84232 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -62,7 +62,6 @@ public: /// /// Gets the managed instance object. /// - /// The Mono managed object or null if not created. MonoObject* GetManagedInstance() const; /// @@ -131,6 +130,7 @@ public: /// The destination class to the cast. /// True if can, otherwise false. static bool CanCast(MClass* from, MClass* to); + static bool CanCast(MClass* from, MonoClass* to); template static T* Cast(ScriptingObject* obj) @@ -145,6 +145,11 @@ public: return CanCast(GetClass(), type); } + bool Is(MonoClass* klass) const + { + return CanCast(GetClass(), klass); + } + template bool Is() const { From 31156433977453372890f5f98f76c74239d35dff Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 16:32:00 +0200 Subject: [PATCH 14/94] Add option to Scene context menu to unload it --- Source/Editor/SceneGraph/Actors/SceneNode.cs | 29 ++++++++++++++----- .../Windows/SceneTreeWindow.ContextMenu.cs | 11 ++++--- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/SceneNode.cs b/Source/Editor/SceneGraph/Actors/SceneNode.cs index 35c71bf02..2846715fc 100644 --- a/Source/Editor/SceneGraph/Actors/SceneNode.cs +++ b/Source/Editor/SceneGraph/Actors/SceneNode.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.Net; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph.GUI; using FlaxEngine; @@ -17,9 +19,6 @@ namespace FlaxEditor.SceneGraph.Actors /// /// Gets or sets a value indicating whether this scene is edited. /// - /// - /// true if this scene is edited; otherwise, false. - /// public bool IsEdited { get => _isEdited; @@ -28,7 +27,6 @@ namespace FlaxEditor.SceneGraph.Actors if (_isEdited != value) { _isEdited = value; - _treeNode.UpdateText(); } } @@ -37,9 +35,6 @@ namespace FlaxEditor.SceneGraph.Actors /// /// Gets the scene. /// - /// - /// The scene. - /// public Scene Scene => _actor as Scene; /// @@ -68,5 +63,25 @@ namespace FlaxEditor.SceneGraph.Actors /// public override SceneNode ParentScene => this; + + /// + public override void OnContextMenu(ContextMenu contextMenu) + { + contextMenu.AddSeparator(); + contextMenu.AddButton("Save scene", OnSave).LinkTooltip("Saves this scene.").Enabled = IsEdited && !Editor.IsPlayMode; + contextMenu.AddButton("Unload scene", OnUnload).LinkTooltip("Unloads this scene.").Enabled = Editor.Instance.StateMachine.CurrentState.CanChangeScene; + + base.OnContextMenu(contextMenu); + } + + private void OnSave() + { + Editor.Instance.Scene.SaveScene(this); + } + + private void OnUnload() + { + Editor.Instance.Scene.CloseScene(Scene); + } } } diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 50187ed3f..fe88a13a1 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -104,12 +104,11 @@ namespace FlaxEditor.Windows Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets; bool hasPrefabLink = canEditScene && isSingleActorSelected && (Editor.SceneEditing.Selection[0] as ActorNode).HasPrefabLink; - - b = contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab); - b.Enabled = hasPrefabLink; - - b = contextMenu.AddButton("Break Prefab Link", Editor.Prefabs.BreakLinks); - b.Enabled = hasPrefabLink; + if (hasPrefabLink) + { + contextMenu.AddButton("Select Prefab", Editor.Prefabs.SelectPrefab); + contextMenu.AddButton("Break Prefab Link", Editor.Prefabs.BreakLinks); + } // Spawning actors options From 9a522615e50d7bf708b39f5edd405442dd5dd16e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 16:32:24 +0200 Subject: [PATCH 15/94] Add support for changing scenes via Editor during play mode --- Source/Editor/Modules/SceneModule.cs | 23 +++++++++++++++++++++++ Source/Editor/States/PlayingState.cs | 3 +++ 2 files changed, 26 insertions(+) diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 33d41ddd6..3e6662f6c 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -251,6 +251,15 @@ namespace FlaxEditor.Modules if (!Editor.StateMachine.CurrentState.CanChangeScene) return; + // In play-mode Editor mocks the level streaming script + if (Editor.IsPlayMode) + { + if (!additive) + Level.UnloadAllScenesAsync(); + Level.LoadSceneAsync(sceneId); + return; + } + if (!additive) { // Ensure to save all pending changes @@ -272,6 +281,13 @@ namespace FlaxEditor.Modules if (!Editor.StateMachine.CurrentState.CanChangeScene) return; + // In play-mode Editor mocks the level streaming script + if (Editor.IsPlayMode) + { + Level.UnloadSceneAsync(scene); + return; + } + // Ensure to save all pending changes if (CheckSaveBeforeClose()) return; @@ -289,6 +305,13 @@ namespace FlaxEditor.Modules if (!Editor.StateMachine.CurrentState.CanChangeScene) return; + // In play-mode Editor mocks the level streaming script + if (Editor.IsPlayMode) + { + Level.UnloadAllScenesAsync(); + return; + } + // Ensure to save all pending changes if (CheckSaveBeforeClose()) return; diff --git a/Source/Editor/States/PlayingState.cs b/Source/Editor/States/PlayingState.cs index 171022745..6258975ae 100644 --- a/Source/Editor/States/PlayingState.cs +++ b/Source/Editor/States/PlayingState.cs @@ -28,6 +28,9 @@ namespace FlaxEditor.States /// public override bool CanEditScene => true; + /// + public override bool CanChangeScene => true; + /// public override bool CanUseUndoRedo => false; From 125d1aa08ce97dd8a6a6ba31b0faab5a04c8ddcf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 17:27:44 +0200 Subject: [PATCH 16/94] Add Zoom value box to timeline view context menu --- Source/Editor/GUI/Timeline/Timeline.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 6f7564f7a..258dbbe81 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -580,7 +580,7 @@ namespace FlaxEditor.GUI.Timeline get => _zoom; set { - value = Mathf.Clamp(value, 0.0001f, 1000.0f); + value = Mathf.Clamp(value, 0.00001f, 1000.0f); if (Mathf.NearEqual(_zoom, value)) return; @@ -1040,6 +1040,13 @@ namespace FlaxEditor.GUI.Timeline menu.AddButton("Show preview values", () => ShowPreviewValues = !ShowPreviewValues).Checked = ShowPreviewValues; + { + var zoom = menu.AddButton("Zoom"); + var zoomValue = new FloatValueBox(Zoom, 140, 2, 50.0f, 0.00001f, 1000.0f, 0.001f); + zoomValue.Parent = zoom; + zoomValue.ValueChanged += () => Zoom = zoomValue.Value; + } + OnShowViewContextMenu(menu); menu.Show(_viewButton.Parent, _viewButton.BottomLeft); From a497ef9d6fb629059c449910cd99bf755cd166ea Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 10 Aug 2021 17:35:27 +0200 Subject: [PATCH 17/94] Fix timeline background stops drawing to be ore readable #519 --- Source/Editor/GUI/Timeline/GUI/Background.cs | 40 ++++++++------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs index 6734b3ec9..8269fc29e 100644 --- a/Source/Editor/GUI/Timeline/GUI/Background.cs +++ b/Source/Editor/GUI/Timeline/GUI/Background.cs @@ -74,23 +74,21 @@ namespace FlaxEditor.GUI.Timeline.GUI } // Setup time axis ticks - int minDistanceBetweenTicks = 4000; - int maxDistanceBetweenTicks = 6000; + var minDistanceBetweenTicks = 50.0f; + var maxDistanceBetweenTicks = 100.0f; var zoom = Timeline.UnitsPerSecond * _timeline.Zoom; var left = Vector2.Min(leftSideMin, rightSideMax).X; var right = Vector2.Max(leftSideMin, rightSideMax).X; - var pixelRange = (right - left) * zoom; var leftFrame = Mathf.Floor((left - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond; var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond; var min = leftFrame; var max = rightFrame; - var range = max - min; int smallestTick = 0; int biggestTick = _tickSteps.Length - 1; for (int i = _tickSteps.Length - 1; i >= 0; i--) { // Calculate how far apart these modulo tick steps are spaced - float tickSpacing = _tickSteps[i] * pixelRange / range; + float tickSpacing = _tickSteps[i] * _timeline.Zoom; // Calculate the strength of the tick markers based on the spacing _tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks)); @@ -117,14 +115,16 @@ namespace FlaxEditor.GUI.Timeline.GUI // Draw all ticks int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1); - int startTick = Mathf.FloorToInt(min / _tickSteps[l]); - int endTick = Mathf.CeilToInt(max / _tickSteps[l]); + var lStep = _tickSteps[l]; + var lNextStep = _tickSteps[l + 1]; + int startTick = Mathf.FloorToInt(min / lStep); + int endTick = Mathf.CeilToInt(max / lStep); Color lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength); for (int i = startTick; i <= endTick; i++) { - if (l < biggestTick && (i % Mathf.RoundToInt(_tickSteps[l + 1] / _tickSteps[l]) == 0)) + if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0)) continue; - var tick = i * _tickSteps[l]; + var tick = i * lStep; var time = tick / _timeline.FramesPerSecond; var x = time * zoom + Timeline.StartOffset; @@ -163,15 +163,17 @@ namespace FlaxEditor.GUI.Timeline.GUI // Draw all ticks int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1); - int startTick = Mathf.FloorToInt(min / _tickSteps[l]); - int endTick = Mathf.CeilToInt(max / _tickSteps[l]); + var lStep = _tickSteps[l]; + var lNextStep = _tickSteps[l + 1]; + int startTick = Mathf.FloorToInt(min / lStep); + int endTick = Mathf.CeilToInt(max / lStep); Color lineColor = style.Foreground.RGBMultiplied(0.8f).AlphaMultiplied(strength); Color labelColor = style.ForegroundDisabled.AlphaMultiplied(strength); for (int i = startTick; i <= endTick; i++) { - if (l < biggestTick && (i % Mathf.RoundToInt(_tickSteps[l + 1] / _tickSteps[l]) == 0)) + if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0)) continue; - var tick = i * _tickSteps[l]; + var tick = i * lStep; var time = tick / _timeline.FramesPerSecond; var x = time * zoom + Timeline.StartOffset; @@ -195,17 +197,7 @@ namespace FlaxEditor.GUI.Timeline.GUI default: throw new ArgumentOutOfRangeException(); } var labelRect = new Rectangle(x + 2, -verticalLinesHeaderExtend + timeAxisHeaderOffset, 50, verticalLinesHeaderExtend); - Render2D.DrawText( - style.FontSmall, - labelText, - labelRect, - labelColor, - TextAlignment.Near, - TextAlignment.Center, - TextWrapping.NoWrap, - 1.0f, - 0.8f - ); + Render2D.DrawText(style.FontSmall, labelText, labelRect, labelColor, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.8f); } } } From 799c4a4f17636f8ff8eaa12828a8216ee73d0a07 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 9 Aug 2021 21:08:11 +0200 Subject: [PATCH 18/94] Remove whitespaces --- Source/Editor/Content/GUI/ContentView.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 23d81421d..2c50455ae 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -32,14 +32,14 @@ namespace FlaxEditor.Content.GUI /// public enum SortType { - /// + /// /// The classic alphabetic sort method (A-Z). /// AlphabeticOrder, - - /// + + /// /// The reverse alphabetic sort method (Z-A). - /// + /// AlphabeticReverse } @@ -272,18 +272,14 @@ namespace FlaxEditor.Content.GUI if (sortType == SortType.AlphabeticReverse) { if (control.CompareTo(control1) > 0) - { - return -1; - } + return -1; if (control.CompareTo(control1) == 0) - { - return 0; - } + return 0; return 1; } return control.CompareTo(control1); })); - + // Unload and perform UI layout IsLayoutLocked = wasLayoutLocked; PerformLayout(); From 622951c6bee3d327d8fd2b5895cdd699a3c5a282 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Aug 2021 18:55:33 +0200 Subject: [PATCH 19/94] Remove insecure `autoInitialize` option from scenes loading --- Source/Engine/Level/Level.cpp | 68 ++++++++++++----------------------- Source/Engine/Level/Level.h | 18 +++++----- 2 files changed, 30 insertions(+), 56 deletions(-) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 2659379a5..0aafe71d6 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -382,13 +382,11 @@ public: Guid SceneId; AssetReference SceneAsset; - bool AutoInitialize; - LoadSceneAction(const Guid& sceneId, JsonAsset* sceneAsset, bool autoInitialize) + LoadSceneAction(const Guid& sceneId, JsonAsset* sceneAsset) { SceneId = sceneId; SceneAsset = sceneAsset; - AutoInitialize = autoInitialize; } bool CanDo() const override @@ -410,7 +408,7 @@ public: } // Load scene - if (Level::loadScene(SceneAsset.Get(), AutoInitialize)) + if (Level::loadScene(SceneAsset.Get())) { LOG(Error, "Failed to deserialize scene {0}", SceneId); CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId); @@ -580,7 +578,7 @@ public: } // Load scene - if (Level::loadScene(document, false)) + if (Level::loadScene(document)) { LOG(Error, "Failed to deserialize scene {0}", scenes[i].Name); CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, scenes[i].ID); @@ -589,18 +587,6 @@ public: } scenes.Resize(0); - // Initialize scenes (will link references and create managed objects using new assembly) - if (Level::Scenes.HasItems()) - { - LOG(Info, "Prepare scene objects"); - SceneBeginData beginData; - for (auto scene : Level::Scenes) - { - scene->BeginPlay(&beginData); - } - beginData.OnDone(); - } - // Fire event LOG(Info, "Scripts reloading end. Total time: {0}ms", static_cast((DateTime::NowUTC() - startTime).GetTotalMilliseconds())); Level::ScriptsReloadEnd(); @@ -805,13 +791,13 @@ bool LevelImpl::unloadScenes() return false; } -bool Level::loadScene(const Guid& sceneId, bool autoInitialize) +bool Level::loadScene(const Guid& sceneId) { const auto sceneAsset = Content::LoadAsync(sceneId); - return loadScene(sceneAsset, autoInitialize); + return loadScene(sceneAsset); } -bool Level::loadScene(const String& scenePath, bool autoInitialize) +bool Level::loadScene(const String& scenePath) { LOG(Info, "Loading scene from file. Path: \'{0}\'", scenePath); @@ -830,10 +816,10 @@ bool Level::loadScene(const String& scenePath, bool autoInitialize) return true; } - return loadScene(sceneData, autoInitialize); + return loadScene(sceneData); } -bool Level::loadScene(JsonAsset* sceneAsset, bool autoInitialize) +bool Level::loadScene(JsonAsset* sceneAsset) { // Keep reference to the asset (prevent unloading during action) AssetReference ref = sceneAsset; @@ -845,10 +831,10 @@ bool Level::loadScene(JsonAsset* sceneAsset, bool autoInitialize) return true; } - return loadScene(*sceneAsset->Data, sceneAsset->DataEngineBuild, autoInitialize); + return loadScene(*sceneAsset->Data, sceneAsset->DataEngineBuild); } -bool Level::loadScene(const BytesContainer& sceneData, bool autoInitialize, Scene** outScene) +bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene) { if (sceneData.IsInvalid()) { @@ -868,10 +854,10 @@ bool Level::loadScene(const BytesContainer& sceneData, bool autoInitialize, Scen return true; } - return loadScene(document, autoInitialize, outScene); + return loadScene(document, outScene); } -bool Level::loadScene(rapidjson_flax::Document& document, bool autoInitialize, Scene** outScene) +bool Level::loadScene(rapidjson_flax::Document& document, Scene** outScene) { auto data = document.FindMember("Data"); if (data == document.MemberEnd()) @@ -880,10 +866,10 @@ bool Level::loadScene(rapidjson_flax::Document& document, bool autoInitialize, S return true; } const int32 saveEngineBuild = JsonTools::GetInt(document, "EngineBuild", 0); - return loadScene(data->value, saveEngineBuild, autoInitialize, outScene); + return loadScene(data->value, saveEngineBuild, outScene); } -bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, bool autoInitialize, Scene** outScene) +bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene) { PROFILE_CPU_NAMED("Level.LoadScene"); @@ -1051,19 +1037,9 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, bool autoI PROFILE_CPU_NAMED("BeginPlay"); Scenes.Add(scene); - - if (autoInitialize) - { - SceneBeginData beginData; - scene->BeginPlay(&beginData); - beginData.OnDone(); - } - else - { - // Send warning to log just in case (easier to track if scene will be loaded without init) - // Why? Because we can load collection of scenes and then call for all of them init so references between objects in a different scenes will be resolved without leaks. - //LOG(Warning, "Scene \'{0}:{1}\', has been loaded but not initialized. Remember to call OnBeginPlay().", scene->GetName(), scene->GetID()); - } + SceneBeginData beginData; + scene->BeginPlay(&beginData); + beginData.OnDone(); } // Fire event @@ -1245,7 +1221,7 @@ void Level::SaveAllScenesAsync() _sceneActions.Enqueue(New(Scenes[i])); } -bool Level::LoadScene(const Guid& id, bool autoInitialize) +bool Level::LoadScene(const Guid& id) { // Check ID if (!id.IsValid()) @@ -1281,7 +1257,7 @@ bool Level::LoadScene(const Guid& id, bool autoInitialize) } // Load scene - if (loadScene(sceneAsset, autoInitialize)) + if (loadScene(sceneAsset)) { LOG(Error, "Failed to deserialize scene {0}", id); CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, id); @@ -1290,10 +1266,10 @@ bool Level::LoadScene(const Guid& id, bool autoInitialize) return false; } -Scene* Level::LoadSceneFromBytes(const BytesContainer& data, bool autoInitialize) +Scene* Level::LoadSceneFromBytes(const BytesContainer& data) { Scene* scene = nullptr; - if (loadScene(data, autoInitialize, &scene)) + if (loadScene(data, &scene)) { LOG(Error, "Failed to deserialize scene from bytes"); CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, Guid::Empty); @@ -1319,7 +1295,7 @@ bool Level::LoadSceneAsync(const Guid& id) } ScopeLock lock(_sceneActionsLocker); - _sceneActions.Enqueue(New(id, sceneAsset, true)); + _sceneActions.Enqueue(New(id, sceneAsset)); return false; } diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index 1f7f0bb98..afd1553d1 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -278,17 +278,15 @@ public: /// Loads scene from the asset. /// /// Scene ID - /// Enable/disable auto scene initialization, otherwise user should do it (in that situation scene is registered but not in a gameplay, call OnBeginPlay to start logic for it; it will deserialize scripts and references to the other objects). /// True if loading cannot be done, otherwise false. - API_FUNCTION() static bool LoadScene(const Guid& id, bool autoInitialize = true); + API_FUNCTION() static bool LoadScene(const Guid& id); /// /// Loads scene from the bytes. /// /// The scene data to load. - /// Enable/disable auto scene initialization, otherwise user should do it (in that situation scene is registered but not in a gameplay, call OnBeginPlay to start logic for it; it will deserialize scripts and references to the other objects). /// Loaded scene object, otherwise null if cannot load data (then see log for more information). - API_FUNCTION() static Scene* LoadSceneFromBytes(const BytesContainer& data, bool autoInitialize = true); + API_FUNCTION() static Scene* LoadSceneFromBytes(const BytesContainer& data); /// /// Loads scene from the asset. Done in the background. @@ -479,10 +477,10 @@ private: }; static void callActorEvent(ActorEventType eventType, Actor* a, Actor* b); - static bool loadScene(const Guid& sceneId, bool autoInitialize); - static bool loadScene(const String& scenePath, bool autoInitialize); - static bool loadScene(JsonAsset* sceneAsset, bool autoInitialize); - static bool loadScene(const BytesContainer& sceneData, bool autoInitialize, Scene** outScene = nullptr); - static bool loadScene(rapidjson_flax::Document& document, bool autoInitialize, Scene** outScene = nullptr); - static bool loadScene(rapidjson_flax::Value& data, int32 engineBuild, bool autoInitialize, Scene** outScene = nullptr); + static bool loadScene(const Guid& sceneId); + static bool loadScene(const String& scenePath); + static bool loadScene(JsonAsset* sceneAsset); + static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr); + static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr); + static bool loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr); }; From d65a85e30ceacc8887c2b4e387153ccad1573c61 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Aug 2021 19:28:53 +0200 Subject: [PATCH 20/94] Add check to prevent double-free from native event in C# --- .../Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 0f0a5c726..05fdff485 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -665,7 +665,11 @@ namespace Flax.Build.Bindings indent += " "; var eventInstance = eventInfo.IsStatic ? string.Empty : "__unmanagedPtr, "; contents.Append(indent).Append($"add {{ Internal_{eventInfo.Name} += value; if (Internal_{eventInfo.Name}_Count++ == 0) Internal_{eventInfo.Name}_Bind({eventInstance}true); }}").AppendLine(); - contents.Append(indent).Append($"remove {{ Internal_{eventInfo.Name} -= value; if (--Internal_{eventInfo.Name}_Count == 0) Internal_{eventInfo.Name}_Bind({eventInstance}false); }}").AppendLine(); + contents.Append(indent).Append("remove { ").AppendLine(); + contents.Append("#if FLAX_EDITOR || BUILD_DEBUG").AppendLine(); + contents.Append(indent).Append($"if (Internal_{eventInfo.Name} != null) {{ bool invalid = true; foreach (Delegate e in Internal_{eventInfo.Name}.GetInvocationList()) {{ if (e == (Delegate)value) {{ invalid = false; break; }} }} if (invalid) throw new Exception(\"Cannot unregister from event if not registered before.\"); }}").AppendLine(); + contents.Append("#endif").AppendLine(); + contents.Append(indent).Append($"Internal_{eventInfo.Name} -= value; if (--Internal_{eventInfo.Name}_Count == 0) Internal_{eventInfo.Name}_Bind({eventInstance}false); }}").AppendLine(); indent = indent.Substring(0, indent.Length - 4); contents.Append(indent).Append('}').AppendLine(); From 921e71671561f16112eb5b0b44daa9f274c756b8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Aug 2021 19:38:26 +0200 Subject: [PATCH 21/94] Add safe lock --- Source/Engine/Level/Level.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 0aafe71d6..5d8c32ea1 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -1036,6 +1036,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou { PROFILE_CPU_NAMED("BeginPlay"); + ScopeLock lock(ScenesLock); Scenes.Add(scene); SceneBeginData beginData; scene->BeginPlay(&beginData); From 916c2733f08c5892c959f939e3c02a2f5657554a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Aug 2021 14:58:56 +0200 Subject: [PATCH 22/94] Fix some problems with LocalizedString serialization in C# --- Source/Engine/Serialization/JsonConverters.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index 3130b597f..3da6342e2 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -244,16 +244,21 @@ namespace FlaxEngine.Json /// public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { +#if FLAX_EDITOR + bool writeTypename = (serializer.TypeNameHandling & TypeNameHandling.Objects) == TypeNameHandling.Objects; +#else + bool writeTypename = false; +#endif var str = (LocalizedString)value; - if (string.IsNullOrEmpty(str.Id)) + if (string.IsNullOrEmpty(str.Id) && !writeTypename) { - writer.WriteValue(str.Value); + writer.WriteValue(str.Value ?? string.Empty); } else { writer.WriteStartObject(); #if FLAX_EDITOR - if ((serializer.TypeNameHandling & TypeNameHandling.Objects) == TypeNameHandling.Objects) + if (writeTypename) { writer.WritePropertyName("$type"); writer.WriteValue("FlaxEngine.LocalizedString, FlaxEngine.CSharp"); From 3cf3f58db17352fa4118b7e617f50787c4d5045e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 11:47:38 +0200 Subject: [PATCH 23/94] Fix UI with list of null items when layout gets rebuilt at item level --- Source/Editor/CustomEditors/CustomEditor.cs | 16 +++++++--------- .../CustomEditors/Editors/CollectionEditor.cs | 12 ++++++++++-- .../CustomEditors/Editors/DictionaryEditor.cs | 9 ++++++++- .../CustomEditors/Editors/GenericEditor.cs | 19 ++++++++++++------- .../CustomEditors/LayoutElementsContainer.cs | 7 +++++++ .../CustomEditors/Values/ValueContainer.cs | 16 ++++++++++++++++ 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index 07cc1c08d..e681127fd 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -101,11 +101,6 @@ namespace FlaxEditor.CustomEditors /// protected bool IsSetBlocked => _isSetBlocked; - /// - /// Gets a value indicating whether this editor needs value propagation up (value synchronization when one of the child editors changes value, used by the struct types). - /// - protected virtual bool NeedsValuePropagationUp => Values.HasValueType; - /// /// The linked label used to show this custom editor. Can be null if not used (eg. editor is inlined or is using a very customized UI layout). /// @@ -281,7 +276,7 @@ namespace FlaxEditor.CustomEditors // Propagate values up (eg. when member of structure gets modified, also structure should be updated as a part of the other object) var obj = _parent; - while (obj._parent != null && !(obj._parent is SyncPointEditor)) // && obj.NeedsValuePropagationUp) + while (obj._parent != null && !(obj._parent is SyncPointEditor)) { obj.Values.Set(obj._parent.Values, obj.Values); obj = obj._parent; @@ -301,12 +296,15 @@ namespace FlaxEditor.CustomEditors _isSetBlocked = false; // Update children + if (_skipChildrenRefresh) + { + _skipChildrenRefresh = false; + return; + } try { - var childrenCount = _skipChildrenRefresh ? 0 : _children.Count; - for (int i = 0; i < childrenCount; i++) + for (int i = 0; i < _children.Count; i++) _children[i].RefreshInternal(); - _skipChildrenRefresh = false; } catch (TargetException ex) { diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 6c3dda04e..9f660c328 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -154,6 +154,10 @@ namespace FlaxEditor.CustomEditors.Editors var panel = layout.VerticalPanel(); panel.Panel.BackgroundColor = _background; var elementType = ElementType; + + // Use separate layout cells for each collection items to improve layout updates for them in separation + var useSharedLayout = elementType.IsPrimitive || elementType.IsEnum; + if (_canReorderItems) { for (int i = 0; i < size; i++) @@ -178,7 +182,9 @@ namespace FlaxEditor.CustomEditors.Editors } var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - panel.Object(new CollectionItemLabel(this, i), new ListValueContainer(elementType, i, Values), overrideEditor); + var property = panel.AddPropertyItem(new CollectionItemLabel(this, i)); + var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); + itemLayout.Object(new ListValueContainer(elementType, i, Values), overrideEditor); } } else @@ -194,7 +200,9 @@ namespace FlaxEditor.CustomEditors.Editors } var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - panel.Object("Element " + i, new ListValueContainer(elementType, i, Values), overrideEditor); + var property = panel.AddPropertyItem("Element " + i); + var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); + itemLayout.Object(new ListValueContainer(elementType, i, Values), overrideEditor); } } } diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs index 51309a2fa..53e539234 100644 --- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs @@ -216,6 +216,11 @@ namespace FlaxEditor.CustomEditors.Editors panel.Panel.BackgroundColor = _background; var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType(); var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray(); + var valuesType = new ScriptType(valueType); + + // Use separate layout cells for each collection items to improve layout updates for them in separation + var useSharedLayout = valueType.IsPrimitive || valueType.IsEnum; + for (int i = 0; i < size; i++) { if (i != 0 && spacing > 0f) @@ -239,7 +244,9 @@ namespace FlaxEditor.CustomEditors.Editors var key = keys.ElementAt(i); var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - panel.Object(new DictionaryItemLabel(this, key), new DictionaryValueContainer(new ScriptType(valueType), key, Values), overrideEditor); + var property = panel.AddPropertyItem(new DictionaryItemLabel(this, key)); + var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); + itemLayout.Object(new DictionaryValueContainer(valuesType, key, Values), overrideEditor); } } _elementsCount = size; diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index fc1b3b047..8510fa9ff 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -228,6 +228,7 @@ namespace FlaxEditor.CustomEditors.Editors } private VisibleIfCache[] _visibleIfCaches; + private bool _isNull; /// /// Gets the items for the type @@ -264,7 +265,7 @@ namespace FlaxEditor.CustomEditors.Editors // Skip properties without getter or setter if (!p.HasGet || (!p.HasSet && !showInEditor)) continue; - + // Skip hidden fields, handle special attributes if ((!p.IsPublic && !showInEditor) || attributes.Any(x => x is HideInEditorAttribute)) continue; @@ -421,7 +422,9 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { + var values = Values; _visibleIfCaches = null; + _isNull = values != null && values.IsNull; // Collect items to edit List items; @@ -446,12 +449,7 @@ namespace FlaxEditor.CustomEditors.Editors Parent = layout.ContainerControl, Location = new Vector2(layout.ContainerControl.Width - ButtonSize - 4, (layout.ContainerControl.Height - ButtonSize) * 0.5f), }; - button.Clicked += () => - { - var newType = Values.Type; - SetValue(newType.CreateInstance()); - RebuildLayoutOnRefresh(); - }; + button.Clicked += () => SetValue(Values.Type.CreateInstance()); } layout.Label(""); @@ -558,6 +556,13 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Refresh() { + // Automatic refresh when value nullability changed + if (_isNull != Values.IsNull) + { + RebuildLayout(); + return; + } + if (_visibleIfCaches != null) { try diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index d7df2fbb1..b63d9f6b9 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -30,6 +30,11 @@ namespace FlaxEditor.CustomEditors /// public readonly List Children = new List(); + /// + /// The child custom editors. + /// + public readonly List Editors = new List(); + /// /// Gets the control represented by this element. /// @@ -722,6 +727,7 @@ namespace FlaxEditor.CustomEditors var customEditor = CustomEditor.CurrentCustomEditor; Assert.IsNotNull(customEditor); customEditor.OnChildCreated(editor); + Editors.Add(editor); } /// @@ -730,6 +736,7 @@ namespace FlaxEditor.CustomEditors public virtual void ClearLayout() { Children.Clear(); + Editors.Clear(); } /// diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index 9bdbc4bcf..1252c9179 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -101,6 +101,22 @@ namespace FlaxEditor.CustomEditors } } + /// + /// Gets a value indicating whether all values in the collection are null. Returns true if collection is empty. + /// + public bool IsNull + { + get + { + for (int i = 0; i < Count; i++) + { + if (this[i] != null) + return false; + } + return true; + } + } + /// /// Gets a value indicating whether this any value in the collection is of value type (eg. a structure, not a class type). Returns false if collection is empty. /// From 70c729b17141dd11a57b89d5d5d491b474400b38 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 11:48:08 +0200 Subject: [PATCH 24/94] Fix LocalizedString undo bug --- Source/Engine/Serialization/JsonConverters.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index 3da6342e2..b0c43f9a5 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -212,6 +212,7 @@ namespace FlaxEngine.Json break; } case JsonToken.Comment: break; + case JsonToken.String: break; default: return value; } } @@ -302,10 +303,15 @@ namespace FlaxEngine.Json break; } case JsonToken.Comment: break; + case JsonToken.String: break; default: return str; } } } + else + return null; + if (existingValue == null && str.Id == null && str.Value == null) + return null; return str; } From 299d0493b92469818cbb31950aa41164b4acc6ce Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 11:48:36 +0200 Subject: [PATCH 25/94] Optimize single undo edit action to not use wrapper --- Source/Editor/Undo/Undo.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Undo/Undo.cs b/Source/Editor/Undo/Undo.cs index 709e21a32..80a5d69a3 100644 --- a/Source/Editor/Undo/Undo.cs +++ b/Source/Editor/Undo/Undo.cs @@ -221,16 +221,20 @@ namespace FlaxEditor var snapshotInstances = (object[])snapshotInstance; if (snapshotInstances == null || snapshotInstances.Length != SnapshotInstances.Length) throw new ArgumentException("Invalid multi undo action objects."); - var actions = new List(); + List actions = null; for (int i = 0; i < snapshotInstances.Length; i++) { var diff = Snapshot[i].Compare(snapshotInstances[i]); if (diff.Count == 0) continue; + if (actions == null) + actions = new List(); actions.Add(new UndoActionObject(diff, ActionString, SnapshotInstances[i])); } - if (actions.Count == 0) + if (actions == null) return null; + if (actions.Count == 1) + return actions[0]; return new MultiUndoAction(actions); } } From 98c43dba42e72846147482480961fcb1c1547de1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 12:51:20 +0200 Subject: [PATCH 26/94] Fix window focus issue when removing actors --- Source/Editor/Modules/SceneEditingModule.cs | 4 ++++ Source/Editor/Windows/PropertiesWindow.cs | 1 + 2 files changed, 5 insertions(+) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 6b2a7090d..255247671 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -383,6 +383,7 @@ namespace FlaxEditor.Modules var objects = Selection.Where(x => x.CanDelete).ToList().BuildAllNodes().Where(x => x.CanDelete).ToList(); if (objects.Count == 0) return; + var isSceneTreeFocus = Editor.Windows.SceneWin.ContainsFocus; SelectionDeleteBegin?.Invoke(); @@ -404,6 +405,9 @@ namespace FlaxEditor.Modules SelectionDeleteEnd?.Invoke(); OnDirty(objects); + + if (isSceneTreeFocus) + Editor.Windows.SceneWin.Focus(); } /// diff --git a/Source/Editor/Windows/PropertiesWindow.cs b/Source/Editor/Windows/PropertiesWindow.cs index 70f58dcc6..f96385536 100644 --- a/Source/Editor/Windows/PropertiesWindow.cs +++ b/Source/Editor/Windows/PropertiesWindow.cs @@ -29,6 +29,7 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.Vertical) { Title = "Properties"; + AutoFocus = true; Presenter = new CustomEditorPresenter(editor.Undo); Presenter.Panel.Parent = this; From 3aecbd75a35478e1eb21d4672f2d7769d32b228d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Thu, 12 Aug 2021 13:48:45 +0200 Subject: [PATCH 27/94] Remove unused include. --- Source/Engine/Core/Math/Math.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Engine/Core/Math/Math.h b/Source/Engine/Core/Math/Math.h index e9bd6e58f..b184ca5ad 100644 --- a/Source/Engine/Core/Math/Math.h +++ b/Source/Engine/Core/Math/Math.h @@ -2,7 +2,6 @@ #pragma once -#include "Engine/Core/Core.h" #include "Engine/Core/Types/BaseTypes.h" #include From 328027300dfdd8ba2415c707fa629cc716d0003d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Thu, 12 Aug 2021 13:49:21 +0200 Subject: [PATCH 28/94] Add Mathd implementation. --- Source/Engine/Core/Math/Mathd.h | 362 ++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 Source/Engine/Core/Math/Mathd.h diff --git a/Source/Engine/Core/Math/Mathd.h b/Source/Engine/Core/Math/Mathd.h new file mode 100644 index 000000000..8da6f0330 --- /dev/null +++ b/Source/Engine/Core/Math/Mathd.h @@ -0,0 +1,362 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include +#include "Engine/Core/Types/BaseTypes.h" + +namespace Mathd +{ + /// + /// Computes the sine and cosine of a scalar float. + /// + /// The input angle (in radians). + /// The output sine value. + /// The output cosine value. + FLAXENGINE_API void SinCos(double angle, double& sine, double& cosine); + + static FORCE_INLINE double Trunc(double value) + { + return trunc(value); + } + + static FORCE_INLINE double Round(double value) + { + return round(value); + } + + static FORCE_INLINE double Floor(double value) + { + return floor(value); + } + + static FORCE_INLINE double Ceil(double value) + { + return ceil(value); + } + + static FORCE_INLINE double Sin(double value) + { + return sin(value); + } + + static FORCE_INLINE double Asin(double value) + { + return asin(value < -1. ? -1. : value < 1. ? value : 1.); + } + + static FORCE_INLINE double Sinh(double value) + { + return sinh(value); + } + + static FORCE_INLINE double Cos(double value) + { + return cos(value); + } + + static FORCE_INLINE double Acos(double value) + { + return acos(value < -1. ? -1. : value < 1. ? value : 1.); + } + + static FORCE_INLINE double Tan(double value) + { + return tan(value); + } + + static FORCE_INLINE double Atan(double value) + { + return atan(value); + } + + static FORCE_INLINE double Atan2(double y, double x) + { + return atan2(y, x); + } + + static FORCE_INLINE double InvSqrt(double value) + { + return 1.0f / sqrt(value); + } + + static FORCE_INLINE double Log(const double value) + { + return log(value); + } + + static FORCE_INLINE double Log2(const double value) + { + return log2(value); + } + + static FORCE_INLINE double Log10(const double value) + { + return log10(value); + } + + static FORCE_INLINE double Pow(const double base, const double exponent) + { + return pow(base, exponent); + } + + static FORCE_INLINE double Sqrt(const double value) + { + return sqrt(value); + } + + static FORCE_INLINE double Exp(const double value) + { + return exp(value); + } + + static FORCE_INLINE double Exp2(const double value) + { + return exp2(value); + } + + static FORCE_INLINE double Abs(const double value) + { + return fabs(value); + } + + static FORCE_INLINE double Mod(const double a, const double b) + { + return fmod(a, b); + } + + static FORCE_INLINE double ModF(double a, double* b) + { + return modf(a, b); + } + + /// + /// Returns signed fractional part of a double. + /// + /// Double point value to convert. + /// A double between [0 ; 1) for nonnegative input. A double between [-1; 0) for negative input. + static FORCE_INLINE double Fractional(double value) + { + return value - Trunc(value); + } + + static int64 TruncToInt(double value) + { + return (int64)value; + } + + static int64 FloorToInt(double value) + { + return TruncToInt(floor(value)); + } + + static FORCE_INLINE int64 RoundToInt(double value) + { + return FloorToInt(value + 0.5); + } + + static FORCE_INLINE int64 CeilToInt(double value) + { + return TruncToInt(ceil(value)); + } + + // Performs smooth (cubic Hermite) interpolation between 0 and 1 + // @param amount Value between 0 and 1 indicating interpolation amount + static double SmoothStep(double amount) + { + return amount <= 0. ? 0. : amount >= 1. ? 1. : amount * amount * (3. - 2. * amount); + } + + // Performs a smooth(er) interpolation between 0 and 1 with 1st and 2nd order derivatives of zero at endpoints + // @param amount Value between 0 and 1 indicating interpolation amount + static double SmootherStep(double amount) + { + return amount <= 0. ? 0. : amount >= 1. ? 1. : amount * amount * amount * (amount * (amount * 6. - 15.) + 10.); + } + + // Determines whether the specified value is close to zero (0.0) + // @param a The floating value + // @returns True if the specified value is close to zero (0.0). otherwise false + inline bool IsZero(double a) + { + return Abs(a) < ZeroTolerance; + } + + // Determines whether the specified value is close to one (1.0f) + // @param a The floating value + // @returns True if the specified value is close to one (1.0f). otherwise false + inline bool IsOne(double a) + { + return IsZero(a - 1.); + } + + // Returns a value indicating the sign of a number + // @returns A number that indicates the sign of value + inline double Sign(double v) + { + return v > 0. ? 1. : v < 0. ? -1. : 0.; + } + + /// + /// Compares the sign of two floating-point values. + /// + /// The first value. + /// The second value. + /// True if given values have the same sign (both positive or negative); otherwise false. + inline bool SameSign(const double a, const double b) + { + return a * b >= 0.; + } + + /// + /// Compares the sign of two floating-point values. + /// + /// The first value. + /// The second value. + /// True if given values don't have the same sign (first is positive and second is negative or vice versa); otherwise false. + inline bool NotSameSign(const double a, const double b) + { + return a * b < 0.; + } + + /// + /// Checks if a and b are not even almost equal, taking into account the magnitude of floating point numbers + /// + /// The left value to compare + /// The right value to compare + /// False if a almost equal to b, otherwise true + static bool NotNearEqual(double a, double b) + { + // Check if the numbers are really close - needed when comparing numbers near zero + if (IsZero(a - b)) + return false; + + // Original from Bruce Dawson: http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + const int64 aInt = *(int64*)&a; + const int64 bInt = *(int64*)&b; + + // Different signs means they do not match + if (aInt < 0 != bInt < 0) + return true; + + // Find the difference in ULPs + const int64 ulp = Math::Abs(aInt - bInt); + + // Choose of maxUlp = 4 + // according to http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h + const int maxUlp = 4; + return ulp > maxUlp; + } + + + /// + /// Checks if a and b are almost equals within the given epsilon value. + /// + /// The left value to compare. + /// The right value to compare. + /// The comparision epsilon value. Should be 1e-4 or less. + /// True if a almost equal to b, otherwise false + static bool NearEqual(double a, double b, double eps) + { + return Abs(a - b) < eps; + } + + /// + /// Remaps the specified value from the specified range to another. + /// + /// The value to remap. + /// The source range minimum. + /// The source range maximum. + /// The destination range minimum. + /// The destination range maximum. + /// The remapped value. + static double Remap(double value, double fromMin, double fromMax, double toMin, double toMax) + { + return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin; + } + + static double ClampAxis(double angle) + { + angle = Mod(angle, 360.); + if (angle < 0.) + angle += 360.; + return angle; + } + + static double NormalizeAxis(double angle) + { + angle = ClampAxis(angle); + if (angle > 180.) + angle -= 360.; + return angle; + } + + // Find the smallest angle between two headings (in radians). + static double FindDeltaAngle(double a1, double a2) + { + double delta = a2 - a1; + if (delta > PI) + delta = delta - TWO_PI; + else if (delta < -PI) + delta = delta + TWO_PI; + return delta; + } + + // Given a heading which may be outside the +/- PI range, 'unwind' it back into that range + static double UnwindRadians(double a) + { + while (a > PI) + a -= TWO_PI; + while (a < -PI) + a += TWO_PI; + return a; + } + + // Utility to ensure angle is between +/- 180 degrees by unwinding + static double UnwindDegrees(double a) + { + while (a > 180.) + a -= 360.; + while (a < -180.) + a += 360.; + return a; + } + + /// + /// Returns value based on comparand. The main purpose of this function is to avoid branching based on floating point comparison which can be avoided via compiler intrinsics. + /// + /// + /// Please note that this doesn't define what happens in the case of NaNs as there might be platform specific differences. + /// + /// Comparand the results are based on. + /// The result value if comparand >= 0. + /// The result value if comparand < 0. + /// the valueGEZero if comparand >= 0, valueLTZero otherwise + static double DoubleSelect(double comparand, double valueGEZero, double valueLTZero) + { + return comparand >= 0. ? valueGEZero : valueLTZero; + } + + /// + /// Returns a smooth Hermite interpolation between 0 and 1 for the value X (where X ranges between A and B). Clamped to 0 for X <= A and 1 for X >= B. + /// + /// The minimum value of x. + /// The maximum value of x. + /// The x. + /// The smoothed value between 0 and 1. + static double SmoothStep(double a, double b, double x) + { + if (x < a) + return 0.; + if (x >= b) + return 1.; + const double fraction = (x - a) / (b - a); + return fraction * fraction * (3. - 2. * fraction); + } + +//TODO: When double vectors are implemented + // Rotates position about the given axis by the given angle, in radians, and returns the offset to position + //Vector3 FLAXENGINE_API RotateAboutAxis(const Vector3& normalizedRotationAxis, float angle, const Vector3& positionOnAxis, const Vector3& position); + + //Vector3 FLAXENGINE_API ExtractLargestComponent(const Vector3& v); +} From 2799136ccb4f44492e3c8b5518b38822d05b4216 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Thu, 12 Aug 2021 13:49:47 +0200 Subject: [PATCH 29/94] Remove Double operation from Math.h --- Source/Engine/Core/Math/Math.h | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Source/Engine/Core/Math/Math.h b/Source/Engine/Core/Math/Math.h index b184ca5ad..462f71678 100644 --- a/Source/Engine/Core/Math/Math.h +++ b/Source/Engine/Core/Math/Math.h @@ -140,11 +140,6 @@ namespace Math return exp2f(value); } - static FORCE_INLINE double Abs(const double value) - { - return fabs(value); - } - static FORCE_INLINE float Abs(const float value) { return fabsf(value); @@ -160,11 +155,6 @@ namespace Math return value < 0 ? -value : value; } - static FORCE_INLINE double Mod(const double a, const double b) - { - return fmod(a, b); - } - static FORCE_INLINE float Mod(const float a, const float b) { return fmodf(a, b); @@ -434,14 +424,6 @@ namespace Math return amount <= 0 ? 0 : amount >= 1 ? 1 : amount * amount * amount * (amount * (amount * 6 - 15) + 10); } - // Determines whether the specified value is close to zero (0.0) - // @param a The floating value - // @returns True if the specified value is close to zero (0.0). otherwise false - inline bool IsZero(double a) - { - return Abs(a) < 1e-7; - } - // Determines whether the specified value is close to zero (0.0f) // @param a The floating value // @returns True if the specified value is close to zero (0.0f). otherwise false From b91800ad9de517f26a925be4fab67ec28ca5402e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Thu, 12 Aug 2021 13:50:09 +0200 Subject: [PATCH 30/94] Use Mathd for double operation in Variant --- Source/Engine/Core/Types/Variant.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 4b3cbd658..af3118858 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -6,6 +6,7 @@ #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Content/Asset.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/Mathd.h" #include "Engine/Core/Math/BoundingBox.h" #include "Engine/Core/Math/BoundingSphere.h" #include "Engine/Core/Math/Vector2.h" @@ -950,7 +951,7 @@ bool Variant::operator==(const Variant& other) const case VariantType::Float: return Math::NearEqual(AsFloat, other.AsFloat); case VariantType::Double: - return Math::Abs(AsDouble - other.AsDouble) < ZeroTolerance; + return Mathd::Abs(AsDouble - other.AsDouble) < ZeroTolerance; case VariantType::Pointer: return AsPointer == other.AsPointer; case VariantType::String: @@ -1103,7 +1104,7 @@ Variant::operator bool() const case VariantType::Float: return !Math::IsZero(AsFloat); case VariantType::Double: - return !Math::IsZero(AsDouble); + return !Mathd::IsZero(AsDouble); case VariantType::Pointer: return AsPointer != nullptr; case VariantType::String: @@ -2850,7 +2851,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) switch (to.Type) { case VariantType::Bool: - return Variant(Math::Abs(v.AsDouble) > ZeroTolerance); + return Variant(Mathd::Abs(v.AsDouble) > ZeroTolerance); case VariantType::Int16: return Variant((int16)v.AsDouble); case VariantType::Int: From 69aac09be8ca47cba66446015f4a61eabf89da36 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Thu, 12 Aug 2021 13:50:34 +0200 Subject: [PATCH 31/94] Add Mathd.cs implementation --- Source/Engine/Core/Math/Mathd.cs | 1111 ++++++++++++++++++++++++++++++ 1 file changed, 1111 insertions(+) create mode 100644 Source/Engine/Core/Math/Mathd.cs diff --git a/Source/Engine/Core/Math/Mathd.cs b/Source/Engine/Core/Math/Mathd.cs new file mode 100644 index 000000000..42a65e023 --- /dev/null +++ b/Source/Engine/Core/Math/Mathd.cs @@ -0,0 +1,1111 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.ComponentModel; + +namespace FlaxEngine +{ + /// + /// A collection of common math functions on double floating-points. + /// + [HideInEditor] + public static class Mathd + { + /// + /// The value for which all absolute numbers smaller than are considered equal to zero. + /// + public const double Epsilon = 1e-7f; + + /// + /// A value specifying the approximation of π which is 180 degrees. + /// + public const double Pi = Math.PI; + + /// + /// A value specifying the approximation of 2π which is 360 degrees. + /// + public const double TwoPi = 2.0 * Math.PI; + + /// + /// A value specifying the approximation of π/2 which is 90 degrees. + /// + public const double PiOverTwo = Math.PI / 2.0; + + /// + /// A value specifying the approximation of π/4 which is 45 degrees. + /// + public const double PiOverFour = Math.PI / 4.0; + + /// + /// A value specifying the golden mean + /// + public const double GoldenRatio = 1.6180339887; + + /// + /// Returns the absolute value of f. + /// + /// + public static double Abs(double f) + { + return Math.Abs(f); + } + + /// + /// Returns the arc-cosine of f - the angle in radians whose cosine is f. + /// + /// + public static double Acos(double f) + { + return Math.Acos(f); + } + + /// + /// Compares two floating point values if they are similar. + /// + /// + /// + public static bool Approximately(double a, double b) + { + return Abs(b - a) < Max(Epsilon * Max(Abs(a), Abs(b)), Epsilon * 8f); + } + + /// + /// Returns the arc-sine of f - the angle in radians whose sine is f. + /// + /// + public static double Asin(double f) + { + return Math.Asin(f); + } + + /// + /// Returns the arc-tangent of f - the angle in radians whose tangent is f. + /// + /// + public static double Atan(double f) + { + return Math.Atan(f); + } + + /// + /// Returns the angle in radians whose Tan is y/x. + /// + /// + /// + public static double Atan2(double y, double x) + { + return Math.Atan2(y, x); + } + + /// + /// Returns the smallest integer greater to or equal to f. + /// + /// + public static double Ceil(double f) + { + return Math.Ceiling(f); + } + + /// + /// Returns the smallest integer greater to or equal to f. + /// + /// + public static long CeilToInt(double f) + { + return (long)Math.Ceiling(f); + } + + /// + /// Clamps value between 0 and 1 and returns value. + /// + /// Value to clamp + /// Result value + public static double Saturate(double value) + { + if (value < 0d) + return 0d; + return value > 1d ? 1d : value; + } + + /// + /// Returns the cosine of angle f in radians. + /// + /// + public static double Cos(double f) + { + return Math.Cos(f); + } + + /// + /// Calculates the shortest difference between two given angles given in degrees. + /// + /// + /// + public static double DeltaAngle(double current, double target) + { + double t = Repeat(target - current, 360f); + if (t > 180d) + t -= 360d; + return t; + } + + /// + /// Returns e raised to the specified power. + /// + /// + public static double Exp(double power) + { + return Math.Exp(power); + } + + /// + /// Returns the largest integer smaller to or equal to f. + /// + /// + public static double Floor(double f) + { + return Math.Floor(f); + } + + /// + /// Returns the largest integer smaller to or equal to f. + /// + /// + public static long FloorToInt(double f) + { + return (long)Math.Floor(f); + } + + /// + /// Remaps the specified value from the specified range to another. + /// + /// The value to remap. + /// The source range minimum. + /// The source range maximum. + /// The destination range minimum. + /// The destination range maximum. + /// The remapped value. + public static double Remap(double value, double fromMin, double fromMax, double toMin, double toMax) + { + return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin; + } + + /// + /// Calculates the linear parameter t that produces the interpolation value within the range [a, b]. + /// + /// + /// + /// + public static double InverseLerp(double a, double b, double value) + { + if (a == b) + return 0d; + return Saturate((value - a) / (b - a)); + } + + /// + /// Same as Lerp but makes sure the values interpolate correctly when they wrap around 360 degrees. + /// + /// + /// + /// + public static double LerpAngle(double a, double b, double t) + { + double c = Repeat(b - a, 360d); + if (c > 180d) + c -= 360d; + return a + c * Saturate(t); + } + + /// + /// Returns the logarithm of a specified number in a specified base. + /// + /// + /// + public static double Log(double f, double p) + { + return Math.Log(f, p); + } + + /// + /// Returns the natural (base e) logarithm of a specified number. + /// + /// + public static double Log(double f) + { + return Math.Log(f); + } + + /// + /// Returns the base 10 logarithm of a specified number. + /// + /// + public static double Log10(double f) + { + return Math.Log10(f); + } + + /// + /// Returns largest of two or more values. + /// + /// + /// + public static double Max(double a, double b) + { + return a <= b ? b : a; + } + + + /// + /// Returns largest of two or more values. + /// + /// + public static double Max(params double[] values) + { + int length = values.Length; + if (length == 0) + return 0d; + + double t = values[0]; + for (var i = 1; i < length; i++) + if (values[i] > t) + t = values[i]; + return t; + } + + + /// + /// Returns the smallest of two or more values. + /// + /// + /// + public static double Min(double a, double b) + { + return a >= b ? b : a; + } + + + /// + /// Returns the smallest of two or more values. + /// + /// + public static double Min(params double[] values) + { + int length = values.Length; + if (length == 0) + return 0d; + + double t = values[0]; + for (var i = 1; i < length; i++) + if (values[i] < t) + t = values[i]; + + return t; + } + + /// + /// Moves a value current towards target. + /// + /// The current value. + /// The value to move towards. + /// The maximum change that should be applied to the value. + public static double MoveTowards(double current, double target, double maxDelta) + { + if (Abs(target - current) <= maxDelta) + return target; + return current + Sign(target - current) * maxDelta; + } + + /// + /// Same as MoveTowards but makes sure the values interpolate correctly when they wrap around 360 degrees. + /// + /// + /// + /// + public static double MoveTowardsAngle(double current, double target, double maxDelta) + { + double delta = DeltaAngle(current, target); + if ((-maxDelta < delta) && (delta < maxDelta)) + return target; + target = current + delta; + return MoveTowards(current, target, maxDelta); + } + + /// + /// PingPongs the value t, so that it is never larger than length and never smaller than 0. + /// + /// + /// + public static double PingPong(double t, double length) + { + t = Repeat(t, length * 2f); + return length - Abs(t - length); + } + + /// + /// Returns f raised to power p. + /// + /// + /// + public static double Pow(double f, double p) + { + return Math.Pow(f, p); + } + + /// + /// Loops the value t, so that it is never larger than length and never smaller than 0. + /// + /// + /// + public static double Repeat(double t, double length) + { + return t - Floor(t / length) * length; + } + + /// + /// Returns f rounded to the nearest integer. + /// + /// + public static double Round(double f) + { + return Math.Round(f); + } + + /// + /// Returns f rounded to the nearest integer. + /// + /// + public static int RoundToInt(double f) + { + return (int)Math.Round(f); + } + + /// + /// Returns f rounded to the nearest integer. + /// + /// + public static long RoundToLong(double f) + { + return (long)Math.Round(f); + } + + /// + /// Returns the sign of f. + /// + /// + public static double Sign(double f) + { + return f < 0d ? -1d : 1d; + } + + /// + /// Returns the sine of angle f in radians. + /// + /// + public static double Sin(double f) + { + return Math.Sin(f); + } + + /// + /// Gradually changes a value towards a desired goal over time with smoothing. + /// + /// The current value. + /// The target value. + /// The current velocity. + /// The smoothing time. Smaller values increase blending time. + /// The maximum speed. + /// The smoothed value. + public static double SmoothDamp(double current, double target, ref double currentVelocity, double smoothTime, double maxSpeed) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, maxSpeed, Time.DeltaTime); + } + + /// + /// Gradually changes a value towards a desired goal over time with smoothing. + /// + /// The current value. + /// The target value. + /// The current velocity. + /// The smoothing time. Smaller values increase blending time. + /// The smoothed value. + public static double SmoothDamp(double current, double target, ref double currentVelocity, double smoothTime) + { + return SmoothDamp(current, target, ref currentVelocity, smoothTime, double.PositiveInfinity, Time.DeltaTime); + } + + /// + /// Gradually changes a value towards a desired goal over time with smoothing. + /// + /// The current value. + /// The target value. + /// The current velocity. + /// The smoothing time. Smaller values increase blending time. + /// The maximum speed. + /// The delta time (in seconds) since last update. + /// The smoothed value. + public static double SmoothDamp(double current, double target, ref double currentVelocity, double smoothTime, [DefaultValue("double.PositiveInfinity")] + double maxSpeed, [DefaultValue("Time.DeltaTime")] double deltaTime) + { + smoothTime = Max(0.0001d, smoothTime); + double a = 2d / smoothTime; + double b = a * deltaTime; + double c = 1d / (1d + b + 0.48d * b * b + 0.235d * b * b * b); + double d = current - target; + double e = target; + double f = maxSpeed * smoothTime; + d = Clamp(d, -f, f); + target = current - d; + double g = (currentVelocity + a * d) * deltaTime; + currentVelocity = (currentVelocity - a * g) * c; + double h = target + (d + g) * c; + if (e - current > 0d == h > e) + { + h = e; + currentVelocity = (h - e) / deltaTime; + } + return h; + } + + /// + /// Gradually changes an angle towards a desired goal over time with smoothing. + /// + /// The current angle. + /// The target angle. + /// The current velocity. + /// The smoothing time. Smaller values increase blending time. + /// The maximum speed. + /// The smoothed value. + public static double SmoothDampAngle(double current, double target, ref double currentVelocity, double smoothTime, double maxSpeed) + { + return SmoothDampAngle(current, target, ref currentVelocity, smoothTime, maxSpeed, Time.DeltaTime); + } + + /// + /// Gradually changes an angle towards a desired goal over time with smoothing. + /// + /// The current angle. + /// The target angle. + /// The current velocity. + /// The smoothing time. Smaller values increase blending time. + /// The smoothed value. + public static double SmoothDampAngle(double current, double target, ref double currentVelocity, double smoothTime) + { + return SmoothDampAngle(current, target, ref currentVelocity, smoothTime, double.PositiveInfinity, Time.DeltaTime); + } + + /// + /// Gradually changes an angle towards a desired goal over time with smoothing. + /// + /// The current angle. + /// The target angle. + /// The current velocity. + /// The smoothing time. Smaller values increase blending time. + /// The maximum speed. + /// The delta time (in seconds) since last update. + /// The smoothed value. + public static double SmoothDampAngle(double current, double target, ref double currentVelocity, double smoothTime, [DefaultValue("double.PositiveInfinity")] + double maxSpeed, [DefaultValue("Time.DeltaTime")] double deltaTime) + { + target = current + DeltaAngle(current, target); + return SmoothDamp(current, target, ref currentVelocity, smoothTime, maxSpeed, deltaTime); + } + + /// + /// Interpolates between min and max with smoothing at the limits. + /// + /// + /// + /// + public static double SmoothStep(double from, double to, double t) + { + t = Saturate(t); + t = -2d * t * t * t + 3d * t * t; + return to * t + from * (1d - t); + } + + /// + /// Performs a cubic interpolation. + /// + /// The first point. + /// The tangent direction at first point. + /// The second point. + /// The tangent direction at second point. + /// The distance along the spline. + /// The interpolated value. + public static double CubicInterp(double p0, double t0, double p1, double t1, double alpha) + { + double alpha2 = alpha * alpha; + double alpha3 = alpha2 * alpha; + return (((2d * alpha3) - (3d * alpha2) + 1d) * p0) + ((alpha3 - (2d * alpha2) + alpha) * t0) + ((alpha3 - alpha2) * t1) + (((-2d * alpha3) + (3d * alpha2)) * p1); + } + + /// + /// Interpolate between A and B, applying an ease in function. Exponent controls the degree of the curve. + /// + public static double InterpEaseIn(double a, double b, double alpha, double exponent) + { + double modifiedAlpha = Pow(alpha, exponent); + return Lerp(a, b, modifiedAlpha); + } + + /// + /// Interpolate between A and B, applying an ease out function. Exponent controls the degree of the curve. + /// + public static double InterpEaseOut(double a, double b, double alpha, double exponent) + { + double modifiedAlpha = 1d - Pow(1d - alpha, exponent); + return Lerp(a, b, modifiedAlpha); + } + + /// + /// Interpolate between A and B, applying an ease in/out function. Exponent controls the degree of the curve. + /// + public static double InterpEaseInOut(double a, double b, double alpha, double exponent) + { + return Lerp(a, b, (alpha < 0.5d) ? InterpEaseIn(0d, 1d, alpha * 2d, exponent) * 0.5d : InterpEaseOut(0d, 1d, alpha * 2d - 1d, exponent) * 0.5d + 0.5d); + } + + /// + /// Interpolation between A and B, applying a sinusoidal in function. + /// + public static double InterpSinIn(double a, double b, double alpha) + { + double modifiedAlpha = -1d * Cos(alpha * PiOverTwo) + 1d; + return Lerp(a, b, modifiedAlpha); + } + + /// + /// Interpolation between A and B, applying a sinusoidal out function. + /// + public static double InterpSinOut(double a, double b, double alpha) + { + double modifiedAlpha = Sin(alpha * PiOverTwo); + return Lerp(a, b, modifiedAlpha); + } + + /// + /// Interpolation between A and B, applying a sinusoidal in/out function. + /// + public static double InterpSinInOut(double a, double b, double alpha) + { + return Lerp(a, b, (alpha < 0.5d) ? InterpSinIn(0d, 1d, alpha * 2d) * 0.5d : InterpSinOut(0d, 1d, alpha * 2d - 1d) * 0.5d + 0.5d); + } + + /// + /// Interpolation between A and B, applying an exponential in function. + /// + public static double InterpExpoIn(double a, double b, double alpha) + { + double modifiedAlpha = (alpha == 0d) ? 0d : Pow(2d, 10d * (alpha - 1d)); + return Lerp(a, b, modifiedAlpha); + } + + /// + /// Interpolation between A and B, applying an exponential out function. + /// + public static double InterpExpoOut(double a, double b, double alpha) + { + double modifiedAlpha = (alpha == 1d) ? 1d : -Pow(2d, -10d * alpha) + 1d; + return Lerp(a, b, modifiedAlpha); + } + + /// + /// Interpolation between A and B, applying an exponential in/out function. + /// + public static double InterpExpoInOut(double a, double b, double alpha) + { + return Lerp(a, b, (alpha < 0.5d) ? InterpExpoIn(0d, 1d, alpha * 2d) * 0.5d : InterpExpoOut(0d, 1d, alpha * 2d - 1d) * 0.5d + 0.5d); + } + + /// + /// Interpolation between A and B, applying a circular in function. + /// + public static double InterpCircularIn(double a, double b, double alpha) + { + double modifiedAlpha = -1d * (Sqrt(1d - alpha * alpha) - 1d); + return Lerp(a, b, modifiedAlpha); + } + + /// + /// Interpolation between A and B, applying a circular out function. + /// + public static double InterpCircularOut(double a, double b, double alpha) + { + alpha -= 1d; + double modifiedAlpha = Sqrt(1d - alpha * alpha); + return Lerp(a, b, modifiedAlpha); + } + + /// + /// Interpolation between A and B, applying a circular in/out function. + /// + public static double InterpCircularInOut(double a, double b, double alpha) + { + return Lerp(a, b, (alpha < 0.5d) ? InterpCircularIn(0d, 1d, alpha * 2d) * 0.5d : InterpCircularOut(0d, 1d, alpha * 2d - 1d) * 0.5d + 0.5d); + } + + /// + /// Maps the specified value from the given range into another. + /// + /// The value to map from range [fromMin; fromMax]. + /// The source range minimum value. + /// The source range maximum value. + /// The destination range minimum value. + /// The destination range maximum value. + /// The mapped value in range [toMin; toMax]. + public static double Map(double value, double fromMin, double fromMax, double toMin, double toMax) + { + double t = (value - fromMin) / (fromMax - fromMin); + return toMin + t * (toMax - toMin); + } + + /// + /// Get the next power of two for a size. + /// + /// The size. + /// System.Int32. + public static double NextPowerOfTwo(double size) + { + return Math.Pow(2d, Math.Ceiling(Math.Log(size, 2d))); + } + + /// + /// Converts a float value from sRGB to linear. + /// + /// The sRGB value. + /// A linear value. + public static double SRgbToLinear(double sRgbValue) + { + if (sRgbValue < 0.04045d) + return sRgbValue / 12.92d; + return Math.Pow((sRgbValue + 0.055d) / 1.055d, 2.4d); + } + + /// + /// Converts a float value from linear to sRGB. + /// + /// The linear value. + /// The encoded sRGB value. + public static double LinearToSRgb(double linearValue) + { + if (linearValue < 0.0031308d) + return linearValue * 12.92d; + return 1.055d * Math.Pow(linearValue, 1d / 2.4d) - 0.055d; + } + + /// + /// Returns square root of f. + /// + /// + public static double Sqrt(double f) + { + return Math.Sqrt(f); + } + + /// + /// Returns square of the given value. + /// + /// The value. + /// The value * value. + public static double Square(double f) + { + return f * f; + } + + /// + /// Returns the tangent of angle f in radians. + /// + /// + public static double Tan(double f) + { + return Math.Tan(f); + } + + /// + /// Checks if a and b are almost equals, taking into account the magnitude of floating point numbers (unlike + /// method). See Remarks. + /// See remarks. + /// + /// The left value to compare. + /// The right value to compare. + /// true if a almost equal to b, false otherwise + /// + /// The code is using the technique described by Bruce Dawson in + /// + /// Comparing + /// Floating point numbers 2012 edition + /// + /// . + /// + public static unsafe bool NearEqual(double a, double b) + { + // Check if the numbers are really close -- needed + // when comparing numbers near zero. + if (IsZero(a - b)) + return true; + + // Original from Bruce Dawson: http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + long aInt = *(long*)&a; + long bInt = *(long*)&b; + + // Different signs means they do not match. + if (aInt < 0 != bInt < 0) + return false; + + // Find the difference in ULPs. + long ulp = Math.Abs(aInt - bInt); + + // Choose of maxUlp = 4 + // according to http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h + const long maxUlp = 4; + return ulp <= maxUlp; + } + + /// + /// Determines whether the specified value is close to zero (0.0f). + /// + /// The floating value. + /// true if the specified value is close to zero (0.0f); otherwise, false. + public static bool IsZero(double a) + { + return Math.Abs(a) < Epsilon; + } + + /// + /// Determines whether the specified value is close to one (1.0f). + /// + /// The floating value. + /// true if the specified value is close to one (1.0f); otherwise, false. + public static bool IsOne(double a) + { + return IsZero(a - 1d); + } + + /// + /// Checks if a - b are almost equals within a float epsilon. + /// + /// The left value to compare. + /// The right value to compare. + /// Epsilon value + /// true if a almost equal to b within a float epsilon, false otherwise + public static bool WithinEpsilon(double a, double b, double epsilon) + { + double num = a - b; + return (-epsilon <= num) && (num <= epsilon); + } + + /// + /// Determines whether the specified value is in a given range [min; max]. + /// + /// The value. + /// The minimum. + /// The maximum. + /// + /// true if the specified value is in a given range; otherwise, false. + /// + public static bool IsInRange(double value, double min, double max) + { + return value >= min && value <= max; + } + + /// + /// Determines whether the specified value is NOT in a given range [min; max]. + /// + /// The value. + /// The minimum. + /// The maximum. + /// + /// true if the specified value is NOT in a given range; otherwise, false. + /// + public static bool IsNotInRange(double value, double min, double max) + { + return value < min || value > max; + } + + #region Angle units conversions + + /// + /// Converts revolutions to degrees. + /// + public static double RevolutionsToDegrees = 360d; + + /// + /// Converts revolutions to radians. + /// + public static double RevolutionsToRadians = TwoPi; + + /// + /// Converts revolutions to gradians. + /// + public static double RevolutionsToGradians = 400d; + + /// + /// Converts degrees to revolutions. + /// + public static double DegreesToRevolutions = (1d / 360d); + + /// + /// Converts degrees to radians. + /// + public static double DegreesToRadians = (Pi / 180d); + + /// + /// Converts radians to revolutions. + /// + public static double RadiansToRevolutions = (1d / TwoPi); + + /// + /// Converts radians to gradians. + /// + public static double RadiansToGradians = (200d / Pi); + + /// + /// Converts gradians to revolutions. + /// + public static double GradiansToRevolutions = (1d / 400d); + + /// + /// Converts gradians to degrees. + /// + public static double GradiansToDegrees = (9.0f / 10d); + + /// + /// Converts gradians to radians. + /// + public static double GradiansToRadians = (Pi / 200d); + + /// + /// Converts radians to degrees. + /// + public static double RadiansToDegrees = (180d / Pi); + + #endregion + + /// + /// Given a heading which may be outside the +/- PI range, 'unwind' it back into that range. + /// + /// Angle in radians to unwind. + /// Valid angle in radians. + public static double UnwindRadians(double angle) + { + // TODO: make it faster? + + while (angle > Pi) + { + angle -= TwoPi; + } + + while (angle < -Pi) + { + angle += TwoPi; + } + + return angle; + } + + /// + /// Utility to ensure angle is between +/- 180 degrees by unwinding + /// + /// Angle in degrees to unwind. + /// Valid angle in degrees. + public static double UnwindDegrees(double angle) + { + // TODO: make it faster? + + while (angle > 180.0f) + { + angle -= 360.0f; + } + + while (angle < -180.0f) + { + angle += 360.0f; + } + + return angle; + } + + /// + /// Clamps the specified value. + /// + /// The value. + /// The min. + /// The max. + /// The result of clamping a value between min and max + public static double Clamp(double value, double min, double max) + { + return value < min ? min : value > max ? max : value; + } + + /// + /// Interpolates between two values using a linear function by a given amount. + /// + /// + /// See http://www.encyclopediaofmath.org/index.php/Linear_interpolation and + /// http://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/ + /// + /// Value to interpolate from. + /// Value to interpolate to. + /// Interpolation amount. + /// The result of linear interpolation of values based on the amount. + public static double Lerp(double from, double to, double amount) + { + return from + (to - from) * amount; + } + + + /// + /// Performs smooth (cubic Hermite) interpolation between 0 and 1. + /// + /// + /// See https://en.wikipedia.org/wiki/Smoothstep + /// + /// Value between 0 and 1 indicating interpolation amount. + public static double SmoothStep(double amount) + { + return amount <= 0d ? 0d + : amount >= 1d ? 1d + : amount * amount * (3d - 2d * amount); + } + + /// + /// Performs a smooth(er) interpolation between 0 and 1 with 1st and 2nd order derivatives of zero at endpoints. + /// + /// + /// See https://en.wikipedia.org/wiki/Smoothstep + /// + /// Value between 0 and 1 indicating interpolation amount. + public static double SmootherStep(double amount) + { + return amount <= 0d ? 0d + : amount >= 1d ? 1d + : amount * amount * amount * (amount * (amount * 6d - 15d) + 10d); + } + + /// + /// Calculates the modulo of the specified value. + /// + /// The value. + /// The modulo. + /// The result of the modulo applied to value + public static double Mod(double value, double modulo) + { + if (modulo == 0d) + return value; + + return value % modulo; + } + + /// + /// Calculates the modulo 2*PI of the specified value. + /// + /// The value. + /// The result of the modulo applied to value + public static double Mod2PI(double value) + { + return Mod(value, TwoPi); + } + + /// + /// Wraps the specified value into a range [min, max] + /// + /// The value. + /// The min. + /// The max. + /// Result of the wrapping. + /// Is thrown when is greater than . + public static double Wrap(double value, double min, double max) + { + if (NearEqual(min, max)) + return min; + + double mind = min; + double maxd = max; + double valued = value; + + if (mind > maxd) + throw new ArgumentException(string.Format("min {0} should be less than or equal to max {1}", min, max), nameof(min)); + + double rangeSize = maxd - mind; + return mind + (valued - mind) - rangeSize * Math.Floor((valued - mind) / rangeSize); + } + + /// + /// Gauss function. + /// http://en.wikipedia.org/wiki/Gaussian_function#Two-dimensional_Gaussian_function + /// + /// Curve amplitude. + /// Position X. + /// Position Y + /// Center X. + /// Center Y. + /// Curve sigma X. + /// Curve sigma Y. + /// The result of Gaussian function. + public static double Gauss(double amplitude, double x, double y, double centerX, double centerY, double sigmaX, double sigmaY) + { + double cx = x - centerX; + double cy = y - centerY; + + double componentX = cx * cx / (2 * sigmaX * sigmaX); + double componentY = cy * cy / (2 * sigmaY * sigmaY); + + return amplitude * Math.Exp(-(componentX + componentY)); + } + + /// + /// Converts the input alpha value from a linear 0-1 value into the output alpha described by blend mode. + /// + /// The alpha (normalized to 0-1). + /// The mode. + /// The output alpha (normalized to 0-1). + public static double InterpolateAlphaBlend(double alpha, AlphaBlendMode mode) + { + switch (mode) + { + case AlphaBlendMode.Sinusoidal: + alpha = (Sin(alpha * Pi - PiOverTwo) + 1d) / 2d; + break; + case AlphaBlendMode.Cubic: + alpha = CubicInterp(0d, 0d, 1d, 0d, alpha); + break; + case AlphaBlendMode.QuadraticInOut: + alpha = InterpEaseInOut(0d, 1d, alpha, 2d); + break; + case AlphaBlendMode.CubicInOut: + alpha = InterpEaseInOut(0d, 1d, alpha, 3d); + break; + case AlphaBlendMode.HermiteCubic: + alpha = SmoothStep(0d, 1d, alpha); + break; + case AlphaBlendMode.QuarticInOut: + alpha = InterpEaseInOut(0d, 1d, alpha, 4d); + break; + case AlphaBlendMode.QuinticInOut: + alpha = InterpEaseInOut(0d, 1d, alpha, 5d); + break; + case AlphaBlendMode.CircularIn: + alpha = InterpCircularIn(0d, 1d, alpha); + break; + case AlphaBlendMode.CircularOut: + alpha = InterpCircularOut(0d, 1d, alpha); + break; + case AlphaBlendMode.CircularInOut: + alpha = InterpCircularInOut(0d, 1d, alpha); + break; + case AlphaBlendMode.ExpIn: + alpha = InterpExpoIn(0d, 1d, alpha); + break; + case AlphaBlendMode.ExpOut: + alpha = InterpExpoOut(0d, 1d, alpha); + break; + case AlphaBlendMode.ExpInOut: + alpha = InterpExpoInOut(0d, 1d, alpha); + break; + } + + return Saturate(alpha); + } + } +} From 2c30f48900ea9ee88bef33da563852c2b2cc37a3 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Thu, 12 Aug 2021 13:51:06 +0200 Subject: [PATCH 32/94] Tweak --- Source/Engine/Core/Math/Mathf.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Core/Math/Mathf.cs b/Source/Engine/Core/Math/Mathf.cs index 93e0ff8a4..488c46777 100644 --- a/Source/Engine/Core/Math/Mathf.cs +++ b/Source/Engine/Core/Math/Mathf.cs @@ -6,7 +6,7 @@ using System.ComponentModel; namespace FlaxEngine { /// - /// A collection of common math functions. + /// A collection of common math functions on single floating-points. /// [HideInEditor] public static class Mathf From c3b9f61b9faef5957fb5d2b26f26e94991f09922 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 13:59:30 +0200 Subject: [PATCH 33/94] Fix snap to the ground to use scene graph query instead of physics only raycast --- Source/Editor/Gizmo/GizmoBase.cs | 7 +++ Source/Editor/Gizmo/IGizmoOwner.cs | 5 ++ Source/Editor/Gizmo/TransformGizmo.cs | 6 +++ Source/Editor/Gizmo/TransformGizmoBase.cs | 51 +++++++++++++++---- .../Editor/Tools/Foliage/EditFoliageGizmo.cs | 22 ++++++++ Source/Editor/Viewport/EditorGizmoViewport.cs | 7 ++- .../Viewport/MainEditorGizmoViewport.cs | 2 +- .../Editor/Viewport/PrefabWindowViewport.cs | 3 ++ 8 files changed, 92 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Gizmo/GizmoBase.cs b/Source/Editor/Gizmo/GizmoBase.cs index a44c677dd..5dfa742bd 100644 --- a/Source/Editor/Gizmo/GizmoBase.cs +++ b/Source/Editor/Gizmo/GizmoBase.cs @@ -80,6 +80,13 @@ namespace FlaxEditor.Gizmo { } + /// + /// Performs scene objects snapping to the ground. + /// + public virtual void SnapToGround() + { + } + /// /// Draws the gizmo. /// diff --git a/Source/Editor/Gizmo/IGizmoOwner.cs b/Source/Editor/Gizmo/IGizmoOwner.cs index d3dc6cbc0..54983b3b8 100644 --- a/Source/Editor/Gizmo/IGizmoOwner.cs +++ b/Source/Editor/Gizmo/IGizmoOwner.cs @@ -89,5 +89,10 @@ namespace FlaxEditor.Gizmo /// Gets a object used by the gizmo owner. /// Undo Undo { get; } + + /// + /// Gets the root tree node for the scene graph. + /// + SceneGraph.RootNode SceneGraphRoot { get; } } } diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index f490de430..6d7a2ccd3 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -218,6 +218,12 @@ namespace FlaxEditor.Gizmo } } + /// + protected override bool IsSelected(SceneGraphNode obj) + { + return _selection.Contains(obj); + } + /// protected override void OnApplyTransformation(ref Vector3 translationDelta, ref Quaternion rotationDelta, ref Vector3 scaleDelta) { diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 768fa0617..60fc7b816 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using FlaxEditor.SceneGraph; using FlaxEngine; namespace FlaxEditor.Gizmo @@ -398,15 +399,7 @@ namespace FlaxEditor.Gizmo // Snap to ground if (_activeAxis == Axis.None && SelectionCount != 0 && Owner.SnapToGround) { - if (Physics.RayCast(Position, Vector3.Down, out var hit, float.MaxValue, uint.MaxValue, false)) - { - StartTransforming(); - var translationDelta = hit.Point - Position; - var rotationDelta = Quaternion.Identity; - var scaleDelta = Vector3.Zero; - OnApplyTransformation(ref translationDelta, ref rotationDelta, ref scaleDelta); - EndTransforming(); - } + SnapToGround(); } // Only when is active else if (_isActive) @@ -517,6 +510,39 @@ namespace FlaxEditor.Gizmo UpdateMatrices(); } + /// + public override void SnapToGround() + { + if (Owner.SceneGraphRoot == null) + return; + var ray = new Ray(Position, Vector3.Down); + while (true) + { + var view = new Ray(Owner.ViewPosition, Owner.ViewDirection); + var rayCastFlags = SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives; + var hit = Owner.SceneGraphRoot.RayCast(ref ray, ref view, out var distance, out _, rayCastFlags); + if (hit != null) + { + // Skip snapping selection to itself + if (IsSelected(hit)) + { + GetSelectedObjectsBounds(out var selectionBounds, out _); + ray.Position = ray.GetPoint(selectionBounds.Size.Y * 0.5f); + continue; + } + + // Snap + StartTransforming(); + var translationDelta = ray.GetPoint(distance) - Position; + var rotationDelta = Quaternion.Identity; + var scaleDelta = Vector3.Zero; + OnApplyTransformation(ref translationDelta, ref rotationDelta, ref scaleDelta); + EndTransforming(); + } + break; + } + } + /// /// Gets a value indicating whether this tool can transform objects. /// @@ -545,6 +571,13 @@ namespace FlaxEditor.Gizmo /// True if editing the selected objects transformations marks the navigation system area dirty (for auto-rebuild), otherwise skip update. protected abstract void GetSelectedObjectsBounds(out BoundingBox bounds, out bool navigationDirty); + /// + /// Checks if the specified object is selected. + /// + /// The object to check. + /// True if it's selected, otherwise false. + protected abstract bool IsSelected(SceneGraphNode obj); + /// /// Called when user starts transforming selected objects. /// diff --git a/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs b/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs index 7443b52ad..5401000c9 100644 --- a/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs +++ b/Source/Editor/Tools/Foliage/EditFoliageGizmo.cs @@ -2,6 +2,7 @@ using System; using FlaxEditor.Gizmo; +using FlaxEditor.SceneGraph; using FlaxEditor.Tools.Foliage.Undo; using FlaxEngine; @@ -87,6 +88,12 @@ namespace FlaxEditor.Tools.Foliage navigationDirty = false; } + /// + protected override bool IsSelected(SceneGraphNode obj) + { + return false; + } + /// protected override void OnStartTransforming() { @@ -231,6 +238,21 @@ namespace FlaxEditor.Tools.Foliage Owner.Undo?.AddAction(action); } + /// + public override void SnapToGround() + { + if (Physics.RayCast(Position, Vector3.Down, out var hit, float.MaxValue, uint.MaxValue, false)) + { + // Snap + StartTransforming(); + var translationDelta = hit.Point - Position; + var rotationDelta = Quaternion.Identity; + var scaleDelta = Vector3.Zero; + OnApplyTransformation(ref translationDelta, ref rotationDelta, ref scaleDelta); + EndTransforming(); + } + } + /// public override void OnActivated() { diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index 5c9fcf034..0e81292a3 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -21,10 +21,12 @@ namespace FlaxEditor.Viewport /// /// The task. /// The undo. - public EditorGizmoViewport(SceneRenderTask task, Undo undo) + /// The scene graph root. + public EditorGizmoViewport(SceneRenderTask task, Undo undo, SceneGraph.RootNode sceneGraphRoot) : base(task, new FPSCamera(), true) { Undo = undo; + SceneGraphRoot = sceneGraphRoot; SetUpdate(ref _update, OnUpdate); } @@ -73,6 +75,9 @@ namespace FlaxEditor.Viewport /// public Undo Undo { get; } + /// + public SceneGraph.RootNode SceneGraphRoot { get; } + /// protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 0c759c950..e29b97dc7 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -184,7 +184,7 @@ namespace FlaxEditor.Viewport /// /// Editor instance. public MainEditorGizmoViewport(Editor editor) - : base(Object.New(), editor.Undo) + : base(Object.New(), editor.Undo, editor.Scene.Root) { _editor = editor; _dragAssets = new DragAssets(ValidateDragItem); diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 4e71677b2..9b2c9b189 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -336,6 +336,9 @@ namespace FlaxEditor.Viewport /// public Undo Undo { get; } + /// + public RootNode SceneGraphRoot => _window.Graph.Root; + /// public EditorViewport Viewport => this; From 6e800ad7bbf73d388582e19b219e14e8b2a1db6c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 14:04:52 +0200 Subject: [PATCH 34/94] Increase fog properties limits --- Source/Engine/Level/Actors/ExponentialHeightFog.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.h b/Source/Engine/Level/Actors/ExponentialHeightFog.h index f46970b0f..ffbcef3a6 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.h +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.h @@ -25,13 +25,13 @@ public: /// /// The fog density factor. /// - API_FIELD(Attributes="EditorOrder(10), DefaultValue(0.02f), Limit(0.000001f, 0.8f, 0.001f), EditorDisplay(\"Exponential Height Fog\")") + API_FIELD(Attributes="EditorOrder(10), DefaultValue(0.02f), Limit(0.0000001f, 100.0f, 0.001f), EditorDisplay(\"Exponential Height Fog\")") float FogDensity = 0.02f; /// /// The fog height density factor that controls how the density increases as height decreases. The smaller values produce more visible transition larger. /// - API_FIELD(Attributes="EditorOrder(20), DefaultValue(0.2f), Limit(0.001f, 2.0f, 0.001f), EditorDisplay(\"Exponential Height Fog\")") + API_FIELD(Attributes="EditorOrder(20), DefaultValue(0.2f), Limit(0.0001f, 10.0f, 0.001f), EditorDisplay(\"Exponential Height Fog\")") float FogHeightFalloff = 0.2f; /// From 2948bfbed069d6b5265812d418b18ec278a63464 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Thu, 12 Aug 2021 14:15:26 +0200 Subject: [PATCH 35/94] Backward compatibility (Mathd -> Math), fix and remove warnings. --- Source/Engine/Core/Math/Math.h | 1 + Source/Engine/Core/Math/Mathd.h | 2 +- Source/Engine/Core/Types/Variant.cpp | 6 +++--- Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Core/Math/Math.h b/Source/Engine/Core/Math/Math.h index 462f71678..f138fadb3 100644 --- a/Source/Engine/Core/Math/Math.h +++ b/Source/Engine/Core/Math/Math.h @@ -2,6 +2,7 @@ #pragma once +#include "Engine/Core/Core.h" #include "Engine/Core/Types/BaseTypes.h" #include diff --git a/Source/Engine/Core/Math/Mathd.h b/Source/Engine/Core/Math/Mathd.h index 8da6f0330..6806ad0a9 100644 --- a/Source/Engine/Core/Math/Mathd.h +++ b/Source/Engine/Core/Math/Mathd.h @@ -5,7 +5,7 @@ #include #include "Engine/Core/Types/BaseTypes.h" -namespace Mathd +namespace Math { /// /// Computes the sine and cosine of a scalar float. diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index af3118858..683b195be 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -951,7 +951,7 @@ bool Variant::operator==(const Variant& other) const case VariantType::Float: return Math::NearEqual(AsFloat, other.AsFloat); case VariantType::Double: - return Mathd::Abs(AsDouble - other.AsDouble) < ZeroTolerance; + return Math::Abs(AsDouble - other.AsDouble) < ZeroTolerance; case VariantType::Pointer: return AsPointer == other.AsPointer; case VariantType::String: @@ -1104,7 +1104,7 @@ Variant::operator bool() const case VariantType::Float: return !Math::IsZero(AsFloat); case VariantType::Double: - return !Mathd::IsZero(AsDouble); + return !Math::IsZero(AsDouble); case VariantType::Pointer: return AsPointer != nullptr; case VariantType::String: @@ -2851,7 +2851,7 @@ Variant Variant::Cast(const Variant& v, const VariantType& to) switch (to.Type) { case VariantType::Bool: - return Variant(Mathd::Abs(v.AsDouble) > ZeroTolerance); + return Variant(Math::Abs(v.AsDouble) > ZeroTolerance); case VariantType::Int16: return Variant((int16)v.AsDouble); case VariantType::Int: diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index dd1873bab..80dadd8c7 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -4,6 +4,7 @@ #include "ModelTool.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Math/Mathd.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Collections/Sorting.h" #include "Engine/Platform/FileSystem.h" From 8074bb2fbe3853a933f435a3b30ad875d8f63b17 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Aug 2021 18:21:46 +0200 Subject: [PATCH 36/94] Add `SuspensionForceOffset` to vehicle wheel config --- Source/Engine/Physics/Actors/WheeledVehicle.cpp | 5 ++--- Source/Engine/Physics/Actors/WheeledVehicle.h | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 2c21e8880..971f882cd 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -324,13 +324,12 @@ void WheeledVehicle::Setup() wheelData.mMaxHandBrakeTorque = M2ToCm2(wheel.MaxHandBrakeTorque); PxVec3 centreOffset = centerOfMassOffset.transformInv(offsets[i]); - float suspensionForceOffset = 0.0f; - PxVec3 forceAppPointOffset(centreOffset.x, centreOffset.y, centreOffset.z + suspensionForceOffset); + PxVec3 forceAppPointOffset(centreOffset.z, centreOffset.y + wheel.SuspensionForceOffset, centreOffset.z); wheelsSimData->setTireData(i, tire); wheelsSimData->setWheelData(i, wheelData); wheelsSimData->setSuspensionData(i, suspensionData); - wheelsSimData->setSuspTravelDirection(i, PxVec3(0, -1, 0)); + wheelsSimData->setSuspTravelDirection(i, centerOfMassOffset.rotate(PxVec3(0.0f, -1.0f, 0.0f))); wheelsSimData->setWheelCentreOffset(i, centreOffset); wheelsSimData->setSuspForceAppPointOffset(i, forceAppPointOffset); wheelsSimData->setTireForceAppPointOffset(i, forceAppPointOffset); diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index 12b5c864c..bbccfd76b 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -228,6 +228,11 @@ public: /// The maximum offset for the suspension that wheel can go below resting location. /// API_FIELD(Attributes="Limit(0)") float SuspensionMaxDrop = 10.0f; + + /// + /// The vertical offset from where suspension forces are applied. + /// + API_FIELD() float SuspensionForceOffset = 0.0f; }; /// From 2d9627df3393033b726f7e340cf588e935623eed Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Perrier Date: Thu, 12 Aug 2021 19:00:41 +0200 Subject: [PATCH 37/94] Tweak docs --- Source/Engine/Core/Math/Mathd.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Core/Math/Mathd.h b/Source/Engine/Core/Math/Mathd.h index 6806ad0a9..47b16b608 100644 --- a/Source/Engine/Core/Math/Mathd.h +++ b/Source/Engine/Core/Math/Mathd.h @@ -8,7 +8,7 @@ namespace Math { /// - /// Computes the sine and cosine of a scalar float. + /// Computes the sine and cosine of a scalar double. /// /// The input angle (in radians). /// The output sine value. @@ -198,7 +198,7 @@ namespace Math } /// - /// Compares the sign of two floating-point values. + /// Compares the sign of two double values. /// /// The first value. /// The second value. @@ -209,7 +209,7 @@ namespace Math } /// - /// Compares the sign of two floating-point values. + /// Compares the sign of two double values. /// /// The first value. /// The second value. @@ -220,7 +220,7 @@ namespace Math } /// - /// Checks if a and b are not even almost equal, taking into account the magnitude of floating point numbers + /// Checks if a and b are not even almost equal, taking into account the magnitude of double numbers /// /// The left value to compare /// The right value to compare From 4984ba1bb3c79ecacb13c5dbe0797daaf2587aba Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Aug 2021 09:58:57 +0200 Subject: [PATCH 38/94] Add `CookCollision` to collision data with triangles as `int32` --- Source/Engine/Physics/CollisionData.cpp | 15 +++++++++++++++ Source/Engine/Physics/CollisionData.h | 13 +++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Source/Engine/Physics/CollisionData.cpp b/Source/Engine/Physics/CollisionData.cpp index 14396bf6a..10dc38e6b 100644 --- a/Source/Engine/Physics/CollisionData.cpp +++ b/Source/Engine/Physics/CollisionData.cpp @@ -78,6 +78,21 @@ bool CollisionData::CookCollision(CollisionDataType type, const Span& v return CookCollision(type, &modelData, convexFlags, convexVertexLimit); } +bool CollisionData::CookCollision(CollisionDataType type, const Span& vertices, const Span& triangles, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) +{ + CHECK_RETURN(vertices.Length() != 0, true); + CHECK_RETURN(triangles.Length() != 0 && triangles.Length() % 3 == 0, true); + ModelData modelData; + modelData.LODs.Resize(1); + auto meshData = New(); + modelData.LODs[0].Meshes.Add(meshData); + meshData->Positions.Set(vertices.Get(), vertices.Length()); + meshData->Indices.Resize(triangles.Length()); + for (int32 i = 0; i < triangles.Length(); i++) + meshData->Indices.Get()[i] = triangles.Get()[i]; + return CookCollision(type, &modelData, convexFlags, convexVertexLimit); +} + bool CollisionData::CookCollision(CollisionDataType type, ModelData* modelData, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) { // Validate state diff --git a/Source/Engine/Physics/CollisionData.h b/Source/Engine/Physics/CollisionData.h index 9436ac80d..19c1efa03 100644 --- a/Source/Engine/Physics/CollisionData.h +++ b/Source/Engine/Physics/CollisionData.h @@ -223,6 +223,19 @@ public: /// The convex mesh vertex limit. Use values in range [8;255] API_FUNCTION() bool CookCollision(CollisionDataType type, const Span& vertices, const Span& triangles, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags::None, int32 convexVertexLimit = 255); + /// + /// Cooks the mesh collision data and updates the virtual asset. action cannot be performed on a main thread. + /// + /// + /// Can be used only for virtual assets (see and ). + /// + /// The collision data type. + /// The source geometry vertex buffer with vertices positions. Cannot be empty. + /// The source data index buffer (triangles list). Uses 32-bit stride buffer. Cannot be empty. Length must be multiple of 3 (as 3 vertices build a triangle). + /// The convex mesh generation flags. + /// The convex mesh vertex limit. Use values in range [8;255] + API_FUNCTION() bool CookCollision(CollisionDataType type, const Span& vertices, const Span& triangles, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags::None, int32 convexVertexLimit = 255); + /// /// Cooks the mesh collision data and updates the virtual asset. action cannot be performed on a main thread. /// From f44e5fb2fe0941bab31a229c1de883dcf1d37450 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Aug 2021 10:04:44 +0200 Subject: [PATCH 39/94] Add support for `uint` as triangle indices for mesh updates API --- Source/Engine/Graphics/Mesh.cs | 101 +++++++++++++++++- Source/Engine/Graphics/Models/Mesh.cpp | 4 +- Source/Engine/Graphics/Models/Mesh.h | 4 +- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 4 +- Source/Engine/Graphics/Models/SkinnedMesh.h | 15 ++- Source/Engine/Graphics/SkinnedMesh.cs | 38 ++++++- 6 files changed, 154 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Graphics/Mesh.cs b/Source/Engine/Graphics/Mesh.cs index f96b4ce63..82491effd 100644 --- a/Source/Engine/Graphics/Mesh.cs +++ b/Source/Engine/Graphics/Mesh.cs @@ -144,7 +144,7 @@ namespace FlaxEngine if (colors != null && colors.Length != vertices.Length) throw new ArgumentOutOfRangeException(nameof(colors)); - if (Internal_UpdateMeshInt( + if (Internal_UpdateMeshUInt( __unmanagedPtr, vertices.Length, triangles.Length / 3, @@ -190,7 +190,100 @@ namespace FlaxEngine if (colors != null && colors.Count != vertices.Count) throw new ArgumentOutOfRangeException(nameof(colors)); - if (Internal_UpdateMeshInt( + if (Internal_UpdateMeshUInt( + __unmanagedPtr, + vertices.Count, + triangles.Count / 3, + Utils.ExtractArrayFromList(vertices), + Utils.ExtractArrayFromList(triangles), + Utils.ExtractArrayFromList(normals), + Utils.ExtractArrayFromList(tangents), + Utils.ExtractArrayFromList(uv), + Utils.ExtractArrayFromList(colors) + )) + throw new FlaxException("Failed to update mesh data."); + } + + /// + /// Updates the model mesh vertex and index buffer data. + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. + /// + /// The mesh vertices positions. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. + /// The normal vectors (per vertex). + /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The texture coordinates (per vertex). + /// The vertex colors (per vertex). + public void UpdateMesh(Vector3[] vertices, uint[] triangles, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null, Color32[] colors = null) + { + // Validate state and input + if (!ParentModel.IsVirtual) + throw new InvalidOperationException("Only virtual models can be updated at runtime."); + if (vertices == null) + throw new ArgumentNullException(nameof(vertices)); + if (triangles == null) + throw new ArgumentNullException(nameof(triangles)); + if (triangles.Length == 0 || triangles.Length % 3 != 0) + throw new ArgumentOutOfRangeException(nameof(triangles)); + if (normals != null && normals.Length != vertices.Length) + throw new ArgumentOutOfRangeException(nameof(normals)); + if (tangents != null && tangents.Length != vertices.Length) + throw new ArgumentOutOfRangeException(nameof(tangents)); + if (tangents != null && normals == null) + throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh."); + if (uv != null && uv.Length != vertices.Length) + throw new ArgumentOutOfRangeException(nameof(uv)); + if (colors != null && colors.Length != vertices.Length) + throw new ArgumentOutOfRangeException(nameof(colors)); + + if (Internal_UpdateMeshUInt( + __unmanagedPtr, + vertices.Length, + triangles.Length / 3, + vertices, triangles, + normals, + tangents, + uv, + colors + )) + throw new FlaxException("Failed to update mesh data."); + } + + /// + /// Updates the model mesh vertex and index buffer data. + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. + /// + /// The mesh vertices positions. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. + /// The normal vectors (per vertex). + /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The texture coordinates (per vertex). + /// The vertex colors (per vertex). + public void UpdateMesh(List vertices, List triangles, List normals = null, List tangents = null, List uv = null, List colors = null) + { + // Validate state and input + if (!ParentModel.IsVirtual) + throw new InvalidOperationException("Only virtual models can be updated at runtime."); + if (vertices == null) + throw new ArgumentNullException(nameof(vertices)); + if (triangles == null) + throw new ArgumentNullException(nameof(triangles)); + if (triangles.Count == 0 || triangles.Count % 3 != 0) + throw new ArgumentOutOfRangeException(nameof(triangles)); + if (normals != null && normals.Count != vertices.Count) + throw new ArgumentOutOfRangeException(nameof(normals)); + if (tangents != null && tangents.Count != vertices.Count) + throw new ArgumentOutOfRangeException(nameof(tangents)); + if (tangents != null && normals == null) + throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh."); + if (uv != null && uv.Count != vertices.Count) + throw new ArgumentOutOfRangeException(nameof(uv)); + if (colors != null && colors.Count != vertices.Count) + throw new ArgumentOutOfRangeException(nameof(colors)); + + if (Internal_UpdateMeshUInt( __unmanagedPtr, vertices.Count, triangles.Count / 3, @@ -314,7 +407,7 @@ namespace FlaxEngine if (triangles.Length == 0 || triangles.Length % 3 != 0) throw new ArgumentOutOfRangeException(nameof(triangles)); - if (Internal_UpdateTrianglesInt( + if (Internal_UpdateTrianglesUInt( __unmanagedPtr, triangles.Length / 3, triangles @@ -338,7 +431,7 @@ namespace FlaxEngine if (triangles.Count == 0 || triangles.Count % 3 != 0) throw new ArgumentOutOfRangeException(nameof(triangles)); - if (Internal_UpdateTrianglesInt( + if (Internal_UpdateTrianglesUInt( __unmanagedPtr, triangles.Count / 3, Utils.ExtractArrayFromList(triangles) diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index ad64a3f61..3ae9b969f 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -608,7 +608,7 @@ ScriptingObject* Mesh::GetParentModel() return _model; } -bool Mesh::UpdateMeshInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj) +bool Mesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj) { return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj); } @@ -618,7 +618,7 @@ bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MonoArray* v return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj); } -bool Mesh::UpdateTrianglesInt(int32 triangleCount, MonoArray* trianglesObj) +bool Mesh::UpdateTrianglesUInt(int32 triangleCount, MonoArray* trianglesObj) { return ::UpdateTriangles(this, triangleCount, trianglesObj); } diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 4af23d3fe..483f15153 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -404,9 +404,9 @@ private: // Internal bindings API_FUNCTION(NoProxy) ScriptingObject* GetParentModel(); - API_FUNCTION(NoProxy) bool UpdateMeshInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj); + API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj); API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj); - API_FUNCTION(NoProxy) bool UpdateTrianglesInt(int32 triangleCount, MonoArray* trianglesObj); + API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, MonoArray* trianglesObj); API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, MonoArray* trianglesObj); API_FUNCTION(NoProxy) bool DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 typeI); }; diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index b5c67bff9..e75136cf2 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -418,9 +418,9 @@ bool UpdateMesh(SkinnedMesh* mesh, MonoArray* verticesObj, MonoArray* trianglesO return mesh->UpdateMesh(vertexCount, triangleCount, vb.Get(), ib); } -bool SkinnedMesh::UpdateMeshInt(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj) +bool SkinnedMesh::UpdateMeshUInt(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj) { - return ::UpdateMesh(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj); + return ::UpdateMesh(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj); } bool SkinnedMesh::UpdateMeshUShort(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj) diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index 108f36b33..462286b44 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -117,6 +117,19 @@ public: return UpdateMesh(vertexCount, triangleCount, vb, ib, false); } + /// + /// Updates the model mesh (used by the virtual models created with Init rather than Load). + /// + /// The amount of vertices in the vertex buffer. + /// The amount of triangles in the index buffer. + /// The vertex buffer data. + /// The index buffer in clockwise order. + /// True if failed, otherwise false. + FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, uint32* ib) + { + return UpdateMesh(vertexCount, triangleCount, vb, ib, false); + } + /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). /// @@ -245,7 +258,7 @@ private: // Internal bindings API_FUNCTION(NoProxy) ScriptingObject* GetParentModel(); - API_FUNCTION(NoProxy) bool UpdateMeshInt(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj); + API_FUNCTION(NoProxy) bool UpdateMeshUInt(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj); API_FUNCTION(NoProxy) bool UpdateMeshUShort(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj); API_FUNCTION(NoProxy) bool DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 typeI); }; diff --git a/Source/Engine/Graphics/SkinnedMesh.cs b/Source/Engine/Graphics/SkinnedMesh.cs index ea4cf283e..d6d77a01b 100644 --- a/Source/Engine/Graphics/SkinnedMesh.cs +++ b/Source/Engine/Graphics/SkinnedMesh.cs @@ -130,7 +130,43 @@ namespace FlaxEngine if (uv != null && uv.Length != vertices.Length) throw new ArgumentOutOfRangeException(nameof(uv)); - if (Internal_UpdateMeshInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv)) + if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv)) + throw new FlaxException("Failed to update mesh data."); + } + + /// + /// Updates the skinned model mesh vertex and index buffer data. + /// Can be used only for virtual assets (see and ). + /// Mesh data will be cached and uploaded to the GPU with a delay. + /// + /// The mesh vertices positions. Cannot be null. + /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null. + /// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null. + /// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null. + /// The normal vectors (per vertex). + /// The normal vectors (per vertex). Use null to compute them from normal vectors. + /// The texture coordinates (per vertex). + public void UpdateMesh(Vector3[] vertices, uint[] triangles, Int4[] blendIndices, Vector4[] blendWeights, Vector3[] normals = null, Vector3[] tangents = null, Vector2[] uv = null) + { + // Validate state and input + if (!ParentSkinnedModel.IsVirtual) + throw new InvalidOperationException("Only virtual skinned models can be updated at runtime."); + if (vertices == null) + throw new ArgumentNullException(nameof(vertices)); + if (triangles == null) + throw new ArgumentNullException(nameof(triangles)); + if (triangles.Length == 0 || triangles.Length % 3 != 0) + throw new ArgumentOutOfRangeException(nameof(triangles)); + if (normals != null && normals.Length != vertices.Length) + throw new ArgumentOutOfRangeException(nameof(normals)); + if (tangents != null && tangents.Length != vertices.Length) + throw new ArgumentOutOfRangeException(nameof(tangents)); + if (tangents != null && normals == null) + throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh."); + if (uv != null && uv.Length != vertices.Length) + throw new ArgumentOutOfRangeException(nameof(uv)); + + if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv)) throw new FlaxException("Failed to update mesh data."); } From af22c7e96f8d2f686224a40cb5ea48cc3bd9a276 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Aug 2021 10:06:29 +0200 Subject: [PATCH 40/94] Refactor `DownloadIndexBuffer` to return unsigned data --- Source/Editor/Windows/Assets/ModelWindow.cs | 12 ++++++------ Source/Editor/Windows/Assets/SkinnedModelWindow.cs | 8 ++++---- Source/Engine/Graphics/Mesh.cs | 4 ++-- Source/Engine/Graphics/SkinnedMesh.cs | 4 ++-- Source/Engine/Utilities/MeshDataCache.cs | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index be7140d61..f08445862 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -538,9 +538,9 @@ namespace FlaxEditor.Windows.Assets for (int i = 0; i < meshData.IndexBuffer.Length; i += 3) { // Cache triangle indices - int i0 = meshData.IndexBuffer[i + 0]; - int i1 = meshData.IndexBuffer[i + 1]; - int i2 = meshData.IndexBuffer[i + 2]; + uint i0 = meshData.IndexBuffer[i + 0]; + uint i1 = meshData.IndexBuffer[i + 1]; + uint i2 = meshData.IndexBuffer[i + 2]; // Cache triangle uvs positions and transform positions to output target Vector2 uv0 = meshData.VertexBuffer[i0].TexCoord * uvScale; @@ -562,9 +562,9 @@ namespace FlaxEditor.Windows.Assets for (int i = 0; i < meshData.IndexBuffer.Length; i += 3) { // Cache triangle indices - int i0 = meshData.IndexBuffer[i + 0]; - int i1 = meshData.IndexBuffer[i + 1]; - int i2 = meshData.IndexBuffer[i + 2]; + uint i0 = meshData.IndexBuffer[i + 0]; + uint i1 = meshData.IndexBuffer[i + 1]; + uint i2 = meshData.IndexBuffer[i + 2]; // Cache triangle uvs positions and transform positions to output target Vector2 uv0 = meshData.VertexBuffer[i0].LightmapUVs * uvScale; diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs index 76639709c..ea5240cb5 100644 --- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs +++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs @@ -645,9 +645,9 @@ namespace FlaxEditor.Windows.Assets for (int i = 0; i < meshData.IndexBuffer.Length; i += 3) { // Cache triangle indices - int i0 = meshData.IndexBuffer[i + 0]; - int i1 = meshData.IndexBuffer[i + 1]; - int i2 = meshData.IndexBuffer[i + 2]; + uint i0 = meshData.IndexBuffer[i + 0]; + uint i1 = meshData.IndexBuffer[i + 1]; + uint i2 = meshData.IndexBuffer[i + 2]; // Cache triangle uvs positions and transform positions to output target Vector2 uv0 = meshData.VertexBuffer[i0].TexCoord * uvScale; @@ -820,7 +820,7 @@ namespace FlaxEditor.Windows.Assets private struct MeshData { - public int[] IndexBuffer; + public uint[] IndexBuffer; public SkinnedMesh.Vertex[] VertexBuffer; } diff --git a/Source/Engine/Graphics/Mesh.cs b/Source/Engine/Graphics/Mesh.cs index 82491effd..6d855788a 100644 --- a/Source/Engine/Graphics/Mesh.cs +++ b/Source/Engine/Graphics/Mesh.cs @@ -592,10 +592,10 @@ namespace FlaxEngine /// If mesh index buffer format (see ) is then it's faster to call . /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data. - public int[] DownloadIndexBuffer(bool forceGpu = false) + public uint[] DownloadIndexBuffer(bool forceGpu = false) { var triangles = TriangleCount; - var result = new int[triangles * 3]; + var result = new uint[triangles * 3]; if (Internal_DownloadBuffer(__unmanagedPtr, forceGpu, result, (int)InternalBufferType.IB32)) throw new FlaxException("Failed to download mesh data."); return result; diff --git a/Source/Engine/Graphics/SkinnedMesh.cs b/Source/Engine/Graphics/SkinnedMesh.cs index d6d77a01b..ef5e5dcd2 100644 --- a/Source/Engine/Graphics/SkinnedMesh.cs +++ b/Source/Engine/Graphics/SkinnedMesh.cs @@ -263,10 +263,10 @@ namespace FlaxEngine /// If mesh index buffer format (see ) is then it's faster to call . /// If set to true the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data. /// The gathered data. - public int[] DownloadIndexBuffer(bool forceGpu = false) + public uint[] DownloadIndexBuffer(bool forceGpu = false) { var triangles = TriangleCount; - var result = new int[triangles * 3]; + var result = new uint[triangles * 3]; if (Internal_DownloadBuffer(__unmanagedPtr, forceGpu, result, (int)InternalBufferType.IB32)) throw new FlaxException("Failed to download mesh data."); return result; diff --git a/Source/Engine/Utilities/MeshDataCache.cs b/Source/Engine/Utilities/MeshDataCache.cs index 0978bdc40..c42b02865 100644 --- a/Source/Engine/Utilities/MeshDataCache.cs +++ b/Source/Engine/Utilities/MeshDataCache.cs @@ -20,7 +20,7 @@ namespace FlaxEngine.Utilities /// /// The index buffer. /// - public int[] IndexBuffer; + public uint[] IndexBuffer; /// /// The vertex buffer. From 7fc144b07b1326f490f2e8dfd9a34c1831367a0c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Aug 2021 13:13:12 +0200 Subject: [PATCH 41/94] Fix some memory leaks --- Source/Engine/Input/Input.cpp | 22 ++++++++++++++++++++++ Source/Engine/Threading/JobSystem.cpp | 10 +++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index 44131504d..788ea6f2e 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -73,6 +73,7 @@ public: } void Update() override; + void Dispose() override; }; InputService InputServiceInstance; @@ -914,3 +915,24 @@ void InputService::Update() } } } + +void InputService::Dispose() +{ + // Dispose input devices + if (Input::Mouse) + { + Input::Mouse->DeleteObject(); + Input::Mouse = nullptr; + } + if (Input::Keyboard) + { + Input::Keyboard->DeleteObject(); + Input::Keyboard = nullptr; + } + for (int32 i = 0; i < Input::Gamepads.Count(); i++) + Input::Gamepads[i]->DeleteObject(); + Input::Gamepads.Clear(); + for (int32 i = 0; i < Input::CustomDevices.Count(); i++) + Input::CustomDevices[i]->DeleteObject(); + Input::CustomDevices.Clear(); +} diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index 7b6651952..56d23901c 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -134,9 +134,13 @@ void JobSystemService::Dispose() for (int32 i = 0; i < ThreadsCount; i++) { - if (Threads[i] && Threads[i]->IsRunning()) - Threads[i]->Kill(true); - Threads[i] = nullptr; + if (Threads[i]) + { + if (Threads[i]->IsRunning()) + Threads[i]->Kill(true); + Delete(Threads[i]); + Threads[i] = nullptr; + } } } From bc634e9d377bee90025f54c7224b4fee460c3928 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Aug 2021 13:44:29 +0200 Subject: [PATCH 42/94] Refactor Tracy source locations to be static #597 --- Source/Engine/Content/Assets/VisualScript.cpp | 7 ++- Source/Engine/Content/Assets/VisualScript.h | 4 ++ Source/Engine/Profiler/ProfilerCPU.h | 26 +++++------ Source/Engine/Profiler/ProfilerSrcLoc.h | 18 ++++++++ .../Scripting/ManagedCLR/MMethod.Mono.cpp | 9 +++- Source/Engine/Scripting/ManagedCLR/MMethod.h | 4 ++ .../Engine/Scripting/Scripting.Internal.cpp | 45 ++++++++++++++++++- .../ThirdParty/tracy/client/TracyScoped.hpp | 17 +++++++ .../ThirdParty/tracy/common/TracySystem.hpp | 1 + .../Bindings/BindingsGenerator.Cpp.cs | 4 +- 10 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 Source/Engine/Profiler/ProfilerSrcLoc.h diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 184360f1f..fe3279856 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -1222,6 +1222,11 @@ Asset::LoadResult VisualScript::load() method.ProfilerName.Get()[assetName.Length()] = ':'; method.ProfilerName.Get()[assetName.Length() + 1] = ':'; Platform::MemoryCopy(method.ProfilerName.Get() + assetName.Length() + 2, method.Name.Get(), method.Name.Length()); + method.ProfilerData.name = method.ProfilerName.Get(); + method.ProfilerData.function = method.Name.Get(); + method.ProfilerData.file = nullptr; + method.ProfilerData.line = 0; + method.ProfilerData.color = 0; } #endif @@ -2139,7 +2144,7 @@ VisualScriptingBinaryModule* VisualScripting::GetBinaryModule() Variant VisualScripting::Invoke(VisualScript::Method* method, ScriptingObject* instance, Span parameters) { CHECK_RETURN(method && method->Script->IsLoaded(), Variant::Zero); - PROFILE_CPU_NAMED(*method->ProfilerName); + PROFILE_CPU_SRC_LOC(method->ProfilerData); // Add to the calling stack ScopeContext scope; diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index db6b5ebd3..3ea98e9a3 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -5,6 +5,9 @@ #include "../BinaryAsset.h" #include "Engine/Scripting/BinaryModule.h" #include "Engine/Visject/VisjectGraph.h" +#if COMPILE_WITH_PROFILER +#include "Engine/Profiler/ProfilerSrcLoc.h" +#endif #define VISUAL_SCRIPT_GRAPH_MAX_CALL_STACK 250 #define VISUAL_SCRIPT_DEBUGGING USE_EDITOR @@ -118,6 +121,7 @@ public: Array> ParamNames; #if COMPILE_WITH_PROFILER StringAnsi ProfilerName; + SourceLocationData ProfilerData; #endif }; diff --git a/Source/Engine/Profiler/ProfilerCPU.h b/Source/Engine/Profiler/ProfilerCPU.h index 2a48e6c15..7ffdc85eb 100644 --- a/Source/Engine/Profiler/ProfilerCPU.h +++ b/Source/Engine/Profiler/ProfilerCPU.h @@ -386,33 +386,31 @@ struct TIsPODType enum { Value = true }; }; +#include "ProfilerSrcLoc.h" + // Shortcut macros for profiling a single code block execution on CPU // Use ZoneTransient for Tracy for code that can be hot-reloaded (eg. in Editor) or if name can be a variable +#define PROFILE_CPU_USE_TRANSIENT_DATA 0 -#define PROFILE_CPU_NAMED(name) ZoneTransientN(___tracy_scoped_zone, name, true); ScopeProfileBlockCPU ProfileBlockCPU(name) - -#if defined(_MSC_VER) - -#if USE_EDITOR -#define PROFILE_CPU() ZoneTransient(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(TEXT(__FUNCTION__)) -#else -#define PROFILE_CPU() ZoneNamed(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(TEXT(__FUNCTION__)) -#endif - -#else - -#if USE_EDITOR +#if PROFILE_CPU_USE_TRANSIENT_DATA #define PROFILE_CPU() ZoneTransient(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(__FUNCTION__) +#define PROFILE_CPU_NAMED(name) ZoneTransientN(___tracy_scoped_zone, name, true); ScopeProfileBlockCPU ProfileBlockCPU(name) #else #define PROFILE_CPU() ZoneNamed(___tracy_scoped_zone, true); ScopeProfileBlockCPU ProfileBlockCPU(__FUNCTION__) +#define PROFILE_CPU_NAMED(name) ZoneNamedN(___tracy_scoped_zone, name, true); ScopeProfileBlockCPU ProfileBlockCPU(name) #endif +#ifdef TRACY_ENABLE +#define PROFILE_CPU_SRC_LOC(srcLoc) tracy::ScopedZone ___tracy_scoped_zone( (tracy::SourceLocationData*)&(srcLoc) ); ScopeProfileBlockCPU ProfileBlockCPU((srcLoc).name) +#else +#define PROFILE_CPU_SRC_LOC(srcLoc) ScopeProfileBlockCPU ProfileBlockCPU((srcLoc).name) #endif #else // Empty macros for disabled profiler -#define PROFILE_CPU_NAMED(name) #define PROFILE_CPU() +#define PROFILE_CPU_NAMED(name) +#define PROFILE_CPU_SRC_LOC(srcLoc) #endif diff --git a/Source/Engine/Profiler/ProfilerSrcLoc.h b/Source/Engine/Profiler/ProfilerSrcLoc.h new file mode 100644 index 000000000..6ee116668 --- /dev/null +++ b/Source/Engine/Profiler/ProfilerSrcLoc.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if COMPILE_WITH_PROFILER + +#include "Engine/Core/Types/BaseTypes.h" + +struct FLAXENGINE_API SourceLocationData +{ + const char* name; + const char* function; + const char* file; + uint32 line; + uint32 color; +}; + +#endif diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp index 3fb8a16a4..6abff601d 100644 --- a/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MMethod.Mono.cpp @@ -60,18 +60,23 @@ MMethod::MMethod(MonoMethod* monoMethod, const char* name, MClass* parentClass) ProfilerName.Get()[className.Length()] = ':'; ProfilerName.Get()[className.Length() + 1] = ':'; Platform::MemoryCopy(ProfilerName.Get() + className.Length() + 2, _name.Get(), _name.Length()); + ProfilerData.name = ProfilerName.Get(); + ProfilerData.function = _name.Get(); + ProfilerData.file = nullptr; + ProfilerData.line = 0; + ProfilerData.color = 0; #endif } MonoObject* MMethod::Invoke(void* instance, void** params, MonoObject** exception) const { - PROFILE_CPU_NAMED(*ProfilerName); + PROFILE_CPU_SRC_LOC(ProfilerData); return mono_runtime_invoke(_monoMethod, instance, params, exception); } MonoObject* MMethod::InvokeVirtual(MonoObject* instance, void** params, MonoObject** exception) const { - PROFILE_CPU_NAMED(*ProfilerName); + PROFILE_CPU_SRC_LOC(ProfilerData); MonoMethod* virtualMethod = mono_object_get_virtual_method(instance, _monoMethod); return mono_runtime_invoke(virtualMethod, instance, params, exception); } diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.h b/Source/Engine/Scripting/ManagedCLR/MMethod.h index 60701a9b3..46320013f 100644 --- a/Source/Engine/Scripting/ManagedCLR/MMethod.h +++ b/Source/Engine/Scripting/ManagedCLR/MMethod.h @@ -3,6 +3,9 @@ #pragma once #include "Engine/Core/Collections/Array.h" +#if COMPILE_WITH_PROFILER +#include "Engine/Profiler/ProfilerSrcLoc.h" +#endif #include "MTypes.h" /// @@ -42,6 +45,7 @@ public: #if COMPILE_WITH_PROFILER MString ProfilerName; + SourceLocationData ProfilerData; #endif #if USE_MONO diff --git a/Source/Engine/Scripting/Scripting.Internal.cpp b/Source/Engine/Scripting/Scripting.Internal.cpp index b437c442e..1c07d3cf4 100644 --- a/Source/Engine/Scripting/Scripting.Internal.cpp +++ b/Source/Engine/Scripting/Scripting.Internal.cpp @@ -9,13 +9,29 @@ #include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Profiler/Profiler.h" +#if TRACY_ENABLE && !PROFILE_CPU_USE_TRANSIENT_DATA +#include "Engine/Core/Collections/ChunkedArray.h" +#endif +#include "Engine/Core/Types/Pair.h" #include "Engine/Threading/Threading.h" #include namespace ProfilerInternal { #if COMPILE_WITH_PROFILER - Array ManagedEventsGPU; + Array> ManagedEventsGPU; +#if TRACY_ENABLE && !PROFILE_CPU_USE_TRANSIENT_DATA + CriticalSection ManagedSourceLocationsLocker; + + struct Location + { + String Name; + StringAnsi NameAnsi; + tracy::SourceLocationData SrcLocation; + }; + + ChunkedArray ManagedSourceLocations; +#endif #endif void BeginEvent(MonoString* nameObj) @@ -24,7 +40,34 @@ namespace ProfilerInternal const StringView name((const Char*)mono_string_chars(nameObj), mono_string_length(nameObj)); ProfilerCPU::BeginEvent(*name); #if TRACY_ENABLE +#if PROFILE_CPU_USE_TRANSIENT_DATA tracy::ScopedZone::Begin(__LINE__, __FILE__, strlen( __FILE__ ), __FUNCTION__, strlen( __FUNCTION__ ), name.Get(), name.Length() ); +#else + ScopeLock lock(ManagedSourceLocationsLocker); + tracy::SourceLocationData* srcLoc = nullptr; + for (auto& e = ManagedSourceLocations.Begin(); e.IsNotEnd(); ++e) + { + if (name == e->Name) + { + srcLoc = &e->SrcLocation; + break; + } + } + if (!srcLoc) + { + auto& e = ManagedSourceLocations.AddOne(); + e.Name = name; + e.NameAnsi = name.Get(); + srcLoc = &e.SrcLocation; + srcLoc->name = e.NameAnsi.Get(); + srcLoc->function = nullptr; + srcLoc->file = nullptr; + srcLoc->line = 0; + srcLoc->color = 0; + } + //static constexpr tracy::SourceLocationData tracySrcLoc{ nullptr, __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 }; + tracy::ScopedZone::Begin(srcLoc); +#endif #endif #endif } diff --git a/Source/ThirdParty/tracy/client/TracyScoped.hpp b/Source/ThirdParty/tracy/client/TracyScoped.hpp index 5de03105c..b18f02388 100644 --- a/Source/ThirdParty/tracy/client/TracyScoped.hpp +++ b/Source/ThirdParty/tracy/client/TracyScoped.hpp @@ -12,8 +12,22 @@ namespace tracy { +void ScopedZone::Begin(const SourceLocationData* srcloc) +{ +#ifdef TRACY_ON_DEMAND + if (!GetProfiler().IsConnected()) return; +#endif + TracyLfqPrepare( QueueType::ZoneBegin ); + MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); + MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc ); + TracyLfqCommit; +} + void ScopedZone::Begin(uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const Char* name, size_t nameSz) { +#ifdef TRACY_ON_DEMAND + if (!GetProfiler().IsConnected()) return; +#endif TracyLfqPrepare( QueueType::ZoneBeginAllocSrcLoc ); const auto srcloc = Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz ); MemWrite( &item->zoneBegin.time, Profiler::GetTime() ); @@ -23,6 +37,9 @@ void ScopedZone::Begin(uint32_t line, const char* source, size_t sourceSz, const void ScopedZone::End() { +#ifdef TRACY_ON_DEMAND + if (!GetProfiler().IsConnected()) return; +#endif TracyLfqPrepare( QueueType::ZoneEnd ); MemWrite( &item->zoneEnd.time, Profiler::GetTime() ); TracyLfqCommit; diff --git a/Source/ThirdParty/tracy/common/TracySystem.hpp b/Source/ThirdParty/tracy/common/TracySystem.hpp index b2d0e8dda..8a699886b 100644 --- a/Source/ThirdParty/tracy/common/TracySystem.hpp +++ b/Source/ThirdParty/tracy/common/TracySystem.hpp @@ -46,6 +46,7 @@ struct TRACY_API SourceLocationData class TRACY_API ScopedZone { public: + static void Begin( const SourceLocationData* srcloc ); static void Begin( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const Char* name, size_t nameSz ); static void End(); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index e61a2cf0a..07d9b490a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1028,7 +1028,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Script.ScriptVTable;"); contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableIndex}]);"); contents.AppendLine($" auto method = scriptVTable[{scriptVTableIndex}];"); - contents.AppendLine(" PROFILE_CPU_NAMED(*method->ProfilerName);"); + contents.AppendLine($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::{functionInfo.Name}\");"); contents.AppendLine(" MonoObject* exception = nullptr;"); contents.AppendLine(" auto prevWrapperCallInstance = WrapperCallInstance;"); @@ -1290,7 +1290,7 @@ namespace Flax.Build.Bindings contents.Append(" if (!mmethod)").AppendLine(); contents.AppendFormat(" mmethod = {1}::TypeInitializer.GetType().ManagedClass->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine(); contents.Append(" CHECK(mmethod);").AppendLine(); - contents.Append(" PROFILE_CPU_NAMED(*mmethod->ProfilerName);").AppendLine(); + contents.Append($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::On{eventInfo.Name}\");").AppendLine(); contents.Append(" MonoObject* exception = nullptr;").AppendLine(); if (paramsCount == 0) contents.AppendLine(" void** params = nullptr;"); From 2e18db7bb8bcb167ed5a6429e92060a27fed9a8a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Aug 2021 13:44:48 +0200 Subject: [PATCH 43/94] Reenable memory allocations profiling in Editor with Tracy --- Source/Engine/Platform/Base/PlatformBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 4459b4f37..e1383c152 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -167,7 +167,7 @@ void PlatformBase::Exit() #if COMPILE_WITH_PROFILER -#define TRACY_ENABLE_MEMORY (TRACY_ENABLE && !USE_EDITOR) +#define TRACY_ENABLE_MEMORY (TRACY_ENABLE) void PlatformBase::OnMemoryAlloc(void* ptr, uint64 size) { From 775e8df752c895f9e7f76883d1a240ac82ad351c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 13 Aug 2021 15:57:10 +0200 Subject: [PATCH 44/94] Fix compilation --- Source/Engine/Scripting/Scripting.Internal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/Scripting.Internal.cpp b/Source/Engine/Scripting/Scripting.Internal.cpp index 1c07d3cf4..1f26fc8b5 100644 --- a/Source/Engine/Scripting/Scripting.Internal.cpp +++ b/Source/Engine/Scripting/Scripting.Internal.cpp @@ -45,7 +45,7 @@ namespace ProfilerInternal #else ScopeLock lock(ManagedSourceLocationsLocker); tracy::SourceLocationData* srcLoc = nullptr; - for (auto& e = ManagedSourceLocations.Begin(); e.IsNotEnd(); ++e) + for (auto e = ManagedSourceLocations.Begin(); e.IsNotEnd(); ++e) { if (name == e->Name) { From 6376b833274ebe05508176bab6371ad78b4fdf1d Mon Sep 17 00:00:00 2001 From: ScottLongley Date: Sun, 15 Aug 2021 19:48:12 +1000 Subject: [PATCH 45/94] Add LookingAt to Actor --- Source/Engine/Level/Actor.cpp | 27 ++++++++++++++++++++------- Source/Engine/Level/Actor.h | 13 +++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 076ac4608..1bbae93fa 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1424,10 +1424,24 @@ Actor* Actor::Intersects(const Ray& ray, float& distance, Vector3& normal) } void Actor::LookAt(const Vector3& worldPos) +{ + const Quaternion orientation = LookingAt(worldPos); + + SetOrientation(orientation); +} + +void Actor::LookAt(const Vector3& worldPos, const Vector3& worldUp) +{ + const Quaternion orientation = LookingAt(worldPos, worldUp); + + SetOrientation(orientation); +} + +Quaternion Actor::LookingAt(const Vector3& worldPos) { const Vector3 direction = worldPos - _transform.Translation; if (direction.LengthSquared() < ZeroTolerance) - return; + return _parent->GetOrientation(); const Vector3 newForward = Vector3::Normalize(direction); const Vector3 oldForward = _transform.Orientation * Vector3::Forward; @@ -1447,26 +1461,25 @@ void Actor::LookAt(const Vector3& worldPos) orientation = rotQuat * _transform.Orientation; } - SetOrientation(orientation); + return orientation; } -void Actor::LookAt(const Vector3& worldPos, const Vector3& worldUp) +Quaternion Actor::LookingAt(const Vector3& worldPos, const Vector3& worldUp) { const Vector3 direction = worldPos - _transform.Translation; if (direction.LengthSquared() < ZeroTolerance) - return; + return _parent->GetOrientation(); const Vector3 forward = Vector3::Normalize(direction); const Vector3 up = Vector3::Normalize(worldUp); if (Math::IsOne(Vector3::Dot(forward, up))) { - LookAt(worldPos); - return; + return LookingAt(worldPos); } Quaternion orientation; Quaternion::LookRotation(direction, up, orientation); - SetOrientation(orientation); + return orientation; } void WriteObjectToBytes(SceneObject* obj, rapidjson_flax::StringBuffer& buffer, MemoryWriteStream& output) diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 890d9f2f1..56930eccf 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -792,6 +792,19 @@ public: /// The up direction that Constrains y axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel. API_FUNCTION() void LookAt(const Vector3& worldPos, const Vector3& worldUp); + /// + /// Gets rotation of the actor oriented towards the specified world position. + /// + /// The world position to orient towards. + API_FUNCTION() Quaternion LookingAt(const Vector3& worldPos); + + /// + /// Gets rotation of the actor oriented towards the specified world position with upwards direction. + /// + /// The world position to orient towards. + /// The up direction that Constrains y axis orientation to a plane this vector lies on. This rule might be broken if forward and up direction are nearly parallel. + API_FUNCTION() Quaternion LookingAt(const Vector3& worldPos, const Vector3& worldUp); + public: /// From 294a4d5477586fb779477bef534082ca2c2daa0c Mon Sep 17 00:00:00 2001 From: Zbigniew Skowron Date: Sun, 15 Aug 2021 14:33:37 +0200 Subject: [PATCH 46/94] Added LinkEnvironment.LinkAsConsoleProgram flag. --- .../Tools/Flax.Build/Build/NativeCpp/LinkEnvironment.cs | 8 +++++++- .../Flax.Build/Platforms/Windows/WindowsToolchainBase.cs | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/LinkEnvironment.cs b/Source/Tools/Flax.Build/Build/NativeCpp/LinkEnvironment.cs index f66dc513c..85a715af6 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/LinkEnvironment.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/LinkEnvironment.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; @@ -76,6 +76,11 @@ namespace Flax.Build.NativeCpp /// public bool GenerateWindowsMetadata = false; + /// + /// Use CONSOLE subsystem on Windows instead of the WINDOWS one. + /// + public bool LinkAsConsoleProgram = false; + /// /// Enables documentation generation. /// @@ -114,6 +119,7 @@ namespace Flax.Build.NativeCpp LinkTimeCodeGeneration = LinkTimeCodeGeneration, UseIncrementalLinking = UseIncrementalLinking, GenerateWindowsMetadata = GenerateWindowsMetadata, + LinkAsConsoleProgram = LinkAsConsoleProgram, GenerateDocumentation = GenerateDocumentation }; foreach (var e in InputFiles) diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index 8a1b3b43f..5caf3e806 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -675,7 +675,14 @@ namespace Flax.Build.Platforms } // Specify subsystem - args.Add("/SUBSYSTEM:WINDOWS"); + if (linkEnvironment.LinkAsConsoleProgram) + { + args.Add("/SUBSYSTEM:CONSOLE"); + } + else + { + args.Add("/SUBSYSTEM:WINDOWS"); + } // Generate Windows Metadata if (linkEnvironment.GenerateWindowsMetadata) From def293f88b6c8df64e754ab6fbfe3d1a47418f5b Mon Sep 17 00:00:00 2001 From: Zbigniew Skowron Date: Sun, 15 Aug 2021 14:35:52 +0200 Subject: [PATCH 47/94] Added Catch2 library for native unit tests. --- Source/ThirdParty/catch2/LICENSE.txt | 23 + Source/ThirdParty/catch2/catch.hpp | 17959 +++++++++++++++++++++ Source/ThirdParty/catch2/catch2.Build.cs | 31 + 3 files changed, 18013 insertions(+) create mode 100644 Source/ThirdParty/catch2/LICENSE.txt create mode 100644 Source/ThirdParty/catch2/catch.hpp create mode 100644 Source/ThirdParty/catch2/catch2.Build.cs diff --git a/Source/ThirdParty/catch2/LICENSE.txt b/Source/ThirdParty/catch2/LICENSE.txt new file mode 100644 index 000000000..36b7cd93c --- /dev/null +++ b/Source/ThirdParty/catch2/LICENSE.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Source/ThirdParty/catch2/catch.hpp b/Source/ThirdParty/catch2/catch.hpp new file mode 100644 index 000000000..7e706f947 --- /dev/null +++ b/Source/ThirdParty/catch2/catch.hpp @@ -0,0 +1,17959 @@ +/* + * Catch v2.13.7 + * Generated: 2021-07-28 20:29:27.753164 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 7 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html +#ifdef __APPLE__ +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # include + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept { return file[0] == '\0'; } + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } + + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; + + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; + + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; + + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template